Source: member.js

'use strict';

const EventEmitter = require('events').EventEmitter;
const inherits = require('util').inherits;

/**
 * @class
 * @classdesc A Member represents a remote Client in a Channel.
 * @property {Channel} channel - The Channel the remote Client is a Member of.
 * @property {String} identity - The identity of the remote Client.
 * @property {Boolean} isTyping - Whether or not this Member is currently typing.
 * @property {Number} lastConsumedMessageIndex - Latest consumed Message index by this Member.
 * @property {Date} lastConsumptionTimestamp - Date when Member has updated his consumption horizon.
 * @property {String} sid - The server-assigned unique identifier for the Member.
 * @fires Member#typingEnded
 * @fires Member#typingStarted
 * @fires Member#updated
 */
function Member(channel, data, sid) {
  if (!(this instanceof Member)) {
    return new Member(channel, data, sid);
  }

  let isTyping = false;
  let typingTimeout = null;

  let identity = data.identity;
  let roleSid = data.roleSid || null;
  let lastConsumedMessageIndex = data.lastConsumedMessageIndex || null;
  let lastConsumptionTimestamp = data.lastConsumptionTimestamp ?
      new Date(data.lastConsumptionTimestamp) : null;

  if (!data.identity) {
    throw new Error('Received invalid Member object from server: Missing identity.');
  }

  Object.defineProperties(this, {
    _identity: {
      get: () => identity,
      set: (_identity) => identity = _identity
    },
    _isTyping: {
      get: () => isTyping,
      set: (_isTyping) => isTyping = _isTyping
    },
    _lastConsumedMessageIndex: {
      get: () => lastConsumedMessageIndex,
      set: _lastConsumedMessageIndex => lastConsumedMessageIndex = _lastConsumedMessageIndex
    },
    _lastConsumptionTimestamp: {
      get: () => lastConsumptionTimestamp,
      set: _lastConsumptionTimestamp => lastConsumptionTimestamp = _lastConsumptionTimestamp
    },
    _roleSid: {
      get: () => roleSid,
      set: _roleSid => roleSid = _roleSid
    },
    _typingTimeout: {
      writable: true,
      value: typingTimeout
    },
    channel: {
      enumerable: true,
      value: channel
    },
    identity: {
      enumerable: true,
      get: () => identity
    },
    isTyping: {
      enumerable: true,
      get: () => isTyping
    },
    lastConsumedMessageIndex: {
      enumerable: true,
      get: () => lastConsumedMessageIndex
    },
    lastConsumptionTimestamp: {
      enumerable: true,
      get: () => lastConsumptionTimestamp
    },
    roleSid: {
      enumerable: true,
      get: () => roleSid
    },
    sid: {
      enumerable: true,
      value: sid
    }
  });

  EventEmitter.call(this);
}

inherits(Member, EventEmitter);

Object.freeze(Member);

/**
 * Private method used to start or reset the typing indicator timeout (with event emitting)
 * @private
 */
Member.prototype._startTyping = function(timeout) {
  if (this._typingTimeout) {
    clearTimeout(this._typingTimeout);
  }

  this.emit('typingStarted', this);
  this.channel.emit('typingStarted', this);

  this._typingTimeout = setTimeout(() => this._endTyping(), timeout);
  return this;
};

/**
 * Private method function used to stop typing indicator (with event emitting)
 * @private
 */
Member.prototype._endTyping = function() {
  if (!this._typingTimeout) { return; }

  this.emit('typingEnded', this);
  this.channel.emit('typingEnded', this);

  clearInterval(this._typingTimeout);
  this._typingTimeout = null;
};

/**
 * Remove this Member from the Channel.
 * @returns Promise
 */
Member.prototype.remove = function removeMember() {
  return this.channel.removeMember(this);
};

/**
 * Private method function used update local object's property roleSid with new value
 * @private
 */
Member.prototype._update = function(data) {
  let updated = false;

  if (data.roleSid && this._roleSid !== data.roleSid) {
    this._roleSid = data.roleSid;
    updated = true;
  }

  if (data.lastConsumedMessageIndex
      && this._lastConsumedMessageIndex !== data.lastConsumedMessageIndex) {
    this._lastConsumedMessageIndex = data.lastConsumedMessageIndex;
    updated = true;
  }

  if (data.lastConsumptionTimestamp) {
    let lastConsumptionTimestamp = new Date(data.lastConsumptionTimestamp);
    if (!this._lastConsumptionTimestamp ||
        this._lastConsumptionTimestamp.getTime() !== lastConsumptionTimestamp.getTime()) {
      this._lastConsumptionTimestamp = lastConsumptionTimestamp;
      updated = true;
    }
  }

  if (updated) {
    this.emit('updated', this);
  }
};

module.exports = Member;

/**
* Fired when member started to type
* @event Member#typingStarted
* @type {Member}
*/

/**
* Fired when member ended to type
* @event Member#typingEnded
* @type {Member}
*/

/**
 * Fired when member is updated
 * @event Member#updated
 * @type {Member}
 */