Source: lib/client.js

'use strict';

const Bluebird = require('bluebird');
const UUID     = require('node-uuid');
const parseURL = require('url-parse');
const pick     = require('lodash').pick;

/**
 * Functions for working with Client objects
 *
 * @module Client
 */

 /**
  * @member {string} KEY_SPLITTER The value used to separate client information
  *   in a PresenceKey
  */
const KEY_SPLITTER = '/';

module.exports = {
  create: create,
  parsePresenceKey: parsePresenceKey,
  getPresenceKey: getPresenceKey,
  serialize: serialize,
  KEY_SPLITTER: KEY_SPLITTER,
};

/**
 * A representation of someone connected to a space over a WebSocket connection
 *
 * @typedef {object} Client
 * @property {string} id The UUID identifying this client
 * @property {string?} requestID The ID of the request this client connected with
 * @property {string} identity The identity value of this client (e.g. an email)
 * @property {string} spaceID The ID of the space this client is connected to
 * @property {WS.WebSocket} socket The socket the client is connected over
 * @property {string} joinedAt The time at which the client joined
 */

/**
 * A key containing information about a connected client
 *
 * The client information is all in the key, split by forward slashes:
 *
 *     longhouse/spaces/${spaceID}/${id}/${identity}/${joinedAt}
 *
 * @typedef {string} PresenceKey
 */

/**
 * Create a new Client.
 *
 * @static
 * @example <caption>Creating a new Client</caption>
 * Client.create(webSocket).then(client => console.log(client));
 * @param {WS.WebSocket} socket The socket that the new client is connected on
 * @return {Promise.<Client>} A promise resolving with a new Client
 */
function create(socket) {
  const parsedURL = parseURL(socket.upgradeReq.url, true);
  const spaceID   = parsedURL.pathname.slice(1);
  const identity  = parsedURL.query.identity;
  const requestID = socket.upgradeReq.headers['x-request-id'];
  const clientID  = parsedURL.query.id || UUID.v4();

  return new Bluebird((resolve, reject) => {
    if (!spaceID) {
      reject(new Error('Space ID is required'));
    }

    if (!identity) {
      reject(new Error('No identity parameter was supplied'));
    }

    resolve(Object.freeze({
      id: clientID,
      requestID: requestID,
      identity: identity,
      spaceID: spaceID,
      socket: socket,
      joinedAt: new Date().toISOString(),
    }));
  });
}

/**
 * Parse a presence key for Client info
 *
 * @static
 * @example <caption>Parsing a presence key</caption>
 * Client.parsePresenceKey('longhouse/spaces/spaceID/clientId/clientIdentity/2015-06-05T20:51:47.411Z');
 * // { id: 'clientID', identity: 'clientIdentity', spaceID: 'spaceID', joinedAt: '2015-06-05T20:51:47.411Z' }
 * @param {string} presenceKey A presence key
 * @return {Client} A client
 */
function parsePresenceKey(presenceKey) {
  const parts = presenceKey.split(KEY_SPLITTER);

  return {
    id: parts[3],
    identity: parts[4],
    spaceID: parts[2],
    joinedAt: parts[5],
  };
}


/**
 * Get the Redis presence key for a client.
 *
 * @static
 * @example <caption>Getting a Redis presence key for a client</caption>
 * Client.getPresenceKey(client);
 * // 'longhouse/spaces/spaceID/clientId/clientIdentity/2015-06-05T20:51:47.411Z'
 * @param {Client} client The client to fetch the presence key for
 * @return {string} A presence key for a client
 */
function getPresenceKey(client) {
  return [
    'longhouse',
    'spaces',
    client.spaceID,
    client.id,
    client.identity,
    client.joinedAt
  ].join(KEY_SPLITTER);
}

/**
 * Serialize a client for sending to another client.
 *
 * @static
 * @example <caption>Serializing a client</caption>
 * Client.serialize(client);
 * // { id: 'clientID', identity: 'clientIdentity', joinedAt: '2015-06-05T20:51:47.411Z' }
 * @param {Client} client The client to serialize
 */
function serialize(client) {
  return pick(client, 'id', 'identity', 'joinedAt');
}