Socket.io: interoperatividad en portal e-commerce

Publicado por David Caviedes Márquez el

socketioMagnoliacommerceToolsDXPDesarrollo Web

En ocasiones, la interoperatividad entre sistemas puede resultar engorrosa y no trivial en un primer momento, máxime cuando los sistemas involucrados son independientes, basados en plataformas distintas y/o inconscientes los unos de los otros.

A continuación, se expondrá un escenario real de un portal e-commerce basado en Magnolia y commerceTools en el que surgió una necesidad de dicha interoperatividad, para, a continuación, mostrar la solución implementada de una forma sencilla y utilizando tecnologías estándares y extendidas.

Necesidad inicial

La necesidad se plantea sobre el siguiente escenario:

  • Plataforma e-commerce basada en commerceTools
  • Portal e-commerce desarrollado en Magnolia 6 que integra commerceTools a través del connector pack ofrecido por el fabricante

La plataforma commerceTools permite, entre otras cosas, gestionar los diferentes productos que serán posteriormente vendidos. Estos productos son almacenados directamente en commerceTools, y además, se ponen a disposición de los clientes a través del portal desarrollado en Magnolia.

Sin embargo, algunos vendedores ofrecen productos especiales o concretos que no forman parte del catálogo presente en commerceTools, pero que necesitan exponer también a través del portal desarrollado en Magnolia y, además, que la transacción de venta se realice a través de commerceTools. Esta operativa es posible gracias a una funcionalidad ofrecida por la plataforma e-commerce llamada “líneas de pedido personalizadas” (custom line items), que son elementos genéricos que pueden ser agregados al carrito pero que no corresponden con ningún producto dado de alta en commerceTools. Estas líneas de pedido personalizadas se suelen utilizar para poder añadir productos virtuales no existentes en la plataforma de ecommerce pero que pueden incluirse al proceso de compra.

En concreto, estos productos especiales se almacenan en una BD externa al portal y a la plataforma e-commerce, y son consumidos por un iframe desarrollado expreso para ello. Este iframe cuenta con capacidad para configurar dichos productos y agregarlos al carrito presente en la sesión actual de cliente y que pertenece a la plataforma e-commerce. De esta forma, queda cubierta la necesidad de poder ofrecer estos productos a los clientes y además añadirlos a la cesta, sin embargo, es commerceTools el sistema que recibe la información cuando estos elementos se agregan al carrito, ya que la comunicación commerceTools <-> iframe es directa, pero el portal desarrollado en Magnolia, que es el que contiene tanto al iframe como al carrito que está visualizando el cliente, no es consciente de esta actualización, por tanto, el portal no es capaz de refrescar el carrito para mostrar los productos que realmente contiene.

Este problema surge a consecuencia de la naturaleza encapsulada e independiente que caracterizan a los iframes, ya que son elementos HTML con un contexto de navegación anidado, con su propio historial de sesión y su propio objeto Document. Además, el iframe opera bajo un dominio diferente al portal web desarrollado en Magnolia. Es por ello que no existe una forma de acceder desde el iframe a la web Magnolia que lo contiene, por lo que es necesario buscar alternativas para conseguir esta comunicación y resolver el problema.

Solución

Algunas de las soluciones más comunes que nos pueden venir a la mente pueden ser utilizar webhooks o servicios REST para implementar la notificación entre sistemas, pero estas soluciones serían válidas en un escenario en el que la retrollamada desencadenase una acción que no implique la ejecución de código javascript en el navegador del cliente, o en el que uno de los dos puntos de comunicación fuese un backend, aunque no es nuestro caso.

La solución la encontramos en socket.io. Esta biblioteca javascript permite la comunicación bidireccional en tiempo real, basada en eventos, entre clientes (navegadores / clientes Node.js) y servidores web (normalmente Node.js, aunque hay más implementaciones del servidor de socket.io basadas en otros lenguajes). Con esta solución no será el cliente el que tenga que pedir información al servidor sino que es el servidor el que emite las notificaciones a los clientes cuando haya un cambio de estado.

Entre las características que ofrece, destacan:

  • Rendimiento: En la mayoría de los casos, la conexión se establece a través de WebSocket, proporcionando una comunicación ligera entre el servidor y el cliente.
  • Confianza: En caso de que la conexión a través de WebSocket no sea posible, se utiliza una comunicación basada en HTTP long-polling, de forma que, si la conexión se pierde, el cliente intentará reconectar automáticamente.
  • Escalabilidad: Es posible configurar una arquitectura multi-servidor, y enviar eventos a todos los clientes con facilidad.

El siguiente diagrama muestra la arquitectura de nuestra solución:

El servidor socket.io (basado en Node.js) hace de intermediario entre el iframe y el navegador, de forma que cuando el iframe agregue un producto especial al carrito, notificará al servidor socket.io, y éste a su vez notificará al navegador para que refresque la página y el carrito muestre la información actualizada. En esta arquitectura, el iframe hace de emisor del evento y el navegador hace de receptor del evento, aunque ambos mantienen una comunicación bidireccional con el servidor socket.io, que es el nexo de unión entre ambos sistemas.
Dentro del servidor socket.io, es posible crear rooms, que son espacios con un nombre arbitrario a través de los cuales los clientes Node.js pueden conectarse para emitir/recibir eventos.

