El objetivo de este artículo es el de conocer la arquitectura básica para el manejo remoto móvil de una web desde un dispositivo móvil. El objetivo es poder comunicarse y ejecutar acciones en una web desde una aplicación web móvil. Para definir esta arquitectura nos vamos a basar en la magia de nodejs, del cual ya hemos hablado en artículos anteriores.
El concepto de ejemplo básico que se quiere demostrar es el de una gran pantalla en la que multiples dispositivos pueden conectarse e interactuar con ella. Una vez que la aplicación desktop ha sido inicializada los dispositivos móviles pueden conectarse a una URL específica en la que pueden manejar, controlar e interactuar con esta aplicación desktop. Esta comunicación con URL se hará con un identificador único que puede ser leído como un código QR, un código que el usuario debe introducir o simplemente un parámetro de la URL.
Implementación
Para implementar esta funcionalidad nos vamos apoyar en un módulo de node llamado Socket.IO el cual añade multiple niveles de soporte de web sockets compatibles con gran parte de los navegadores. Esto permite que se permita una comunicación entre websockets en la que la comunicación no siempre tiene que iniciarla el cliente, si no que el servidor puede empezar esta comunicación.
Lo primero de todo veamos la parte del cliente desktop. Este al inicializarse debe de asignarsele un identificador con el cual los dispositivos móviles se emparejaran. Se podría generar este identificador en el cliente, pero también puede ser el servidor el que se encargue de gestionar estos identificadores. Por lo que la parte cliente lo que comienza es mandando un mensaje solicitando al servidor una nueva conexión de esta manera recibiendo un identificador que el servidor guardará mientras el socket permanezca abierto.
var uniqSocketId=false; function initializeSocket(){ // solicitar nueva conexión socket.emit('desktop.new.connection',{}); // escuchar la recepción de un id socket.on('playerId.assigned', function(data){ uniqSocketId=data; }); } // escuchar un dispositivo movil conectado al socket desktop socket.on('desktop.mobile.connected', function(data){ alert("CONEXION OK"); });
Por otro lado el cliente móvil debe requeir al servidor una conexión a un socket específico. Se debe de llamar al servidor con el identificador generado al cliente desktop, el cual se puede recibir a través de un input, un código qr, un parámetro de la url…
// solicitar nueva conexión mobile socket.emit('mobile.new.connection', { playerId: codigo}, function(data){ if(data.registered == true){ registered = true; alert("OK"); }else{ alert("Codigo no encontrado"); } });
Por otro lado veamos la parte servidor. Este proceso que es el se correrá en nodejs almacena un array de conexiones manejano estas y asociando a cada sesión de desktop, las sesiones móviles pertinentes.
Cuando un cliente desktop solicita una nueva conexión se almacena un nuevo dato en el array y se devuelve a este el id que se le ha asignado. De igual manera cuando un cliente movil solicita una nueva conexión se buscará el socket concreto y se le asociará a este emparajándose permitiendo una vez emparejados que la comunicación sea entre los sockets adecuados.
var http = require('http').Server(); var io = require('socket.io')(http); var uniqid = require('uniqid'); var connections=[]; // constructor nueva conexión function connection(connectionSocket, playerId){ this.connectionSocket = connectionSocket; // desktop conexion this.playerId = playerId; // identificador único this.mobileSockets = []; // conexiones moviles }; io.on('connection',function(socket){ // añadir nueva conexión desktop socket.on("desktop.new.connection", function(data){ playerId=uniqid(); connections.push(new connection(socket, playerId)); socket.emit('playerId.assigned',playerId); }); // añadir nueva conexión mobile socket.on("mobile.new.connection", function(data, fn){ var desktopConnection = null; for(var i = 0; i < connections.length; i++){ if(connections[i].playerId == data.playerId){ desktopConnection = i; } } if(desktopConnection !== null){ connections[desktopConnection].mobileSockets.push(socket); socket.playerId=data.playerId; fn({registered: true}); connections[desktopConnection].connectionSocket.emit('desktop.mobile.connected', socket.id, data); }else{ //Callback returns false with an error fn({registered: false, error: "No se ha encontrado este identificador"}); } }); // gestión de destrucción de sockets socket.on("disconnect", function(){ var destroyThis = null; if(typeof socket.playerId == 'undefined'){ for(var i in connections){ if(connections[i].connectionSocket.id == socket.id){ destroyThis = i; } } if(destroyThis !== null){ connections.splice(destroyThis, 1);} }else{ var playerId = socket.playerId; if(connections[playerId] != undefined){ for(var i in connections[playerId].mobileSockets){ if(connections[playerId].mobileSockets[i] == socket){ destroyThis = i; } } if(destroyThis !== null){connections[playerId].mobileSockets.splice(destroyThis, 1);} } } }); }); http.listen(3000, function(){ console.log('listening on localhost:3000'); });
Como conclusión esta arquitectura básica puede llevar a cabo distintas implementaciones y aplicaciones en el mundo real. Por ejemplo, aprovechando las ventajas del acelerómetro del dispositivo o simplemente como si de un mando se tratara podemos lanzar acciones en nuestra aplicación principal. Esto por ejemplo puede tener aplicaciones en el mundo de los videojuegos donde desde la aplicación móvil podamos interactuar con un videojuego que corre en la web o con cualquier aplicación de marketing que busca que el usuario interactue involucrándose en la acción.