Anteriormente aprendimos a crear la sala de chat en la que recibimos y enviamos mensajes, todo ello centralizado en la base de datos de Firebase. En esta parte, vamos a trabajar en una implementación en OpenChat que nos permitirá enviar y recibir imágenes en nuestro chat. Para ello trabajaremos con imágenes que quedarán guardadas en Firebase Storage, como hemos visto en la sección anterior.

Paso 1: Añadir un botón para subir imágenes

Para ello, en el componente SendBox añadiremos el método PostImage(), el cual se encarga de llamar a un evento de Electron que crearemos en el siguiente paso. Es importante que creemos un botón que llame al método para así poder iniciar el proceso de subida de imágenes. Quedaría algo tal que así:

import React from 'react'
import { ipcRenderer } from 'electron'

class SendBox extends React.Component {
    constructor(props) {
        super(props)

        this.sendMessage = this.sendMessage.bind(this)
        this.postImage = this.postImage.bind(this)
    }
    sendMessage() {
        const messageObject = {
            message: document.getElementById('send-message').value,
            userURL: 'https://avatars.slack-edge.com/2017-11-27/279122939639_c199c00a34366734b118_72.jpg',
            userName: 'Miguh Ruiz'
        }

        ipcRenderer.send('newMessage', messageObject)
    }
    postImage() {
        ipcRenderer.send('upload-dialog')
    }
    render() {
        return(
            <div className="grid-x grid-margin-x">
                <a className="button warning large-1 cell" onClick={this.postImage}>Foto</a>
                <input type="text" id="send-message" className="large-10 cell" placeholder="Escribe tu mensaje aqui" />
                <a className="button success large-1 cell" onClick={this.sendMessage}>Enviar</a>
            </div>
        )
    }
}

export default SendBox

Paso 2: Creando el evento upload-dialog

En el archivo index.js de Electron procederemos a añadir un listener para dicho evento. Debemos destacar que tendremos que usar la API de dialog que nos permite abrir ventanas emergentes para seleccionar archivos, directorios… Para ello usamos el método dialog.showOpenDialog() pues pretendemos poder seleccionar un archivo. Este método lleva un objeto de configuración como segundo parámetro que nos permitirá definir cosas como los filters. Los filters nos ayudan para delimitar el tipo de archivos que puede seleccionar el usuario. Como puedes apreciar en esta ocasión el usuario solo puede seleccionar archivos con extensión JPG, PNG y GIF pues estamos buscando que el usuario suba imágenes. El resultado debe de ser parecido a:

ipcMain.on('upload-dialog', (ev) => {
    dialog.showOpenDialog(win, {
        title: 'Elige la imagen que deseas subir',
        properties: [
            'openFile'
        ],
        filters: [
            { name: 'Images', extensions: ['jpg', 'png', 'gif'] }
        ]
    }, (dir) => {
        uploadImage(dir[0]) // dir[0] => Ruta del archivo
    })
})

La función uploadImages() será la encargada de subir nuestra imagen a Firebase. Esta la vamos a crear en nuestro archivo de utilidades(/utilities.js) y la importaremos al principio del archivo en el que nos encontramos:

const { app, BrowserWindow, ipcMain, dialog } = require('electron')
const admin = require('firebase-admin')
const { uploadImage } = require('./utilities')

[...]

Paso 3: Creando la utilidad para subir imágenes a Firebase Storage

Usando los métodos que vimos en la sección anterior procedemos a inicializar el módulo de Google Cloud Storage que necesitaremos instalar usando:

$ npm i @google-cloud/storage

Procedamos una vez tengamos instalado el módulo a crear la función que mencionamos en el paso anterior. Como vimos en la introducción a Firebase Storage tendremos que usar el módulo de Google Cloud.

Antes que nada(dentro de la función) para comenzar con el método crearemos una instancia del módulo, a esta le pasaremos datos como el id de nuestra aplicación de Firebase y el JSON con las claves.

El método esencial para construir el puente entre Google Cloud y Firebase será el método storage.bucket() al que le pasaremos la URL de nuestro bucket. La puedes encontrar en el panel de administración de Firebase y viene precedida por el protocolo gs://.
A esto le añadimos la llamada al método bucket.upload() que pasándole como parámetro la ruta en la que se encuentra nuestro archivo, la cual la hemos obtenido anteriormente de la ventana emergente. Debe de quedarte algo como lo siguiente:

const Storage = require('@google-cloud/storage')
const fs = require('fs')

const uploadImage = function(imageDir) {
    const storage = new Storage({
        projectId: 'openchat17',
        keyFilename: './firebase_key.json'
    })
    const bucket = storage.bucket('openchat17.appspot.com')

    bucket.upload(imageDir, (err, file) => {
        if(err) throw err
    })
}

module.exports = {
    uploadImage
}

Paso 4: Adaptando el sistema de mensajes para recibir imágenes

Retomando la manera en la que estamos grabando los mensajes en la base de datos, nos encontramos que el objeto que se le manda a Firebase y que es parte de nuestro componente esta construido tal que así:

const messageObject = {
    message: document.getElementById('send-message').value,
    userURL: 'https://avatars.slack-edge.com/2017-11-27/279122939639_c199c00a34366734b118_72.jpg',
    userName: 'Miguh Ruiz'
}

Para poder comenzar a trabajar con imágenes deberemos de adaptar este objeto pues tras subir las imágenes tendremos que referenciarlas en la base de datos, pudiéndolas desplegar de esta manera cuando carguen los mensajes. Convertiremos la propiedad message que hasta ahora era un String a Objeto de manera que contenga a su vez una propiedad type para diferenciar los mensajes que son textos de los que son imágenes. Debería de quedarte algo como:

const messageObject = {
    message: 
    {
        type: 'text',
        value: document.getElementById('send-message').value
    },
    userURL: 'https://avatars.slack-edge.com/2017-11-27/279122939639_c199c00a34366734b118_72.jpg',
    userName: 'Miguh Ruiz'
}

Paso 5: Obteniendo la URL de las imágenes para enviarlas a la base de datos

Una vez tenemos la estructura para la base de datos bien definida, tendremos que obtener la dirección de la imagen que estamos subiendo e incorporarla a un objeto de mensaje para de esta manera subirla a la base de datos. actualizaremos la función uploadImage() y usaremos el método file.getSignedUrl() para llevar este procedimiento a cabo:

const uploadImage = function(ev, imageDir) {
    const storage = new Storage({
        projectId: 'openchat17',
        keyFilename: './firebase_key.json'
    })
    const bucket = storage.bucket('openchat17.appspot.com')

    bucket.upload(imageDir, (err, file) => {
        if(err) throw err

        file.getSignedUrl({
            action: 'read',
            expires: '12-31-2999'
        },)
            .then(urls => {
                const messageObject = {
                    message: 
                    {
                        type: 'image',
                        value: urls[0]
                    },
                    userURL: 'https://avatars.slack-edge.com/2017-11-27/279122939639_c199c00a34366734b118_72.jpg',
                    userName: 'Miguh Ruiz'
                }
                sendMessage(db, messageObject)

            })
    })
}

Si te fijas estamos usando una función llamada sendMessage() que no hace más que transmitir este objeto hacia la base de datos usando los métodos que ya hemos visto, la función quedó así:

const sendMessage = function(message) {
    const docRef = db.ref('messages')

    docRef.push(message)
}

Paso 6: Adaptando la visualización de mensajes para ver textos e imágenes

Una vez estemos diferenciando en la base de datos los mensajes que los usuarios envían deberemos de reflejar estos cambios en las vistas. Para ello en el componente Message crearemos un if que nos permitirá distinguir los mensajes de tipo texto y los de tipo imagen:

import React from 'react'

function Message(props) {
    if(props.message.message.type == 'image') {
        return(
            <div className="Message row">
                <img src={props.message.userURL} className="large-2 columns"/>
                <div className="large-10 columns">
                    <b>{props.message.userName}</b>
                    <img src={props.message.message.value} />
                </div>
            </div>
        )
    } else {
        return(
            <div className="Message row">
                <img src={props.message.userURL} className="large-2 columns"/>
                <div className="large-10 columns">
                    <b>{props.message.userName}</b>
                    <p>{props.message.message.value}</p>
                </div>
            </div>
        )
    }
}

export default Message

Conclusión

Como hemos visto, nuestra aplicación no es más que un puzzle al que le vamos añadiendo piezas, que sin dudas son totalmente opcionales y que pueden ser tantas como queramos. En la próxima sección haremos un inciso en el desarrollo tal como en la sección anterior pero esta vez para explicar los fundamentos de Firebase Auth y, como puedes predecir, tras ello retomaremos el desarrollo para comenzar a trabajar con la autenticación.