Manejo de infomación con WebSockets

Sails usa socket.io como el motor subyacente para la comunicación, por lo que la especificación de la comunicación se ejemplifica con fragmentos de código JavaScript.

El sistema debe notificar a los asistentes y a los temporizadores de las estaciones cuando se lleven a cabo acciones de los usuarios que requieran atención. Esta comunicación será llevada a cabo utilizando la tecnología WebSocket.

Descripción general

Establecimiento de conexión

_images/client_socket_connection_sequence.svg
  1. El cliente intenta establecer una conexión con el servidor Onsite en la ruta {local_ip}:1337/notifications, donde local_ip corresponde a la IP local del servidor Onsite; y notifications es el nombre del espacio de nombres que se usará para el manejo de conexiones WebSocket.
  2. El servidor recibe la conexión y crea un socket para comunicarse con el cliente.
  3. Inmediatamente después de establecer la conexión, el cliente debe autenticarse ante Onsite como el tipo de cliente que anunció.
  4. El servidor responde con el resultado de la autenticación. Si el proceso es exitoso, el servidor incluirá al cliente en el room correspondiente. Si la autenticación resulta en error, el servidor cierra el socket.

Flujo

La conexión entre el cliente y el servidor se establece sin autenticación. Por lo tanto, inmediatamente después de establecer conexión, el cliente debe identificarse ante Onsite con una petición firmada con un JWT de la siguiente manera:

Servidor
connect: function (req, res) {
  var socket = req.socket;
  var jwtAuth = req.body.params['jwt-api-key'];

  // JWT validation goes here
}
Cliente

Conexión hacia Onsite

var notifications = io.connect('https://[local].onsite.gamersarena.com.mx/');
notifications.emit('get', {
    url: "/notifications",
    params: {
        "jwt-api-key": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"
    }
}, function(data) {
    // data is server response
});

Conexión hacia Tournament Handler

var notifications = io.connect('http://tournament.gamersarena.com.mx/');
notifications.emit('get', {
    url: "/notifications",
    params: {
      "jwt-api-key": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ",
      "tournamentId": 2  // identificador del torneo para recibir notificaciones
    }
}, function(data) {
    // data is server response
});

Si el proceso de autenticación es exitoso, el servidor debe agregar al cliente al room correspondiente dependiendo de la entidad a la que pertenezca su JWT. Adicionalmente, se establece en la BD el identificador del socket asociado al cliente, el cual se obtiene invocando a la variable id del socket.

Si la autenticación falla, el servidor cierra el socket asociado al cliente cuyas credenciales no pudieron ser verificadas. Esto se logra invocando a la función disconnect() del socket.

Enlace a room

Un room es un conjunto de sockets identificados por un nombre, el cual es usado para enviar mensajes a un subconjunto de los clientes conectados.

Por defecto, cada socket se une automáticamente a un room cuyo nombre es el id del socket. Con el objetivo de propagar mensajes a cierto tipo de clientes en el local, es conveniente incorporarlos a un room dependiendo de su rol.

Los rooms gestionados por el sistema Onsite son:

  • assistant
  • timer

Por otro lado, en Tournament Handler se crean rooms con la estructura:

  • assistant_[id], donde id corresponde al identificador del
    torneo en Tournament Handler al cual está suscrito el asistente.

Agregar a un cliente a un room se logra de la siguiente manera

socket.join('room name');

Y después enviar un mensaje a los sockets en el room como sigue

broadcast('room name', "eventName", message_as_object);

donde

  • room names es el nombre o nombres (arreglo de cadenas) de los rooms a los cuales enviar el mensaje
  • eventName es el identificador de la operación
  • message_as_object es el objeto que se envía como mensaje

Desconexión

En caso de ocurrir un desconexión de parte de el cliente, su id de socket debe ser limpiado de la base de datos, lo cual se logra estableciendo como nulo el campo socket_id de la entidad correspondiente.

Estuctura de los mensajes

Servidor

Si la comunicación es iniciada por el servidor, los mensajes enviados tienen una estructura de objeto como la siguiente:

{
    check_in_id: {
      id: 4,
      nick: "aguila_calva"
    }
}

donde el cuerpo es variable, por lo que la estructura del mensaje esperado por el cliente será descrita en la especificación de cada evento.

Si el mensaje corresponde a una respuesta de una petición efectuada por el cliente, la estructura será la siguiente:

{
  body: {
    error: {
      code: '409-13',
      message: 'There was a problem with the checkin.'
    }
  },
  statusCode: 409
}

Si la respuesta fue exitosa, el campo error no es incluido.

Cliente

Los mensajes del cliente siempre presentan dos campos: header y body. En el header se incluyen encabezados de la operación, como el token de autenticación. Por otro lado, el body alberga los valores relativos a la transacción.

{
  "header": {
      "jwt-api-key": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"
  },
  "body": {
    "check_in_id": {
        "id": 4,
        "nick": "aguila_calva"
    }
  }
}