Source: NextGeneration/thaliNotificationClient.js

'use strict';

var EventEmitter = require('events');
var thaliPeerDictionary = require('thaliPeerDictionary');

/** @module thaliNotificationClient */

/**
 * Creates a class that can register to receive the {@link
 * module:thaliMobile.event:peerAvailabilityChanged} event. It will listen for
 * the event and upon receiving it, will enqueue an action with the
 * submitted thaliPeerPool. Once called back by the pool then the callback will
 * issue a HTTP GET request to retrieve the notification beacons for the peer,
 * parse them, see if one matches and if so then fire a {@link
 * module:thaliNotificationClient.event:peerAdvertisesDataForUs}. Callers can
 * listen for the event by using the emitter member.
 *
 * @public
 * @constructor
 * @param {module:thaliPeerPoolInterface~ThaliPeerPoolInterface} thaliPeerPool
 * Requests to retrieve notification beacons are enqueued on this object in
 * order to make sure we don't overwhelm our bandwidth or native communication
 * capabilities.
 * @param {Crypto.ECDH} ecdhForLocalDevice A Crypto.ECDH object initialized
 * with the local device's public and private keys.
 * @param {addressBookCallback} addressBookCallback An object used to validate
 * which peers we are interested in talking to.
 * @fires module:thaliNotificationClient.event:peerAdvertisesDataForUs
 */
function ThaliNotificationClient(thaliPeerPool, ecdhForLocalDevice,
                                 addressBookCallback) {
  EventEmitter.call(this);
  this._init();
  this.peerDictionary = new thaliPeerDictionary.PeerDictionary();
}

/**
 * A dictionary used to track the state of peers we have received notifications
 * about from {@link module:thaliMobile}.
 *
 * @type {ThaliNotificationClient.PeerDictionary}
 */
ThaliNotificationClient.prototype.peerDictionary = null;

/**
 * This method will cause a listener to be registered on the global singleton
 * {@link module:thaliMobile} object for the {@link
 * module:thaliMobile.event:peerAvailabilityChanged} event.
 *
 * This method MUST be idempotent so calling it twice in a row MUST NOT cause
 * multiple listeners to be registered with thaliMobile.
 *
 * ### Handling peerAvailabilityChanged Events
 *
 * The notification code is triggered via peerAvailabilityChanged events. In
 * handling these events remember that our goal is to find the notification
 * beacons associated with each peerIdentifier. Once we have retrieved the
 * beacons for a specific peerIdentifier we don't ever need to deal with that
 * specific peerIdentifier again. If the peer behind the identifier changes
 * their beacons then we will get a new peerIdentifier.
 *
 * + If hostAddress != null
 *  + If this peer is not in the dictionary
 *    + Create a {@link module:thaliNotificationAction~NotificationAction} and
 *    then call enqueue on the submitted {@link
 *    module:thaliPeerPoolInterface~ThaliPeerPoolInterface} object and then
 *    create a new PeerDictionaryEntry object with the peerState set to
 *    enqueued, the peerConnectionDictionary set to a single entry matching the
 *    data in the peerAvailabilityChanged event and the notificationAction set
 *    to the previously created notificationAction object.
 *  + If this peer is in the table
 *    + If this peer has been marked as RESOLVED
 *      + Ignore the event
 *    + If this peer has been marked as CONTROLLED_BY_POOL and the action's
 *      state is QUEUED or if the peer's state is WAITING
 *      + First update the connection dictionary for the peer's entry with the
 *      new data. Then if the connectionType of the new event is TCP_NATIVE and
 *      if the connectionType of the existing action isn't that then kill the
 *      existing action using {@link
 *      module:thaliPeerPoolInterface~ThaliPeerPoolInterface#kill} and create a
 *      new action and enqueue it and update the entry. The point of this
 *      exercise is that we prefer native TCP transport to other options.
 *    + If this peer has been marked as CONTROLLED_BY_POOL and the action's
 *      state is STARTED
 *      + If the connectionType of the event is different than the
 *      connectionType of the action then just update the
 *      peerConnectionDictionary and move on. If the connectionTypes are
 *      identical then kill the existing action as above. If the
 *      peerConnectionDictionary contains a TCP_NATIVE entry then create and
 *      enqueue an action for that. Otherwise take the entry that was just
 *      updated and create and enqueue that as an action.
 * + If hostAddress == null
 *   + If this peer is not in the table
 *     + This is technically possible in a number of cases. If this happens
 *     then just ignore the event.
 *   + If this peer is in the table
 *     + If this peer has been marked as resolved
 *       + Ignore the event.
 *     + If this peer has been marked as CONTROLLED_BY_POOL and the action's
 *     state is QUEUED or if the peer's state is WAITING or if this peer has
 *     been marked as CONTROLLED_BY_POOL and the action's state is STARTED
 *       + Call kill on the action via {@link
 *       module:thaliPeerPoolInterface~ThaliPeerPoolInterface#kill} and remove
 *       the associated entry in peerConnectionDictionary. If this leaves no
 *       entries in peerConnectionDictionary then remove this table entry in
 *       total from the dictionary. If there is still an entry left then create
 *       a notificationAction for it and enqueue it.
 *
 * ## Handling Resolved events from notificationActions
 *
 * When creating a notificationAction a listener MUST be placed on that action
 * to listen for the
 * {@link module:thaliNotificationAction~NotificationAction.event:Resolved}
 * event.
 *
 * + BEACONS_RETRIEVED_AND_PARSED
 *  + Mark the entry in the dictionary as RESOLVED and fire
 *  {@link module:thaliNotificationClient.event:peerAdvertisesDataForUs}
 * + BEACONS_RETRIEVED_BUT_BAD
 *  + This indicates a malfunctioning peer. We need to assume they are bad all
 *  up and mark their entry as RESOLVED without taking any further action. This
 *  means we will ignore this peerIdentifier in the future.
 * + HTTP_BAD_RESPONSE
 *  + This tells us that the peer is there but not in good shape. But we will
 *  give them the benefit of the doubt. We will wait 100 ms if we are in the
 *  foreground and 500 ms if we are in the background (the later only applies
 *  to Android) and then create a new action (remember, prefer TCP_NATIVE) and
 *  then enqueue it. Make sure to set the dictionary entry's state to
 *  WAITING and when the timer is up and we enqueue to CONTROLLED_BY_POOL.
 * + NETWORK_problem
 *  + Treat the same as HTTP_BAD_RESPONSE
 * + KILLED
 *  + We MUST check the value of the notificationAction on the associated
 *  dictionary entry with the notificationAction that this handler was created
 *  on. If they are different then this means that this class was the one who
 *  called kill and so we can ignore this event. If they are the same then it
 *  means that the ThaliPeerPoolInterface called kill (due to resource
 *  exhaustion). Having the pool kill us is a pretty extreme event, it means
 *  we have so many peerIdentifiers that we blew up the pool. So at that point
 *  the best thing for us to do is to just delete the entire entry for this
 *  peerIdentifier and move on.
 *
 * @public
 */