Pero ¿cómo se configuran los diferentes actores de la solución para que todo funcione? ¡Vamos a verlo!

Servidor socket.io

Para disponer del servidor socket.io, es necesario instalar el entorno de ejecución javascript, en nuestro caso, sobre SO Linux:

nodejs

yum install -y nodejs

express

npm install express --save

socketio

npm install socket.io

forever

npm install forever -g

Sobre el mismo directorio de instalación de las librerías anteriores, se crea un fichero index.js que contiene el código del servidor socket.io, el cual realizará las siguientes acciones:

  • Creación de servidor HTTPS con certificado válido sobre el que correrá socket.io
  • Captura de evento de conexión a socket.io para:
  • Recuperar datos de conexión
  • Crear una room (si no existe previamente) y agregar a dicha room el socket (cliente Node.js) que se está conectando
  • Gestionar eventos de:
  • Desconexión de un socket
  • Actualización de carrito de compra
  • Levantar el servidor HTTPS con socket.io sobre el puerto especificado

index.js

var fs = require('fs');  
var https = require('https');

var express = require('express');  
var app = express();

var options = {  
  key: fs.readFileSync('/etc/nginx/ssl/myServerDomain.com.key'),
  cert: fs.readFileSync('/etc/nginx/ssl/myServerDomain.com.crt')
}; // Clave y certificado para establecer una conexion HTTPS valida
var serverPort = 3000; // Puerto de conexion del servidor socket.io

var server = https.createServer(options, app); // Creacion de servidor HTTPS  
var io = require('socket.io')(server, {  
  pingInterval: 600000,
  pingTimeout: 600000,
  cors: {
    origin: ["https://myIframeDomain.com", "https://myMagnoliaEcommerceWebsite.com"],
    methods: ["GET", "POST"]
  }
}); // Levantar socket.io sobre el servidor HTTPS, habilitando solamente los origenes CORS sobre los que corre el iframe y el portal web Magnolia que contiene al carrito

io.on('connection', (socket) => {

   // Recuperar los datos de conexión enviados por el socket (cliente Node.js)  
   let userid = socket.handshake.query.userid;
   let from = socket.handshake.query.from;

   // Crear y conectar a una room utilizando el userid
   socket.join(userid);
   console.log('CONECTADO -> room ' + userid + ' from ' + from);

   // Evento al desconectar del socket
   socket.on('disconnect', () => {
    console.log('DESCONECTADO -> room ' + userid + ' from ' + from);    
   });

   // Evento actualizar carrito
   socket.on('UPDATE_CART', (u) => {
    console.log('UPDATE CART -> room: ' + userid + ', usuario: ' + u );
    io.to(userid).emit('UPDATE_CART');
    });

 });    

server.listen(serverPort, function() {  
  console.log('Servidor levantado y corriendo en el puerto %s', serverPort);
});

Para ejecutar el fichero index.js y levantar el servidor socket.io en segundo plano (para que no se pare el servidor al cerrar la consola), bastará con lanzar el siguiente comando:

forever start index.js
Emisor (iframe)

Para conseguir notificar el evento de actualización del carrito de compra, el emisor (iframe) debe implementar las siguientes acciones:

  • Establecer la comunicación con el servidor socket.io, es decir, crear el socket
  • Generar un evento, a través del socket creado, que indique una actualización del carrito de compra
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.2.0/socket.io.js" />

...
var socket = io.connect("https://myServerDomain.com:3000?userid=myUserId&from=myIframeUser", {secure: true});  
...
function actualizar() {  
  socket.emit("UPDATE_CART"); // Los parametros a enviar son arbitrarios, pero al menos se envia el tipo de evento
}
Receptor (navegador)

El receptor es el encargado de recibir un evento de actualización del carrito de compra y, en consecuencia, refrescar la página para que el carrito muestre la información actualizada. Por tanto, el receptor debe implementar las siguientes acciones:

<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.2.0/socket.io.js" />  
...

var socket = io.connect("https://myServerDomain.com:3000?userid=myUserId&from=myMagnoliaUser", {secure: true});  
socket.on("UPDATE_CART", function (data) {  
    setInterval( function(){
                    location.reload()
                }, 3000);
    console.log("Se ha actualizado carrito");
});

Con todos los pasos completos, cada actor de este entorno podrá contribuir a dar solución al escenario inicial: permitir la comunicación entre el gestor de productos especiales (iframe) y el visualizador del carrito (portal web Magnolia), para que cuando se agregue un producto especial al carrito, éste pueda mostrar la información actualizada.

Conclusión

En este artículo, hemos podido ver cómo la plataforma socket.io facilita la interconexión y comunicación entre un portal web e-commerce desarrollado en Magnolia y un iframe, aunque la potencia de esta solución es su validez ante diferentes sistemas que pueden ser totalmente independientes, implementados en otras tecnologías y no tener visibilidad entre ellos.

Gracias a María Sánchez Postigo que ha sido la artífice de esta solución ;)

¡Esperamos que os haya gustado! Síguenos en Twitter para estar al día de próximos posts.

Autor

David Caviedes Márquez

DXP Specialist / Technical Leader en knowmad mood. En mi tiempo libre, me encanta practicar deporte, en este caso triatlón, además, soy un apasionado de las motos, la playa y de mi familia y amigos.