Source: lib/client-register.js

'use strict';

const ClientMessager = require('./client-messager');
const Client         = require('./client');
const Redis          = require('./redis');
const objectValues   = require('lodash').values;
const KEY_SPLITTER   = Client.KEY_SPLITTER;

/**
 * A register of all contected Clients
 *
 * @module ClientRegister
 */

module.exports = {
  clientsInSpace  : clientsInSpace,
  deregisterClient: deregisterClient,
  removeAllClients: removeAllClients,
  getClient       : getClient,
  renewClient     : renewClient,
  registerClient  : registerClient,
};

/**
 * @private
 * @member {object} clients An object containing all clients currently connected
 */
const clients = {};

/**
 * Return the list of Clients in a space *in the current process*.
 *
 * @static
 * @example <caption>Listing the clients in a space</caption>
 * ClientRegister.clientsInSpace(spaceID);
 * // [{ id: 'clientID' }] etc...
 * @param {string} spaceID The space ID to get clients for
 * @return {Client[]} The clients in the given space
 */
function clientsInSpace(spaceID) {
  return objectValues(clients).filter(client => client.spaceID === spaceID);
}

/**
 * Deregister a client, and remove their persisted presence.
 *
 * @static
 * @example <caption>Deregistering a client</caption>
 * ClientRegister.deregisterClient(client).then(client => console.log(client));
 * @param {Client} client The client to deregister
 * @return {Promise.<Client>} A promise resolving with the client after
 *   deregistration
 */
function deregisterClient(client) {
  const presenceKey = Client.getPresenceKey(client);
  delete clients[client.id];
  return Redis.delAsync(presenceKey).return(client);
}

/**
 * Get a client by ID *from the current process's client pool*.
 *
 * @static
 * @example <caption>Getting a client</caption>
 * Client.getClient('clientID');
 * @param {string} clientID The ID of the client to fetch
 * @return {Client}
 */
function getClient(clientID) {
  return clients[clientID];
}

/**
 * Register a new client with a given client ID, and send them a list of the
 * currently joined members in their space.
 *
 * @static
 * @example <caption>Registering a client</caption>
 * ClientRegister.registerClient(webSocket).then(client => console.log(client));
 * @param {WS.WebSocket} socket The socket that the new client is connected on
 * @return {Promise.<Client>} A promise resolved with the newly-registered
 *   Client
 */
function registerClient(socket) {
  let client;

  return Client.create(socket).then(_client => {
    client = _client;
    return persistClientPresence(client);
  }).then(() => {
    return setClientExpiration(client);
  }).then(() => {
    const nsKeyPattern = getNSKeyPattern(client.spaceID);
    return Redis.keysAsync(nsKeyPattern);
  }).then(presenceKeys => {
    clients[client.id] = client;
    const members = presenceKeys.map(Client.parsePresenceKey)
      .map(Client.serialize);

    return ClientMessager.send(client, { id: client.id, clients: members });
  }).then(() => client);
}

/**
 * Renew a client's presence.
 *
 * @static
 * @example <caption>Renewing a client's presence lease</caption>
 * ClientRegister.renewClient(client).then(client => console.log(client));
 * @param {Client} client The client to renew presence for
 * @return {Promise.<Client>} A promise resolving with the client after presence
 *   renewed
 */
function renewClient(client) {
  return setClientExpiration(client).return(client);
}

/**
 * Forcibly remove all clients from the registry (but do not remove them from
 * Redis).
 *
 * @static
 */
function removeAllClients() {
  for (let key in clients) {
    delete clients[key];
  }
}

/**
 * Get the presence key pattern for a space.
 *
 * @private
 * @param {string} spaceID The ID of the space to get presence key pattern for
 * @return {string} A pattern to fetch presence keys for a space
 */
function getNSKeyPattern(spaceID) {
  return `longhouse${KEY_SPLITTER}spaces${KEY_SPLITTER}${spaceID}${KEY_SPLITTER}*`;
}

/**
 * Persist a client's presence to Redis.
 *
 * @private
 * @param {Client} client The client to persist presence for
 * @return {Promise.<Client>} A promise resolving with the client after
 *   persistence
 */
function persistClientPresence(client) {
  const presenceKey = Client.getPresenceKey(client);
  return Redis.setAsync(presenceKey, client.identity).return(client);
}

/**
 * Set a client's expiration time in Redis.
 *
 * @private
 * @param {Client} client The client to set expiration for
 * @return {Promise.<Client>} A promise resolving when the expiration is set
 */
function setClientExpiration(client) {
  const presenceKey = Client.getPresenceKey(client);
  return Redis.pexpireAsync(presenceKey,
    parseInt(process.env.PRESENCE_TTL, 10) || 60000).return(client);
}