ThaliNotificationClient.prototype.start = function () {

};

/**
 * Will remove the listener registered on the global thaliMobile object, if
 * any. This method MUST be idempotent so calling it multiple times MUST only
 * cause a single call to removeListener on thaliMobile and only then if there
 * already was a call to addListener on this object.
 *
 * Removing the listener MUST not just stop listening for the event but MUST
 * also cause all non-resolved entries in the dictionary to either stop
 * waiting or if under control of the pool to be killed and then their entries
 * MUST be removed from the peer dictionary.
 *
 * @public
 */
ThaliNotificationClient.prototype.stop = function () {

};

/**
 * This is the action type that will be used by instances of this class when
 * registering with {@link
 * module:thaliPeerPoolInterface~ThaliPeerPoolInterface}.
 * @type {string}
 * @readonly
 */
ThaliNotificationClient.ACTION_TYPE = 'GetRequestBeacon';

/**
 * Fired whenever we discover a peer who is looking for us.
 *
 * @public
 * @event module:thaliNotificationClient.event:peerAdvertisesDataForUs
 * @type {Object}
 * @property {buffer} keyId The buffer contains the HKey as defined
 * [here](https://github.com/thaliproject/thali/blob/gh-pages/pages/documentation/PresenceProtocolForOpportunisticSynching.md#processing-the-pre-amble-and-beacons).
 * @property {string} pskIdentifyField This is the value to put in the PSK
 * identity field of the ClientKeyExchange message when establishing a TLS
 * connection using PSK. This value is generated
 * @property {buffer} psk This is the calculated pre-shared key that will be
 * needed to establish a TLS PSK connection.
 * @property {string} hostAddress The IP/DNS address of the peer
 * @property {number} portNumber The TCP/IP port at the hostAddress the peer
 * can be contacted on
 * @property {number} suggestedTCPTimeout Provides a hint to what time out to
 * put on the TCP connection. For some transports a handshake can take quite a
 * long time.
 */

module.exports = ThaliNotificationClient;