<commit_before>'use strict';

define(['angular', 'jquery'], function(angular, $) {
    return angular.module('my-app.search.services', [])

    .factory('googleCustomSearchService',
      ['$log', '$http', '$q', 'PortalGroupService',
        'miscSearchService', 'miscService',
      function($log, $http, $q, PortalGroupService,
        miscSearchService, miscService) {
      var googleSearchURLPromise;
      var googleSearchEnabledPromise;
      var webSearchURLPromise;
      var domainResultsLabelPromise;

      /**
       * Get the Domain Results Label
       * @return {Promise} domainResultsLabelPromise
       */
      function getDomainResultsLabel() {
        var successFn;
        var errorFn;
        if (domainResultsLabelPromise) {
          return domainResultsLabelPromise;
        }
        successFn = function(groups) {
          return miscSearchService.getSearchURLS(groups).then(function(result) {
            if (result && result.domainResultsLabel) {
              return result.domainResultsLabel;
            } else {
              return null;
            }
          });
        };
        errorFn = function(reason) {
          miscService.redirectUser(
            reason.status,
            'Could not get appropriate domain results label'
          );
        };
        domainResultsLabelPromise = PortalGroupService.getGroups()
          .then(successFn, errorFn);
        return domainResultsLabelPromise;
      }

      /**
       * Returns a public web search url. Useful for 'See more results here'
       * @return {Promise} webSearchURLPromise
       */
      function getPublicWebSearchURL() {
        var successFn;
        var errorFn;
        if (webSearchURLPromise) {
          return webSearchURLPromise;
        }
        successFn = function(groups) {
          return miscSearchService.getSearchURLS(groups)
            .then(function(result) {
              if (result && result.webSearchURL) {
                return result.webSearchURL;
              } else {
                return null;
              }
            });
        };
        errorFn = function(reason) {
          miscService.redirectUser(
            reason.status,
            'Could not get appropriate public web search url'
          );
        };
        webSearchURLPromise = PortalGroupService.getGroups()
          .then(successFn, errorFn);
        return webSearchURLPromise;
      }

      /**
       * Search Google for term
       * @param {string} term
       * @return {Promise} Promise of google search results
       */
      function googleSearch(term) {
        return getGoogleSearchURL().then(function(googleSearchURL) {
          return $q(function(resolve, reject) {
            if (googleSearchURL) {
              return $http.get(googleSearchURL + '&q=' + term).then(
                function(response) {
                  var data = {
                    results: null,
                    estimatedResultCount: null,
                  };
                  // Standardize data
                  if (response.data) {
                    // Find the results
                    if (response.data.results) { // uwrf
                        data.results = response.data.results;
                    } else if ( // uwmad
                        response.data.responseData &&
                        response.data.responseData.results) {
                      data.results = response.data.responseData.results;
                    }
                    // Find the estimated count
                    var respData = response.data;
                    if ( // uwrf
                        respData.cursor &&
                        respData.cursor.estimatedResultCount) {
                      data.estimatedResultCount =
                          respData.cursor.estimatedResultCount;
                    } else if ( // uwmad
                        respData.responseData &&
                        respData.responseData.cursor &&
                        respData.responseData.cursor.estimatedResultCount) {
                      data.estimatedResultCount =
                        respData.responseData.cursor.estimatedResultCount;
                    }
                  }
                  resolve(data);
                  return response;
                }
              );
            } else {
              reject('User has no group for google URL search');
            }
          });
        });
      }

      /**
       * Returns a promise that will return a googleSearchURL if any exist
       * for a users group or null if one does not exists.
       */
      function getGoogleSearchURL() {
        var successFn;
        var errorFn;

        if (googleSearchURLPromise) {
          return googleSearchURLPromise;
        }

        successFn = function(groups) {
          return miscSearchService.getSearchURLS(groups).then(function(result) {
            if (result && result.googleSearchURL) {
              return result.googleSearchURL;
            } else {
              return null;
            }
          });
        };

        errorFn = function(reason) {
          miscService.redirectUser(
            reason.status,
            'Could not get appropriate google search url'
          );
        };

        googleSearchURLPromise =
          PortalGroupService.getGroups().then(successFn, errorFn);

        return googleSearchURLPromise;
      }

      /**
       * Returns promise that will return true or false if there exists
       * a directorySearchURL for any of the users groups
       */
      function googleSearchEnabled() {
        var successFn;
        var errorFn;

        if (googleSearchEnabledPromise) {
          return googleSearchEnabledPromise;
        }

        successFn = function(googleSearchURL) {
          if (googleSearchURL) {
            return true;
          } else {
            return false;
          }
        };

        errorFn = function(response) {
          $log.log('error determing if google search is enabled: ' +
            response.status);
          return false;
        };

        googleSearchEnabledPromise =
          getGoogleSearchURL().then(successFn, errorFn);
        return googleSearchEnabledPromise;
      }

      return {
        googleSearch: googleSearch,
        googleSearchEnabled: googleSearchEnabled,
        getPublicWebSearchURL: getPublicWebSearchURL,
        getDomainResultsLabel: getDomainResultsLabel,
      };
    }])

    .factory('miscSearchService',
      ['$q', '$sessionStorage', 'PortalGroupService',
        'filterFilter', 'SEARCH_CONFIG',
      function($q, $sessionStorage, PortalGroupService,
        filterFilter, SEARCH_CONFIG) {
      /**
       * retrieve the KB Search URL from the Group Service
       * @return {string} kbSearchURL
       */
      function getKBSearchURL() {
        return PortalGroupService.getGroups().then(
          function(groups) {
            return getSearchURLS(groups).then(
              function(result) {
                if (result && result.kbSearchURL) {
                  return result.kbSearchURL;
                } else {
                  return null;
                }
              }
            );
          }
        );
      }

      /**
       * retrieve the Events Search URL from the Group Service
       * @return {string} eventsSearchURL
       */
      function getEventSearchURL() {
        return PortalGroupService.getGroups().then(
          function(groups) {
            return getSearchURLS(groups).then(
              function(result) {
                if (result && result.eventsSearchURL) {
                  return result.eventsSearchURL;
                } else {
                  return null;
                }
              }
            );
          }
        );
      }

      /**
       * retrieve the Help Desk URL from the Group Service
       * @return {string} helpdeskURL
       */
      function getHelpDeskHelpURL() {
        return PortalGroupService.getGroups().then(
          function(groups) {
            return getSearchURLS(groups).then(
              function(result) {
                if (result && result.helpdeskURL) {
                  return result.helpdeskURL;
                } else {
                  return null;
                }
              }
            );
          }
        );
      }

      /**
       * retrieve the search URLs
       */
      function getSearchURLS(groups) {
        return $q(function(resolve, reject) {
          if ($sessionStorage.search) {
            resolve($sessionStorage.search);
          }
          for (var i = 0; i < SEARCH_CONFIG.length; i++) {
            var searchURLS = SEARCH_CONFIG[i];
            var searchGroup = searchURLS.group;
            var filterTest = filterFilter(groups, {name: searchGroup});
            if (filterTest && filterTest.length >0) {
              $sessionStorage.search = searchURLS;
              resolve(searchURLS);
            }
          }
          resolve(null);
        });
      }

      return {
          getSearchURLS: getSearchURLS,
          getKBSearchURL: getKBSearchURL,
          getEventSearchURL: getEventSearchURL,
          getHelpDeskHelpURL: getHelpDeskHelpURL,
      };
    }])

    .factory('directorySearchService',
        ['$log', '$http', '$q', 'PortalGroupService',
          'miscSearchService', 'miscService',
        function($log, $http, $q, PortalGroupService,
          miscSearchService, miscService) {
      var directoryUrlPromise;
      var directorySearchEnabledPromise;

      /**
       * Search the directory for term
       * @param  {string} term
       */
      function directorySearch(term) {
        return getDirectorySearchURL().then(function(searchDirectoryURL) {
          return $q(function(resolve, reject) {
            if (searchDirectoryURL) {
              return $http.get(searchDirectoryURL + '/?name=' + term).then(
                function(response) {
                  return resolve(response.data);
                },
                function(response) {
                  return reject('error searching the directory: ' +
                    response.status);
                }
              );
            } else {
              return reject('User has no group for directory search');
            }
          });
        });
      }

      /**
       * Returns promise that will return true or false if there exists
       * a directorySearchURL for any of the users groups
       */
      function directorySearchEnabled() {
          var successFn;
          var errorFn;

          if (directorySearchEnabledPromise) {
            return directorySearchEnabledPromise;
          }

          successFn = function(directoryURL) {
            if (directoryURL) {
              return true;
            } else {
              return false;
            }
          };

          errorFn = function(response) {
            $log.log('error determing if directory search is enabled: ' +
              response.status);
            return false;
          };

          directorySearchEnabledPromise = getDirectorySearchURL()
            .then(successFn, errorFn);
          return directorySearchEnabledPromise;
      }

      /**
       * Returns a promise that will return a directorySearchURL if any exist
       * for a users group or null if one does not exists.
       * @return {Promise} directoryUrlPromise
       */
      function getDirectorySearchURL() {
        var successFn;
        var errorFn;

        if (directoryUrlPromise) {
          return directoryUrlPromise;
        }

        successFn = function(groups) {
          return miscSearchService.getSearchURLS(groups).then(function(result) {
              if (result && result.directorySearchURL) {
                  return result.directorySearchURL;
              } else {
                  return null;
              }
          });
        };

        errorFn = function(reason) {
          miscService.redirectUser(reason.status, 'search directory url call');
        };

        directoryUrlPromise = PortalGroupService.getGroups()
          .then(successFn, errorFn);

        return directoryUrlPromise;
      }

      return {
        directorySearch: directorySearch,
        directorySearchEnabled: directorySearchEnabled,
      };
    }]);
});
<commit_msg>updating search to use the renamed portalGroupService<commit_after>'use strict';

define(['angular', 'jquery'], function(angular, $) {
    return angular.module('my-app.search.services', [])

    .factory('googleCustomSearchService',
      ['$log', '$http', '$q', 'portalGroupService',
        'miscSearchService', 'miscService',
      function($log, $http, $q, portalGroupService,
        miscSearchService, miscService) {
      var googleSearchURLPromise;
      var googleSearchEnabledPromise;
      var webSearchURLPromise;
      var domainResultsLabelPromise;

      /**
       * Get the Domain Results Label
       * @return {Promise} domainResultsLabelPromise
       */
      function getDomainResultsLabel() {
        var successFn;
        var errorFn;
        if (domainResultsLabelPromise) {
          return domainResultsLabelPromise;
        }
        successFn = function(groups) {
          return miscSearchService.getSearchURLS(groups).then(function(result) {
            if (result && result.domainResultsLabel) {
              return result.domainResultsLabel;
            } else {
              return null;
            }
          });
        };
        errorFn = function(reason) {
          miscService.redirectUser(
            reason.status,
            'Could not get appropriate domain results label'
          );
        };
        domainResultsLabelPromise = portalGroupService.getGroups()
          .then(successFn, errorFn);
        return domainResultsLabelPromise;
      }

      /**
       * Returns a public web search url. Useful for 'See more results here'
       * @return {Promise} webSearchURLPromise
       */
      function getPublicWebSearchURL() {
        var successFn;
        var errorFn;
        if (webSearchURLPromise) {
          return webSearchURLPromise;
        }
        successFn = function(groups) {
          return miscSearchService.getSearchURLS(groups)
            .then(function(result) {
              if (result && result.webSearchURL) {
                return result.webSearchURL;
              } else {
                return null;
              }
            });
        };
        errorFn = function(reason) {
          miscService.redirectUser(
            reason.status,
            'Could not get appropriate public web search url'
          );
        };
        webSearchURLPromise = portalGroupService.getGroups()
          .then(successFn, errorFn);
        return webSearchURLPromise;
      }

      /**
       * Search Google for term
       * @param {string} term
       * @return {Promise} Promise of google search results
       */
      function googleSearch(term) {
        return getGoogleSearchURL().then(function(googleSearchURL) {
          return $q(function(resolve, reject) {
            if (googleSearchURL) {
              return $http.get(googleSearchURL + '&q=' + term).then(
                function(response) {
                  var data = {
                    results: null,
                    estimatedResultCount: null,
                  };
                  // Standardize data
                  if (response.data) {
                    // Find the results
                    if (response.data.results) { // uwrf
                        data.results = response.data.results;
                    } else if ( // uwmad
                        response.data.responseData &&
                        response.data.responseData.results) {
                      data.results = response.data.responseData.results;
                    }
                    // Find the estimated count
                    var respData = response.data;
                    if ( // uwrf
                        respData.cursor &&
                        respData.cursor.estimatedResultCount) {
                      data.estimatedResultCount =
                          respData.cursor.estimatedResultCount;
                    } else if ( // uwmad
                        respData.responseData &&
                        respData.responseData.cursor &&
                        respData.responseData.cursor.estimatedResultCount) {
                      data.estimatedResultCount =
                        respData.responseData.cursor.estimatedResultCount;
                    }
                  }
                  resolve(data);
                  return response;
                }
              );
            } else {
              reject('User has no group for google URL search');
            }
          });
        });
      }

      /**
       * Returns a promise that will return a googleSearchURL if any exist
       * for a users group or null if one does not exists.
       */
      function getGoogleSearchURL() {
        var successFn;
        var errorFn;

        if (googleSearchURLPromise) {
          return googleSearchURLPromise;
        }

        successFn = function(groups) {
          return miscSearchService.getSearchURLS(groups).then(function(result) {
            if (result && result.googleSearchURL) {
              return result.googleSearchURL;
            } else {
              return null;
            }
          });
        };

        errorFn = function(reason) {
          miscService.redirectUser(
            reason.status,
            'Could not get appropriate google search url'
          );
        };

        googleSearchURLPromise =
          portalGroupService.getGroups().then(successFn, errorFn);

        return googleSearchURLPromise;
      }

      /**
       * Returns promise that will return true or false if there exists
       * a directorySearchURL for any of the users groups
       */
      function googleSearchEnabled() {
        var successFn;
        var errorFn;

        if (googleSearchEnabledPromise) {
          return googleSearchEnabledPromise;
        }

        successFn = function(googleSearchURL) {
          if (googleSearchURL) {
            return true;
          } else {
            return false;
          }
        };

        errorFn = function(response) {
          $log.log('error determing if google search is enabled: ' +
            response.status);
          return false;
        };

        googleSearchEnabledPromise =
          getGoogleSearchURL().then(successFn, errorFn);
        return googleSearchEnabledPromise;
      }

      return {
        googleSearch: googleSearch,
        googleSearchEnabled: googleSearchEnabled,
        getPublicWebSearchURL: getPublicWebSearchURL,
        getDomainResultsLabel: getDomainResultsLabel,
      };
    }])

    .factory('miscSearchService',
      ['$q', '$sessionStorage', 'portalGroupService',
        'filterFilter', 'SEARCH_CONFIG',
      function($q, $sessionStorage, portalGroupService,
        filterFilter, SEARCH_CONFIG) {
      /**
       * retrieve the KB Search URL from the Group Service
       * @return {string} kbSearchURL
       */
      function getKBSearchURL() {
        return portalGroupService.getGroups().then(
          function(groups) {
            return getSearchURLS(groups).then(
              function(result) {
                if (result && result.kbSearchURL) {
                  return result.kbSearchURL;
                } else {
                  return null;
                }
              }
            );
          }
        );
      }

      /**
       * retrieve the Events Search URL from the Group Service
       * @return {string} eventsSearchURL
       */
      function getEventSearchURL() {
        return portalGroupService.getGroups().then(
          function(groups) {
            return getSearchURLS(groups).then(
              function(result) {
                if (result && result.eventsSearchURL) {
                  return result.eventsSearchURL;
                } else {
                  return null;
                }
              }
            );
          }
        );
      }

      /**
       * retrieve the Help Desk URL from the Group Service
       * @return {string} helpdeskURL
       */
      function getHelpDeskHelpURL() {
        return portalGroupService.getGroups().then(
          function(groups) {
            return getSearchURLS(groups).then(
              function(result) {
                if (result && result.helpdeskURL) {
                  return result.helpdeskURL;
                } else {
                  return null;
                }
              }
            );
          }
        );
      }

      /**
       * retrieve the search URLs
       */
      function getSearchURLS(groups) {
        return $q(function(resolve, reject) {
          if ($sessionStorage.search) {
            resolve($sessionStorage.search);
          }
          for (var i = 0; i < SEARCH_CONFIG.length; i++) {
            var searchURLS = SEARCH_CONFIG[i];
            var searchGroup = searchURLS.group;
            var filterTest = filterFilter(groups, {name: searchGroup});
            if (filterTest && filterTest.length >0) {
              $sessionStorage.search = searchURLS;
              resolve(searchURLS);
            }
          }
          resolve(null);
        });
      }

      return {
          getSearchURLS: getSearchURLS,
          getKBSearchURL: getKBSearchURL,
          getEventSearchURL: getEventSearchURL,
          getHelpDeskHelpURL: getHelpDeskHelpURL,
      };
    }])

    .factory('directorySearchService',
        ['$log', '$http', '$q', 'portalGroupService',
          'miscSearchService', 'miscService',
        function($log, $http, $q, portalGroupService,
          miscSearchService, miscService) {
      var directoryUrlPromise;
      var directorySearchEnabledPromise;

      /**
       * Search the directory for term
       * @param  {string} term
       */
      function directorySearch(term) {
        return getDirectorySearchURL().then(function(searchDirectoryURL) {
          return $q(function(resolve, reject) {
            if (searchDirectoryURL) {
              return $http.get(searchDirectoryURL + '/?name=' + term).then(
                function(response) {
                  return resolve(response.data);
                },
                function(response) {
                  return reject('error searching the directory: ' +
                    response.status);
                }
              );
            } else {
              return reject('User has no group for directory search');
            }
          });
        });
      }

      /**
       * Returns promise that will return true or false if there exists
       * a directorySearchURL for any of the users groups
       */
      function directorySearchEnabled() {
          var successFn;
          var errorFn;

          if (directorySearchEnabledPromise) {
            return directorySearchEnabledPromise;
          }

          successFn = function(directoryURL) {
            if (directoryURL) {
              return true;
            } else {
              return false;
            }
          };

          errorFn = function(response) {
            $log.log('error determing if directory search is enabled: ' +
              response.status);
            return false;
          };

          directorySearchEnabledPromise = getDirectorySearchURL()
            .then(successFn, errorFn);
          return directorySearchEnabledPromise;
      }

      /**
       * Returns a promise that will return a directorySearchURL if any exist
       * for a users group or null if one does not exists.
       * @return {Promise} directoryUrlPromise
       */
      function getDirectorySearchURL() {
        var successFn;
        var errorFn;

        if (directoryUrlPromise) {
          return directoryUrlPromise;
        }

        successFn = function(groups) {
          return miscSearchService.getSearchURLS(groups).then(function(result) {
              if (result && result.directorySearchURL) {
                  return result.directorySearchURL;
              } else {
                  return null;
              }
          });
        };

        errorFn = function(reason) {
          miscService.redirectUser(reason.status, 'search directory url call');
        };

        directoryUrlPromise = portalGroupService.getGroups()
          .then(successFn, errorFn);

        return directoryUrlPromise;
      }

      return {
        directorySearch: directorySearch,
        directorySearchEnabled: directorySearchEnabled,
      };
    }]);
});
<|endoftext|>


<commit_before>var uuid = require('node-uuid');

module.exports = {
  startPriority: 101,
  stopPriority:  999,
  loadPriority:  200,
  initialize: function(api, next){

    api.redis = {};
    api.redis.clusterCallbaks = {};
    api.redis.clusterCallbakTimeouts = {};
    api.redis.subscriptionHandlers = {};
    api.redis.status = {
      client: false,
      subscriber: false,
      subscribed: false,
      calledback: false,
    };

    var redisPackage = require(api.config.redis.pkg);
    if(api.config.redis.pkg === 'fakeredis'){
      api.log('running with fakeredis', 'warning');
      redisPackage.fast = true;
    }

    api.redis.initialize = function(callback){
      if(api.config.redis.pkg === 'fakeredis'){
        api.redis.client     = redisPackage.createClient(String(api.config.redis.host));
        api.redis.subscriber = redisPackage.createClient(String(api.config.redis.host));
      }else{
        api.redis.client     = redisPackage.createClient(api.config.redis.port, api.config.redis.host, api.config.redis.options);
        api.redis.subscriber = redisPackage.createClient(api.config.redis.port, api.config.redis.host, api.config.redis.options);
      }

      api.redis.client.on('error', function(err){
        api.log(['Redis Error (client): %s', err], 'emerg');
      });

      api.redis.subscriber.on('error', function(err){
        api.log(['Redis Error (subscriber): %s', err], 'emerg');
      });

      api.redis.client.on('end', function(){
        api.log('Redis Connection Closed (client)', 'debug');
        api.redis.status.client = false;
      });

      api.redis.subscriber.on('end', function(){
        api.log('Redis Connection Closed (subscriber)', 'debug');
        api.redis.status.subscriber = false;
        api.redis.status.subscribed = false;
      });

      api.redis.client.on('connect', function(){
        if(api.config.redis.database){ api.redis.client.select(api.config.redis.database); }
        api.log('connected to redis (client)', 'debug');
        api.redis.status.client = true;
        if(api.redis.status.client === true && api.redis.status.subscriber === true && api.redis.status.calledback === false){
          api.redis.status.calledback = true;
          callback();
        }
      });

      if(!api.redis.status.subscribed){
        api.redis.subscriber.subscribe(api.config.redis.channel);
        api.redis.status.subscribed = true;
      }

      api.redis.subscriber.on('message', function(messageChannel, message){
        try{ message = JSON.parse(message); }catch(e){ message = {}; }
        if(messageChannel === api.config.redis.channel && message.serverToken === api.config.general.serverToken){
          if(api.redis.subscriptionHandlers[message.messageType]){
            api.redis.subscriptionHandlers[message.messageType](message);
          }
        }
      });

      api.redis.subscriber.on('connect', function(){
        // if(api.config.redis.database){ api.redis.subscriber.select(api.config.redis.database); }
        api.log('connected to redis (subscriber)', 'debug');
        api.redis.status.subscriber = true;
        if(api.redis.status.client === true && api.redis.status.subscriber === true && api.redis.status.calledback === false){
          api.redis.status.calledback = true;
          callback();
        }
      });

      if(api.config.redis.pkg === 'fakeredis'){
        api.redis.status.client = true;
        api.redis.status.subscriber = true;
        process.nextTick(function(){
          api.redis.status.calledback = true;
          callback();
        });
      }
    };

    api.redis.publish = function(payload){
      var channel = api.config.redis.channel;
      api.redis.client.publish(channel, JSON.stringify(payload));
    };

    // Subsciption Handlers

    api.redis.subscriptionHandlers.do = function(message){
      if(!message.connectionId || ( api.connections && api.connections.connections[message.connectionId]) ){
        var cmdParts = message.method.split('.');
        var cmd = cmdParts.shift();
        if(cmd !== 'api'){ throw new Error('cannot operate on a method outside of the api object') }
        var method = api.utils.stringToHash(cmdParts.join('.'));

        var callback = function(){
          var responseArgs = Array.apply(null, arguments).sort();
          process.nextTick(function(){
            api.redis.respondCluster(message.requestId, responseArgs);
          });
        };
        var args = message.args;
        if(args === null){ args = []; }
        if(!Array.isArray(args)){ args = [args]; }
        args.push(callback);
        method.apply(null, args);
      }
    };

    api.redis.subscriptionHandlers.doResponse = function(message){
      if(api.redis.clusterCallbaks[message.requestId]){
        clearTimeout(api.redis.clusterCallbakTimeouts[message.requestId]);
        api.redis.clusterCallbaks[message.requestId].apply(null, message.response);
        delete api.redis.clusterCallbaks[message.requestId];
        delete api.redis.clusterCallbakTimeouts[message.requestId];
      }
    };

    // RPC

    api.redis.doCluster = function(method, args, connectionId, callback){
      var requestId = uuid.v4();
      var payload = {
        messageType  : 'do',
        serverId     : api.id,
        serverToken  : api.config.general.serverToken,
        requestId    : requestId,
        method       : method,
        connectionId : connectionId,
        args         : args,   // [1,2,3]
      };

      api.redis.publish(payload);

      if(typeof callback === 'function'){
        api.redis.clusterCallbaks[requestId] = callback;
        api.redis.clusterCallbakTimeouts[requestId] = setTimeout(function(requestId){
          if(typeof api.redis.clusterCallbaks[requestId] === 'function'){
            api.redis.clusterCallbaks[requestId](new Error('RPC Timeout'));
          }
          delete api.redis.clusterCallbaks[requestId];
          delete api.redis.clusterCallbakTimeouts[requestId];
        }, api.config.redis.rpcTimeout, requestId);
      }
    };

    api.redis.respondCluster = function(requestId, response){
      var payload = {
        messageType  : 'doResponse',
        serverId     : api.id,
        serverToken  : api.config.general.serverToken,
        requestId    : requestId,
        response     : response, // args to pass back, including error
      };

      api.redis.publish(payload);
    };

    // Boot

    api.redis.initialize(function(){
      api.redis.doCluster('api.log', [['actionhero member %s has joined the cluster', api.id]], null, null);
      process.nextTick(next);
    });

  },

  start: function(api, next){
    next();
  },

  stop: function(api, next){
    for(var i in api.redis.clusterCallbakTimeouts){
      clearTimeout( api.redis.clusterCallbakTimeouts[i] );
      delete api.redis.clusterCallbakTimeouts[i];
      delete api.redis.clusterCallbaks[i];
    }
    api.redis.doCluster('api.log', [['actionhero member %s has left the cluster', api.id]], null, null);

    process.nextTick(function(){
      api.redis.subscriber.unsubscribe();
      next();
    });
  }
};
<commit_msg>Package keyword issues<commit_after>var uuid = require('node-uuid');

module.exports = {
  startPriority: 101,
  stopPriority:  999,
  loadPriority:  200,
  initialize: function(api, next){

    api.redis = {};
    api.redis.clusterCallbaks = {};
    api.redis.clusterCallbakTimeouts = {};
    api.redis.subscriptionHandlers = {};
    api.redis.status = {
      client: false,
      subscriber: false,
      subscribed: false,
      calledback: false,
    };
    api.config.redis.pkg = api.config.redis.pkg || api.config.redis.package;
    var redisPackage = require(api.config.redis.pkg);
    if(api.config.redis.pkg === 'fakeredis'){
      api.log('running with fakeredis', 'warning');
      redisPackage.fast = true;
    }

    api.redis.initialize = function(callback){
      if(api.config.redis.pkg === 'fakeredis'){
        api.redis.client     = redisPackage.createClient(String(api.config.redis.host));
        api.redis.subscriber = redisPackage.createClient(String(api.config.redis.host));
      }else{
        api.redis.client     = redisPackage.createClient(api.config.redis.port, api.config.redis.host, api.config.redis.options);
        api.redis.subscriber = redisPackage.createClient(api.config.redis.port, api.config.redis.host, api.config.redis.options);
      }

      api.redis.client.on('error', function(err){
        api.log(['Redis Error (client): %s', err], 'emerg');
      });

      api.redis.subscriber.on('error', function(err){
        api.log(['Redis Error (subscriber): %s', err], 'emerg');
      });

      api.redis.client.on('end', function(){
        api.log('Redis Connection Closed (client)', 'debug');
        api.redis.status.client = false;
      });

      api.redis.subscriber.on('end', function(){
        api.log('Redis Connection Closed (subscriber)', 'debug');
        api.redis.status.subscriber = false;
        api.redis.status.subscribed = false;
      });

      api.redis.client.on('connect', function(){
        if(api.config.redis.database){ api.redis.client.select(api.config.redis.database); }
        api.log('connected to redis (client)', 'debug');
        api.redis.status.client = true;
        if(api.redis.status.client === true && api.redis.status.subscriber === true && api.redis.status.calledback === false){
          api.redis.status.calledback = true;
          callback();
        }
      });

      if(!api.redis.status.subscribed){
        api.redis.subscriber.subscribe(api.config.redis.channel);
        api.redis.status.subscribed = true;
      }

      api.redis.subscriber.on('message', function(messageChannel, message){
        try{ message = JSON.parse(message); }catch(e){ message = {}; }
        if(messageChannel === api.config.redis.channel && message.serverToken === api.config.general.serverToken){
          if(api.redis.subscriptionHandlers[message.messageType]){
            api.redis.subscriptionHandlers[message.messageType](message);
          }
        }
      });

      api.redis.subscriber.on('connect', function(){
        // if(api.config.redis.database){ api.redis.subscriber.select(api.config.redis.database); }
        api.log('connected to redis (subscriber)', 'debug');
        api.redis.status.subscriber = true;
        if(api.redis.status.client === true && api.redis.status.subscriber === true && api.redis.status.calledback === false){
          api.redis.status.calledback = true;
          callback();
        }
      });

      if(api.config.redis.pkg === 'fakeredis'){
        api.redis.status.client = true;
        api.redis.status.subscriber = true;
        process.nextTick(function(){
          api.redis.status.calledback = true;
          callback();
        });
      }
    };

    api.redis.publish = function(payload){
      var channel = api.config.redis.channel;
      api.redis.client.publish(channel, JSON.stringify(payload));
    };

    // Subsciption Handlers

    api.redis.subscriptionHandlers.do = function(message){
      if(!message.connectionId || ( api.connections && api.connections.connections[message.connectionId]) ){
        var cmdParts = message.method.split('.');
        var cmd = cmdParts.shift();
        if(cmd !== 'api'){ throw new Error('cannot operate on a method outside of the api object') }
        var method = api.utils.stringToHash(cmdParts.join('.'));

        var callback = function(){
          var responseArgs = Array.apply(null, arguments).sort();
          process.nextTick(function(){
            api.redis.respondCluster(message.requestId, responseArgs);
          });
        };
        var args = message.args;
        if(args === null){ args = []; }
        if(!Array.isArray(args)){ args = [args]; }
        args.push(callback);
        method.apply(null, args);
      }
    };

    api.redis.subscriptionHandlers.doResponse = function(message){
      if(api.redis.clusterCallbaks[message.requestId]){
        clearTimeout(api.redis.clusterCallbakTimeouts[message.requestId]);
        api.redis.clusterCallbaks[message.requestId].apply(null, message.response);
        delete api.redis.clusterCallbaks[message.requestId];
        delete api.redis.clusterCallbakTimeouts[message.requestId];
      }
    };

    // RPC

    api.redis.doCluster = function(method, args, connectionId, callback){
      var requestId = uuid.v4();
      var payload = {
        messageType  : 'do',
        serverId     : api.id,
        serverToken  : api.config.general.serverToken,
        requestId    : requestId,
        method       : method,
        connectionId : connectionId,
        args         : args,   // [1,2,3]
      };

      api.redis.publish(payload);

      if(typeof callback === 'function'){
        api.redis.clusterCallbaks[requestId] = callback;
        api.redis.clusterCallbakTimeouts[requestId] = setTimeout(function(requestId){
          if(typeof api.redis.clusterCallbaks[requestId] === 'function'){
            api.redis.clusterCallbaks[requestId](new Error('RPC Timeout'));
          }
          delete api.redis.clusterCallbaks[requestId];
          delete api.redis.clusterCallbakTimeouts[requestId];
        }, api.config.redis.rpcTimeout, requestId);
      }
    };

    api.redis.respondCluster = function(requestId, response){
      var payload = {
        messageType  : 'doResponse',
        serverId     : api.id,
        serverToken  : api.config.general.serverToken,
        requestId    : requestId,
        response     : response, // args to pass back, including error
      };

      api.redis.publish(payload);
    };

    // Boot

    api.redis.initialize(function(){
      api.redis.doCluster('api.log', [['actionhero member %s has joined the cluster', api.id]], null, null);
      process.nextTick(next);
    });

  },

  start: function(api, next){
    next();
  },

  stop: function(api, next){
    for(var i in api.redis.clusterCallbakTimeouts){
      clearTimeout( api.redis.clusterCallbakTimeouts[i] );
      delete api.redis.clusterCallbakTimeouts[i];
      delete api.redis.clusterCallbaks[i];
    }
    api.redis.doCluster('api.log', [['actionhero member %s has left the cluster', api.id]], null, null);

    process.nextTick(function(){
      api.redis.subscriber.unsubscribe();
      next();
    });
  }
};
<|endoftext|>


<commit_before>module.exports = [
  {
    name: 'Pre-bundled for Browser (UMD)',
    path: 'dist/browser/hibp.umd.min.js',
    limit: '5.5 KB',
  },
  {
    name: 'Pre-bundled for Browser (ESM)',
    path: 'dist/browser/hibp.esm.min.js',
    limit: '5 KB',
  },
  {
    name: 'Bundled with Webpack (CJS)',
    path: 'dist/cjs/*.js',
    limit: '6 KB',
  },
  {
    name: 'Bundled with Webpack (ESM)',
    path: 'dist/esm/*.js',
    limit: '6 KB',
  },
];
<commit_msg>ci(size-limit): track all exports separately (#152)<commit_after>module.exports = [
  // Pre-bundled for Browser (UMD)
  {
    path: 'dist/browser/hibp.umd.min.js',
    limit: '5.25 KB',
  },
  // Pre-bundled for Browser (ESM)
  {
    path: 'dist/browser/hibp.esm.min.js',
    limit: '4.75 KB',
  },
  // Bundled with Webpack (CJS)
  {
    path: 'dist/cjs/breach.js',
    limit: '1.3 KB',
  },
  {
    path: 'dist/cjs/breachedAccount.js',
    limit: '1.4 KB',
  },
  {
    path: 'dist/cjs/breaches.js',
    limit: '1.4 KB',
  },
  {
    path: 'dist/cjs/dataClasses.js',
    limit: '1.3 KB',
  },
  {
    path: 'dist/cjs/pasteAccount.js',
    limit: '1.3 KB',
  },
  {
    path: 'dist/cjs/pwnedPassword.js',
    limit: '4.1 KB',
  },
  {
    path: 'dist/cjs/pwnedPasswordRange.js',
    limit: '1 KB',
  },
  {
    path: 'dist/cjs/search.js',
    limit: '2 KB',
  },
  // Bundled with Webpack (ESM)
  {
    path: 'dist/esm/breach.js',
    limit: '1.1 KB',
  },
  {
    path: 'dist/esm/breachedAccount.js',
    limit: '1.1 KB',
  },
  {
    path: 'dist/esm/breaches.js',
    limit: '1.1 KB',
  },
  {
    path: 'dist/esm/dataClasses.js',
    limit: '1.1 KB',
  },
  {
    path: 'dist/esm/pasteAccount.js',
    limit: '1.1 KB',
  },
  {
    path: 'dist/esm/pwnedPassword.js',
    limit: '4.1 KB',
  },
  {
    path: 'dist/esm/pwnedPasswordRange.js',
    limit: '1 KB',
  },
  {
    path: 'dist/esm/search.js',
    limit: '1.7 KB',
  },
];
<|endoftext|>


<commit_before>/* global $, Whisper, moment, WebAudioRecorder */

/* eslint-disable more/no-then */

// eslint-disable-next-line func-names
(function() {
  'use strict';

  window.Whisper = window.Whisper || {};

  Whisper.RecorderView = Whisper.View.extend({
    className: 'recorder clearfix',
    templateName: 'recorder',
    initialize() {
      this.startTime = Date.now();
      this.interval = setInterval(this.updateTime.bind(this), 1000);

      this.onSwitchAwayBound = this.onSwitchAway.bind(this);
      $(window).on('blur', this.onSwitchAwayBound);

      this.start();
    },
    events: {
      'click .close': 'close',
      'click .finish': 'finish',
      close: 'close',
    },
    onSwitchAway() {
      this.close();
    },
    updateTime() {
      const duration = moment.duration(Date.now() - this.startTime, 'ms');
      const minutes = `${Math.trunc(duration.asMinutes())}`;
      let seconds = `${duration.seconds()}`;
      if (seconds.length < 2) {
        seconds = `0${seconds}`;
      }
      this.$('.time').text(`${minutes}:${seconds}`);
    },
    close() {
      // Note: the 'close' event can be triggered by InboxView, when the user clicks
      //   anywhere outside the recording pane.

      if (this.recorder.isRecording()) {
        this.recorder.cancelRecording();
      }
      this.recorder = null;

      if (this.interval) {
        clearInterval(this.interval);
      }
      this.interval = null;

      if (this.source) {
        this.source.disconnect();
      }
      this.source = null;

      if (this.context) {
        this.context.close().then(() => {
          window.log.info('audio context closed');
        });
      }
      this.context = null;

      this.remove();
      this.trigger('closed');

      $(window).off('blur', this.onSwitchAwayBound);
    },
    finish() {
      this.clickedFinish = true;
      this.recorder.finishRecording();
      this.close();
    },
    handleBlob(recorder, blob) {
      if (blob && this.clickedFinish) {
        this.trigger('send', blob);
      } else {
        this.close();
      }
    },
    start() {
      this.clickedFinish = false;
      this.context = new AudioContext();
      this.input = this.context.createGain();
      this.recorder = new WebAudioRecorder(this.input, {
        encoding: 'mp3',
        workerDir: 'js/', // must end with slash
      });
      this.recorder.onComplete = this.handleBlob.bind(this);
      this.recorder.onError = this.onError.bind(this);
      navigator.webkitGetUserMedia(
        { audio: true },
        stream => {
          this.source = this.context.createMediaStreamSource(stream);
          this.source.connect(this.input);
        },
        this.onError.bind(this)
      );
      this.recorder.startRecording();
    },
    onError(error) {
      // Protect against out-of-band errors, which can happen if the user revokes media
      //   permissions after successfully accessing the microphone.
      if (!this.recorder) {
        return;
      }

      this.close();

      if (error && error.name === 'PermissionDeniedError') {
        window.log.warn(
          'RecorderView.onError: Microphone access is not allowed!'
        );
        window.showPermissionsPopup();
      } else {
        window.log.error(
          'RecorderView.onError:',
          error && error.stack ? error.stack : error
        );
      }
    },
  });
})();
<commit_msg>Ensure that dialog pops up when permissions denied for voice note<commit_after>/* global $, Whisper, moment, WebAudioRecorder */

/* eslint-disable more/no-then */

// eslint-disable-next-line func-names
(function() {
  'use strict';

  window.Whisper = window.Whisper || {};

  Whisper.RecorderView = Whisper.View.extend({
    className: 'recorder clearfix',
    templateName: 'recorder',
    initialize() {
      this.startTime = Date.now();
      this.interval = setInterval(this.updateTime.bind(this), 1000);

      this.onSwitchAwayBound = this.onSwitchAway.bind(this);
      $(window).on('blur', this.onSwitchAwayBound);

      this.start();
    },
    events: {
      'click .close': 'close',
      'click .finish': 'finish',
      close: 'close',
    },
    onSwitchAway() {
      this.close();
    },
    updateTime() {
      const duration = moment.duration(Date.now() - this.startTime, 'ms');
      const minutes = `${Math.trunc(duration.asMinutes())}`;
      let seconds = `${duration.seconds()}`;
      if (seconds.length < 2) {
        seconds = `0${seconds}`;
      }
      this.$('.time').text(`${minutes}:${seconds}`);
    },
    close() {
      // Note: the 'close' event can be triggered by InboxView, when the user clicks
      //   anywhere outside the recording pane.

      if (this.recorder.isRecording()) {
        this.recorder.cancelRecording();
      }
      this.recorder = null;

      if (this.interval) {
        clearInterval(this.interval);
      }
      this.interval = null;

      if (this.source) {
        this.source.disconnect();
      }
      this.source = null;

      if (this.context) {
        this.context.close().then(() => {
          window.log.info('audio context closed');
        });
      }
      this.context = null;

      this.remove();
      this.trigger('closed');

      $(window).off('blur', this.onSwitchAwayBound);
    },
    finish() {
      this.clickedFinish = true;
      this.recorder.finishRecording();
      this.close();
    },
    handleBlob(recorder, blob) {
      if (blob && this.clickedFinish) {
        this.trigger('send', blob);
      } else {
        this.close();
      }
    },
    start() {
      this.clickedFinish = false;
      this.context = new AudioContext();
      this.input = this.context.createGain();
      this.recorder = new WebAudioRecorder(this.input, {
        encoding: 'mp3',
        workerDir: 'js/', // must end with slash
      });
      this.recorder.onComplete = this.handleBlob.bind(this);
      this.recorder.onError = this.onError.bind(this);
      navigator.webkitGetUserMedia(
        { audio: true },
        stream => {
          this.source = this.context.createMediaStreamSource(stream);
          this.source.connect(this.input);
        },
        this.onError.bind(this)
      );
      this.recorder.startRecording();
    },
    onError(error) {
      // Protect against out-of-band errors, which can happen if the user revokes media
      //   permissions after successfully accessing the microphone.
      if (!this.recorder) {
        return;
      }

      this.close();

      if (error && error.name === 'NotAllowedError') {
        window.log.warn(
          'RecorderView.onError: Microphone access is not allowed!'
        );
        window.showPermissionsPopup();
      } else {
        window.log.error(
          'RecorderView.onError:',
          error && error.stack ? error.stack : error
        );
      }
    },
  });
})();
<|endoftext|>


<commit_before>'use strict';
var tape = require('../lib/thaliTape');
var express = require('express');
var crypto = require('crypto');
var sinon = require('sinon');
var Promise = require('lie');
var http = require('http');
var httpTester = require('../lib/httpTester.js');

var ThaliPeerDictionary =
  require('thali/NextGeneration/notification/thaliPeerDictionary');
var ThaliNotificationClient =
  require('thali/NextGeneration/notification/thaliNotificationClient');
var ThaliMobileNativeWrapper =
  require('thali/NextGeneration/thaliMobileNativeWrapper');

var ThaliPeerPoolDefault =
  require('thali/NextGeneration/thaliPeerPool/thaliPeerPoolDefault');

var NotificationBeacons =
  require('thali/NextGeneration/notification/thaliNotificationBeacons');

var thaliConfig =
  require('thali/NextGeneration/thaliConfig');

var pskIdToSecret = function (id) {
  return id === thaliConfig.BEACON_PSK_IDENTITY ? thaliConfig.BEACON_KEY : null;
};

var globals = {};

/**
 * @classdesc This class is a container for all variables and
 * functionality that are common to most of the ThaliNoficationServer
 * tests.
 */
var GlobalVariables = function () {

  this.expressApp = express();
  this.expressRouter = express.Router();

  this.sourceKeyExchangeObject = crypto.createECDH(thaliConfig.BEACON_CURVE);
  this.sourcePublicKey = this.sourceKeyExchangeObject.generateKeys();
  this.sourcePublicKeyHash =
    NotificationBeacons.createPublicKeyHash(this.sourcePublicKey);

  this.peerPoolInterface = new ThaliPeerPoolDefault();
  this.peerPoolInterface.start();
  this.peerPoolInterfaceStub = new ThaliPeerPoolDefault();
  this.peerPoolInterfaceStub.start();

  this.TCPEvent = {
    peerIdentifier: 'id124',
    hostAddress: '127.0.0.1',
    portNumber: 0,
    connectionType: ThaliMobileNativeWrapper.connectionTypes.TCP_NATIVE,
    suggestedTCPTimeout: 10000
  };

  this.originalTimeOuts = ThaliNotificationClient.RETRY_TIMEOUTS;
  this.createPublicKeysToNotifyAndPreamble();
};

GlobalVariables.prototype.init = function () {
  var self = this;
  return httpTester.getTestHttpsServer(self.expressApp,
    self.expressRouter, pskIdToSecret)
    .then(function (server) {
      self.expressServer = server;
      self.TCPEvent.portNumber = self.expressServer.address().port;
      return Promise.resolve();
    })
    .catch(function (failure) {
      return Promise.reject(failure);
    });
};

/**
 * Frees GlobalVariables instance's resources.
 * @returns {Promise<?Error>} Returns a promise that will resolve when the
 * resources are released.
 */
GlobalVariables.prototype.kill = function () {

  ThaliNotificationClient.RETRY_TIMEOUTS =
    this.originalTimeOuts;

  if (this.expressServer) {
    return this.expressServer.closeAllPromise();
  }
  return Promise.resolve();
};

GlobalVariables.prototype.createPublicKeysToNotifyAndPreamble = function () {
  this.targetPublicKeysToNotify = [];
  this.targetPublicKeysToNotifyHashes = [];
  this.targetDeviceKeyExchangeObjects = [];
  this.preambleAndBeacons = {};

  var device1 = crypto.createECDH(thaliConfig.BEACON_CURVE);
  var device1Key = device1.generateKeys();
  var device1KeyHash = NotificationBeacons.createPublicKeyHash(device1Key);

  var device2 = crypto.createECDH(thaliConfig.BEACON_CURVE);
  var device2Key = device2.generateKeys();
  var device2KeyHash = NotificationBeacons.createPublicKeyHash(device2Key);

  this.targetPublicKeysToNotify.push(device1Key, device2Key);
  this.targetPublicKeysToNotifyHashes.push(device1KeyHash, device2KeyHash);
  this.targetDeviceKeyExchangeObjects.push(device2, device2);

  this.preambleAndBeacons =
    NotificationBeacons.generatePreambleAndBeacons(
      this.targetPublicKeysToNotify, this.sourceKeyExchangeObject,
      60 * 60 * 1000);
};

var test = tape({
  setup: function (t) {
    globals = new GlobalVariables();
    globals.init().then(function () {
      t.end();
    }).catch(function (failure) {
      t.fail('Test setting up failed:' + failure);
      t.end();
    });
  },
  teardown: function (t) {
    globals.kill().then(function () {
      t.end();
    }).catch(function (failure) {
      t.fail('Server cleaning failed:' + failure);
      t.end();
    });
  }
});

test('Add two Peers.', function (t) {

  // Scenario:
  // 1. Event: connectionType is BLUETOOTH
  // 2. Event: connectionType is TCP_NATIVE

  // Expected result:
  // Two peers are added into the dictionary

  var notificationClient =
    new ThaliNotificationClient(globals.peerPoolInterfaceStub,
      globals.sourceKeyExchangeObject, function () {});

  notificationClient.start([]);

  var BluetoothEvent = {
    peerIdentifier: 'id123',
    hostAddress: 'anything',
    portNumber: 8080,
    connectionType: ThaliMobileNativeWrapper.connectionTypes.BLUETOOTH,
    suggestedTCPTimeout: 1000
  };

  var TCPEvent = {
    peerIdentifier: 'id3212',
    hostAddress: 'anything',
    portNumber: 8080,
    connectionType: ThaliMobileNativeWrapper.connectionTypes.TCP_NATIVE,
    suggestedTCPTimeout: 1000
  };

  // New peer with Bluetooth connection
  notificationClient._peerAvailabilityChanged(BluetoothEvent);
  t.equal(notificationClient.peerDictionary.size(), 1);

  // New peer with TCP_NATIVE connection
  notificationClient._peerAvailabilityChanged(TCPEvent);
  t.equal(notificationClient.peerDictionary.size(), 2);

  var peer = notificationClient.peerDictionary.get('id123');
  var peer2 = notificationClient.peerDictionary.get('id3212');

  t.equal(peer.notificationAction.getConnectionType(),
    ThaliMobileNativeWrapper.connectionTypes.BLUETOOTH);

  t.equal(peer2.notificationAction.getConnectionType(),
    ThaliMobileNativeWrapper.connectionTypes.TCP_NATIVE);

  notificationClient.stop();
  t.equal(notificationClient.peerDictionary, null);
  t.end();

});

test('TCP_NATIVE peer loses DNS', function (t) {

  // Scenario:
  // 1. Event: connectionType is TCP_NATIVE, hostaddress is set
  // 2. Event: connectionType is TCP_NATIVE, hostaddress is not set

  // Expected result: Peer will be removed from the dictionary

  var notificationClient =
    new ThaliNotificationClient(globals.peerPoolInterfaceStub,
      globals.sourceKeyExchangeObject);

  notificationClient.start([]);

  // New peer with TCP connection
  notificationClient._peerAvailabilityChanged(globals.TCPEvent);
  t.equal(notificationClient.peerDictionary.size(), 1);

  globals.TCPEvent.hostAddress = undefined;

  // New peer with TCP_NATIVE connection but without hostaddress
  notificationClient._peerAvailabilityChanged(globals.TCPEvent);

  t.equal(notificationClient.peerDictionary.size(), 0);

  notificationClient.stop();

  t.end();

});

test('Received beacons with no values for us', function (t) {
  var enqueue = function (action) {
    var keepAliveAgent = httpTester.getTestAgent(
      thaliConfig.BEACON_PSK_IDENTITY, thaliConfig.BEACON_KEY);
    action.start(keepAliveAgent).then(function () {
      setImmediate(function () {
        var entry =
          notificationClient.peerDictionary.get(globals.TCPEvent.peerIdentifier);
        t.ok(entry, 'entry exists');
        t.equal(entry.peerState, ThaliPeerDictionary.peerState.RESOLVED, 'entry ' +
          'is resolved');
        notificationClient.stop();
        t.end();
      });
    }).catch( function ( ) {
      t.fail('This action should not fail!');
      t.end();
    });
  };

  sinon.stub(globals.peerPoolInterface, 'enqueue', enqueue);

  httpTester.runServer(globals.expressRouter,
    thaliConfig.NOTIFICATION_BEACON_PATH,
    200, globals.preambleAndBeacons, 1);

  var notificationClient =
    new ThaliNotificationClient(globals.peerPoolInterface,
      globals.targetDeviceKeyExchangeObjects[0]);

  var bogusPublicKey =
    crypto.createECDH(thaliConfig.BEACON_CURVE).generateKeys();
  notificationClient.start([bogusPublicKey]);

  notificationClient.on(notificationClient.Events.PeerAdvertisesDataForUs,
    function () {
      t.fail('We should not have gotten an event!');
    });

  // New peer with TCP connection
  notificationClient._peerAvailabilityChanged(globals.TCPEvent);
});

test('Resolves an action locally', function (t) {

  // Scenario:
  // 1. Event: connectionType is TCP_NATIVE, hostaddress is set

  // Expected result:
  // Action is getting resolved ok

  // Simulates how the peer pool runs actions
  var enqueue = function (action) {
    var keepAliveAgent = httpTester.getTestAgent(
      thaliConfig.BEACON_PSK_IDENTITY, thaliConfig.BEACON_KEY);
    action.start(keepAliveAgent).then( function () {
    }).catch( function ( ) {
      t.fail('This action should not fail!');
    });
  };

  sinon.stub(globals.peerPoolInterface, 'enqueue', enqueue);

  httpTester.runServer(globals.expressRouter,
    thaliConfig.NOTIFICATION_BEACON_PATH,
    200, globals.preambleAndBeacons, 1);

  var notificationClient =
    new ThaliNotificationClient(globals.peerPoolInterface,
      globals.targetDeviceKeyExchangeObjects[0]);

  notificationClient.start([globals.sourcePublicKey]);

  notificationClient.on(notificationClient.Events.PeerAdvertisesDataForUs,
    function (res) {
      t.equals(
        res.hostAddress,
        globals.TCPEvent.hostAddress,
        'Host address must match');
      t.equals(
        res.suggestedTCPTimeout,
        globals.TCPEvent.suggestedTCPTimeout,
        'suggestedTCPTimeout must match');
      t.equals(
        res.connectionType,
        globals.TCPEvent.connectionType,
        'connectionType must match');
      t.equals(
        res.portNumber,
        globals.TCPEvent.portNumber,
        'portNumber must match');

      notificationClient.stop();
      t.end();
    });

  // New peer with TCP connection
  notificationClient._peerAvailabilityChanged(globals.TCPEvent);

});

test('Resolves an action locally using ThaliPeerPoolDefault', function (t) {

  // Scenario:
  // 1. Event: connectionType is TCP_NATIVE, hostaddress is set

  // Expected result:
  // Action is getting resolved ok

  var peerPool = new ThaliPeerPoolDefault();
  peerPool.start();
  httpTester.runServer(globals.expressRouter,
    thaliConfig.NOTIFICATION_BEACON_PATH,
    200, globals.preambleAndBeacons, 1);

  var notificationClient =
    new ThaliNotificationClient(peerPool,
      globals.targetDeviceKeyExchangeObjects[0]);

  notificationClient.start([globals.sourcePublicKey]);

  notificationClient.on(notificationClient.Events.PeerAdvertisesDataForUs,
    function ( res) {
      t.equals(
        res.hostAddress,
        globals.TCPEvent.hostAddress,
        'Host address must match');
      t.equals(
        res.suggestedTCPTimeout,
        globals.TCPEvent.suggestedTCPTimeout,
        'suggestedTCPTimeout must match');
      t.equals(
        res.connectionType,
        globals.TCPEvent.connectionType,
        'connectionType must match');
      t.equals(
        res.portNumber,
        globals.TCPEvent.portNumber,
        'portNumber must match');

      t.end();
    });

  // New peer with TCP connection
  notificationClient._peerAvailabilityChanged(globals.TCPEvent);

});

test('Action fails because of a bad hostname.', function (t) {

  // Scenario:
  // ConnectionType is TCP_NATIVE, host address is having wrong DNS.

  // Expected result:
  // Connection is tried RETRY_TIMEOUTS.length times

  // Make timeouts shorter (kill will return values to original)
  ThaliNotificationClient.RETRY_TIMEOUTS =
    [100, 300, 600];

  var requestCount = 0;
  var failCount = 0;

  // Simulates how peer pool runs actions
  var enqueue = function (action) {
    requestCount++;
    var keepAliveAgent = new http.Agent({ keepAlive: true });
    action.start(keepAliveAgent).then( function () {
      t.fail('This action should fail always.');
      t.end();
    }).catch( function ( ) {
      failCount++;
    });
  };

  sinon.stub(globals.peerPoolInterface, 'enqueue', enqueue);

  var notificationClient =
    new ThaliNotificationClient(globals.peerPoolInterface,
      globals.targetDeviceKeyExchangeObjects[0]);

  notificationClient.start([]);

  var TCPEvent = {
    peerIdentifier: 'id123',
    hostAddress: 'address-that-does-not-exists',
    portNumber: 123,
    connectionType: ThaliMobileNativeWrapper.connectionTypes.TCP_NATIVE,
    suggestedTCPTimeout: 10000
  };

  // New peer with TCP connection
  notificationClient._peerAvailabilityChanged(TCPEvent);

  // Waits 5 seconds. And checks results
  setTimeout( function () {
    t.equals(requestCount-1, ThaliNotificationClient.RETRY_TIMEOUTS.length);
    t.equals(failCount-1, ThaliNotificationClient.RETRY_TIMEOUTS.length);
    var entry = notificationClient.peerDictionary.get('id123');
    t.equals(entry.peerState, ThaliPeerDictionary.peerState.RESOLVED);
    notificationClient.stop();
    t.end();
  }, 5000);
});

test('hostaddress is removed when the action is running. ', function (t) {

  // Scenario:
  // 1. Event: connectionType is TCP_NATIVE, hostaddress is set
  // 2. Start to resolve the action
  // 3. Event: connectionType is TCP_NATIVE, hostaddress is not set

  // Expected result:
  // Action gets killed while the peer pool is running it
  // and the peer is removed from the dictionary.

  // Simulates how peer pool runs actions
  var enqueue = function (action) {
    var keepAliveAgent = new http.Agent({ keepAlive: true });
    action.start(keepAliveAgent).then( function () {

    }).catch( function () {
      t.fail('This action should not fail');
    });
  };

  sinon.stub(globals.peerPoolInterface, 'enqueue', enqueue);

  httpTester.runServer(globals.expressRouter,
    thaliConfig.NOTIFICATION_BEACON_PATH,
    200, globals.preambleAndBeacons, 1, 10000); // 10 seconds delay

  var notificationClient =
    new ThaliNotificationClient(globals.peerPoolInterface,
      globals.targetDeviceKeyExchangeObjects[0]);

  notificationClient.start([globals.sourcePublicKey]);

  notificationClient.on(notificationClient.Events.PeerAdvertisesDataForUs,
    function () {
      t.fail('This should never happen when action is getting killed' +
        'because of the hostname is removed');
      t.end();
    });

  // New peer with TCP connection
  notificationClient._peerAvailabilityChanged(globals.TCPEvent);

  // This updates the action after 2 seconds. This should give enough time to
  // establish a HTTP connection in slow devices but since the server waits
  // 10 seconds before it answers we have time to update the entry.

  setTimeout( function () {
    globals.TCPEvent.hostAddress = undefined;
    notificationClient._peerAvailabilityChanged(globals.TCPEvent);
    t.equal(notificationClient.peerDictionary.size(), 0);
    notificationClient.stop();
    t.end();
  }, 2000);
});

<commit_msg>fix #1059<commit_after>'use strict';
var tape = require('../lib/thaliTape');
var express = require('express');
var crypto = require('crypto');
var sinon = require('sinon');
var Promise = require('lie');
var http = require('http');
var httpTester = require('../lib/httpTester.js');

var ThaliPeerDictionary =
  require('thali/NextGeneration/notification/thaliPeerDictionary');
var ThaliNotificationClient =
  require('thali/NextGeneration/notification/thaliNotificationClient');
var ThaliMobileNativeWrapper =
  require('thali/NextGeneration/thaliMobileNativeWrapper');
var ThaliNotificationAction =
  require('thali/NextGeneration/notification/thaliNotificationAction');

var ThaliPeerPoolDefault =
  require('thali/NextGeneration/thaliPeerPool/thaliPeerPoolDefault');

var NotificationBeacons =
  require('thali/NextGeneration/notification/thaliNotificationBeacons');

var thaliConfig =
  require('thali/NextGeneration/thaliConfig');

var pskIdToSecret = function (id) {
  return id === thaliConfig.BEACON_PSK_IDENTITY ? thaliConfig.BEACON_KEY : null;
};

var globals = {};

/**
 * @classdesc This class is a container for all variables and
 * functionality that are common to most of the ThaliNoficationServer
 * tests.
 */
var GlobalVariables = function () {

  this.expressApp = express();
  this.expressRouter = express.Router();

  this.sourceKeyExchangeObject = crypto.createECDH(thaliConfig.BEACON_CURVE);
  this.sourcePublicKey = this.sourceKeyExchangeObject.generateKeys();
  this.sourcePublicKeyHash =
    NotificationBeacons.createPublicKeyHash(this.sourcePublicKey);

  this.peerPoolInterface = new ThaliPeerPoolDefault();
  this.peerPoolInterface.start();
  this.peerPoolInterfaceStub = new ThaliPeerPoolDefault();
  this.peerPoolInterfaceStub.start();

  this.TCPEvent = {
    peerIdentifier: 'id124',
    hostAddress: '127.0.0.1',
    portNumber: 0,
    connectionType: ThaliMobileNativeWrapper.connectionTypes.TCP_NATIVE,
    suggestedTCPTimeout: 10000
  };

  this.originalTimeOuts = ThaliNotificationClient.RETRY_TIMEOUTS;
  this.createPublicKeysToNotifyAndPreamble();
};

GlobalVariables.prototype.init = function () {
  var self = this;
  return httpTester.getTestHttpsServer(self.expressApp,
    self.expressRouter, pskIdToSecret)
    .then(function (server) {
      self.expressServer = server;
      self.TCPEvent.portNumber = self.expressServer.address().port;
      return Promise.resolve();
    })
    .catch(function (failure) {
      return Promise.reject(failure);
    });
};

/**
 * Frees GlobalVariables instance's resources.
 * @returns {Promise<?Error>} Returns a promise that will resolve when the
 * resources are released.
 */
GlobalVariables.prototype.kill = function () {

  ThaliNotificationClient.RETRY_TIMEOUTS =
    this.originalTimeOuts;

  if (this.expressServer) {
    return this.expressServer.closeAllPromise();
  }
  return Promise.resolve();
};

GlobalVariables.prototype.createPublicKeysToNotifyAndPreamble = function () {
  this.targetPublicKeysToNotify = [];
  this.targetPublicKeysToNotifyHashes = [];
  this.targetDeviceKeyExchangeObjects = [];
  this.preambleAndBeacons = {};

  var device1 = crypto.createECDH(thaliConfig.BEACON_CURVE);
  var device1Key = device1.generateKeys();
  var device1KeyHash = NotificationBeacons.createPublicKeyHash(device1Key);

  var device2 = crypto.createECDH(thaliConfig.BEACON_CURVE);
  var device2Key = device2.generateKeys();
  var device2KeyHash = NotificationBeacons.createPublicKeyHash(device2Key);

  this.targetPublicKeysToNotify.push(device1Key, device2Key);
  this.targetPublicKeysToNotifyHashes.push(device1KeyHash, device2KeyHash);
  this.targetDeviceKeyExchangeObjects.push(device2, device2);

  this.preambleAndBeacons =
    NotificationBeacons.generatePreambleAndBeacons(
      this.targetPublicKeysToNotify, this.sourceKeyExchangeObject,
      60 * 60 * 1000);
};

var test = tape({
  setup: function (t) {
    globals = new GlobalVariables();
    globals.init().then(function () {
      t.end();
    }).catch(function (failure) {
      t.fail('Test setting up failed:' + failure);
      t.end();
    });
  },
  teardown: function (t) {
    globals.kill().then(function () {
      t.end();
    }).catch(function (failure) {
      t.fail('Server cleaning failed:' + failure);
      t.end();
    });
  }
});

test('Add two Peers.', function (t) {

  // Scenario:
  // 1. Event: connectionType is BLUETOOTH
  // 2. Event: connectionType is TCP_NATIVE

  // Expected result:
  // Two peers are added into the dictionary

  var notificationClient =
    new ThaliNotificationClient(globals.peerPoolInterfaceStub,
      globals.sourceKeyExchangeObject, function () {});

  notificationClient.start([]);

  var BluetoothEvent = {
    peerIdentifier: 'id123',
    hostAddress: 'anything',
    portNumber: 8080,
    connectionType: ThaliMobileNativeWrapper.connectionTypes.BLUETOOTH,
    suggestedTCPTimeout: 1000
  };

  var TCPEvent = {
    peerIdentifier: 'id3212',
    hostAddress: 'anything',
    portNumber: 8080,
    connectionType: ThaliMobileNativeWrapper.connectionTypes.TCP_NATIVE,
    suggestedTCPTimeout: 1000
  };

  // New peer with Bluetooth connection
  notificationClient._peerAvailabilityChanged(BluetoothEvent);
  t.equal(notificationClient.peerDictionary.size(), 1);

  // New peer with TCP_NATIVE connection
  notificationClient._peerAvailabilityChanged(TCPEvent);
  t.equal(notificationClient.peerDictionary.size(), 2);

  var peer = notificationClient.peerDictionary.get('id123');
  var peer2 = notificationClient.peerDictionary.get('id3212');

  t.equal(peer.notificationAction.getConnectionType(),
    ThaliMobileNativeWrapper.connectionTypes.BLUETOOTH);

  t.equal(peer2.notificationAction.getConnectionType(),
    ThaliMobileNativeWrapper.connectionTypes.TCP_NATIVE);

  notificationClient.stop();
  t.equal(notificationClient.peerDictionary, null);
  t.end();

});

test('TCP_NATIVE peer loses DNS', function (t) {

  // Scenario:
  // 1. Event: connectionType is TCP_NATIVE, hostaddress is set
  // 2. Event: connectionType is TCP_NATIVE, hostaddress is not set

  // Expected result: Peer will be removed from the dictionary

  var notificationClient =
    new ThaliNotificationClient(globals.peerPoolInterfaceStub,
      globals.sourceKeyExchangeObject);

  notificationClient.start([]);

  // New peer with TCP connection
  notificationClient._peerAvailabilityChanged(globals.TCPEvent);
  t.equal(notificationClient.peerDictionary.size(), 1);

  globals.TCPEvent.hostAddress = undefined;

  // New peer with TCP_NATIVE connection but without hostaddress
  notificationClient._peerAvailabilityChanged(globals.TCPEvent);

  t.equal(notificationClient.peerDictionary.size(), 0);

  notificationClient.stop();

  t.end();

});

test('Received beacons with no values for us', function (t) {
  var enqueue = function (action) {
    var keepAliveAgent = httpTester.getTestAgent(
      thaliConfig.BEACON_PSK_IDENTITY, thaliConfig.BEACON_KEY);
    action.start(keepAliveAgent).then(function () {
      setImmediate(function () {
        var entry =
          notificationClient.peerDictionary.get(globals.TCPEvent.peerIdentifier);
        t.ok(entry, 'entry exists');
        t.equal(entry.peerState, ThaliPeerDictionary.peerState.RESOLVED, 'entry ' +
          'is resolved');
        notificationClient.stop();
        t.end();
      });
    }).catch( function ( ) {
      t.fail('This action should not fail!');
      t.end();
    });
  };

  sinon.stub(globals.peerPoolInterface, 'enqueue', enqueue);

  httpTester.runServer(globals.expressRouter,
    thaliConfig.NOTIFICATION_BEACON_PATH,
    200, globals.preambleAndBeacons, 1);

  var notificationClient =
    new ThaliNotificationClient(globals.peerPoolInterface,
      globals.targetDeviceKeyExchangeObjects[0]);

  var bogusPublicKey =
    crypto.createECDH(thaliConfig.BEACON_CURVE).generateKeys();
  notificationClient.start([bogusPublicKey]);

  notificationClient.on(notificationClient.Events.PeerAdvertisesDataForUs,
    function () {
      t.fail('We should not have gotten an event!');
    });

  // New peer with TCP connection
  notificationClient._peerAvailabilityChanged(globals.TCPEvent);
});

test('Resolves an action locally', function (t) {

  // Scenario:
  // 1. Event: connectionType is TCP_NATIVE, hostaddress is set

  // Expected result:
  // Action is getting resolved ok

  // Simulates how the peer pool runs actions
  var enqueue = function (action) {
    var keepAliveAgent = httpTester.getTestAgent(
      thaliConfig.BEACON_PSK_IDENTITY, thaliConfig.BEACON_KEY);
    action.start(keepAliveAgent).then( function () {
    }).catch( function ( ) {
      t.fail('This action should not fail!');
    });
  };

  sinon.stub(globals.peerPoolInterface, 'enqueue', enqueue);

  httpTester.runServer(globals.expressRouter,
    thaliConfig.NOTIFICATION_BEACON_PATH,
    200, globals.preambleAndBeacons, 1);

  var notificationClient =
    new ThaliNotificationClient(globals.peerPoolInterface,
      globals.targetDeviceKeyExchangeObjects[0]);

  notificationClient.start([globals.sourcePublicKey]);

  notificationClient.on(notificationClient.Events.PeerAdvertisesDataForUs,
    function (res) {
      t.equals(
        res.hostAddress,
        globals.TCPEvent.hostAddress,
        'Host address must match');
      t.equals(
        res.suggestedTCPTimeout,
        globals.TCPEvent.suggestedTCPTimeout,
        'suggestedTCPTimeout must match');
      t.equals(
        res.connectionType,
        globals.TCPEvent.connectionType,
        'connectionType must match');
      t.equals(
        res.portNumber,
        globals.TCPEvent.portNumber,
        'portNumber must match');

      notificationClient.stop();
      t.end();
    });

  // New peer with TCP connection
  notificationClient._peerAvailabilityChanged(globals.TCPEvent);

});

test('Resolves an action locally using ThaliPeerPoolDefault', function (t) {

  // Scenario:
  // 1. Event: connectionType is TCP_NATIVE, hostaddress is set

  // Expected result:
  // Action is getting resolved ok

  var peerPool = new ThaliPeerPoolDefault();
  peerPool.start();
  httpTester.runServer(globals.expressRouter,
    thaliConfig.NOTIFICATION_BEACON_PATH,
    200, globals.preambleAndBeacons, 1);

  var notificationClient =
    new ThaliNotificationClient(peerPool,
      globals.targetDeviceKeyExchangeObjects[0]);

  notificationClient.start([globals.sourcePublicKey]);

  notificationClient.on(notificationClient.Events.PeerAdvertisesDataForUs,
    function ( res) {
      t.equals(
        res.hostAddress,
        globals.TCPEvent.hostAddress,
        'Host address must match');
      t.equals(
        res.suggestedTCPTimeout,
        globals.TCPEvent.suggestedTCPTimeout,
        'suggestedTCPTimeout must match');
      t.equals(
        res.connectionType,
        globals.TCPEvent.connectionType,
        'connectionType must match');
      t.equals(
        res.portNumber,
        globals.TCPEvent.portNumber,
        'portNumber must match');

      t.end();
    });

  // New peer with TCP connection
  notificationClient._peerAvailabilityChanged(globals.TCPEvent);

});

test('Action fails because of a bad hostname.', function (t) {

  // Scenario:
  // ConnectionType is TCP_NATIVE, host address is having wrong DNS.

  // Expected result:
  // Connection is tried RETRY_TIMEOUTS.length times

  // Make timeouts shorter (kill will return values to original)
  var retryTimeouts = [100, 300, 600];
  ThaliNotificationClient.RETRY_TIMEOUTS = retryTimeouts;

  var requestCount = 0;
  var failCount = 0;

  var testResolutionEvent = function (peerId, resolution) {
    t.equal(
      resolution,
      ThaliNotificationAction.ActionResolution.NETWORK_PROBLEM,
      'action should be resolved with NETWORK_PROBLEM error'
    );
  };

  // Simulates how peer pool runs actions
  var enqueueStub = sinon.stub(
    globals.peerPoolInterface,
    'enqueue',
    function (action) {
      requestCount++;
      var keepAliveAgent = new http.Agent({ keepAlive: true });
      action.eventEmitter.on(
        ThaliNotificationAction.Events.Resolved,
        testResolutionEvent
      );
      action.start(keepAliveAgent).then(function () {
        t.fail('This action should fail always.');
        finishTest(true);
      }).catch(function () {
        failCount++;
      }).then(function () {
        if (requestCount - 1 === retryTimeouts.length) {
          finishTest();
        }
      });
    }
  );

  var notificationClient =
    new ThaliNotificationClient(globals.peerPoolInterface,
      globals.targetDeviceKeyExchangeObjects[0]);

  var TCPEvent = {
    peerIdentifier: 'id123',
    hostAddress: 'address-that-does-not-exists',
    portNumber: 123,
    connectionType: ThaliMobileNativeWrapper.connectionTypes.TCP_NATIVE,
    suggestedTCPTimeout: 10000
  };

  var finishTest = function (skipChecks) {
    if (!skipChecks) {
      t.equals(
        requestCount - 1,
        retryTimeouts.length,
        'correct number of requests'
      );
      t.equals(
        failCount - 1,
        retryTimeouts.length,
        'correct number of failures'
      );
      var entry = notificationClient.peerDictionary.get('id123');
      t.equals(
        entry.peerState,
        ThaliPeerDictionary.peerState.RESOLVED,
        'correct final peer state'
      );
    }
    enqueueStub.restore();
    notificationClient.stop();
    t.end();
  };

  notificationClient.start([]);

  // New peer with TCP connection
  notificationClient._peerAvailabilityChanged(TCPEvent);

});

test('hostaddress is removed when the action is running. ', function (t) {

  // Scenario:
  // 1. Event: connectionType is TCP_NATIVE, hostaddress is set
  // 2. Start to resolve the action
  // 3. Event: connectionType is TCP_NATIVE, hostaddress is not set

  // Expected result:
  // Action gets killed while the peer pool is running it
  // and the peer is removed from the dictionary.

  // Simulates how peer pool runs actions
  var enqueue = function (action) {
    var keepAliveAgent = new http.Agent({ keepAlive: true });
    action.start(keepAliveAgent).then( function () {

    }).catch( function () {
      t.fail('This action should not fail');
    });
  };

  sinon.stub(globals.peerPoolInterface, 'enqueue', enqueue);

  httpTester.runServer(globals.expressRouter,
    thaliConfig.NOTIFICATION_BEACON_PATH,
    200, globals.preambleAndBeacons, 1, 10000); // 10 seconds delay

  var notificationClient =
    new ThaliNotificationClient(globals.peerPoolInterface,
      globals.targetDeviceKeyExchangeObjects[0]);

  notificationClient.start([globals.sourcePublicKey]);

  notificationClient.on(notificationClient.Events.PeerAdvertisesDataForUs,
    function () {
      t.fail('This should never happen when action is getting killed' +
        'because of the hostname is removed');
      t.end();
    });

  // New peer with TCP connection
  notificationClient._peerAvailabilityChanged(globals.TCPEvent);

  // This updates the action after 2 seconds. This should give enough time to
  // establish a HTTP connection in slow devices but since the server waits
  // 10 seconds before it answers we have time to update the entry.

  setTimeout( function () {
    globals.TCPEvent.hostAddress = undefined;
    notificationClient._peerAvailabilityChanged(globals.TCPEvent);
    t.equal(notificationClient.peerDictionary.size(), 0);
    notificationClient.stop();
    t.end();
  }, 2000);
});

<|endoftext|>


<commit_before>// !preview r2d3 data=list(sources = list("FileStreamSource[file]", "Other[file]"), sinks = list("FileSink[file]"), demo = TRUE)

var barWidth = 10;
var margin = 20;
var remotesCircle = 10;
var remotesMargin = margin + remotesCircle;
var remotesHeight = 80;

var sourceCircles = svg.selectAll(".source")
  .data(data.sources)
  .enter()
  .append("g")
    .attr("class", "source")
    .attr("transform", function(d, i) {
      return "translate(" + (remotesMargin + i * 24) + ", " + remotesMargin + ")";
    })
    .append("circle");

sourceCircles.attr("cx", "0").attr("cy", "0").attr("r", "10")
  .attr("stroke", function (d, i) {
    return (i == data.sources.length - 1) ? "orange" : "white";
  })
  .attr("stroke-width", "2")
  .attr("fill", "orange")
  .on("mouseover", function (d, i) {
    sourceCircles.attr("stroke", "white");
    d3.select(this).attr("stroke", "orange");
    sourceText.text(d);
  });

var sourceText = svg
  .append("text")
    .attr("y", 35).attr("x", 24 + data.sources.length * 24)
    .attr("class", "label")
    .text(function(d) { return data.sources[data.sources.length - 1]; });

var rowsPerSecondIn = svg
  .append("text")
    .attr("y", 60).attr("x", 24 + data.sources.length * 24)
    .attr("class", "rps")
    .text("0 rps");

rowsPerSecondIn.append("title").text("rows per second");

var sinkCircles = svg.selectAll(".sink")
  .data(data.sinks)
  .enter()
  .append("g")
    .attr("class", "sink")
    .attr("transform", function(d, i) {
      return "translate(" + (remotesMargin + i * 24) + ","  + (height - remotesMargin) + ")";
    })
    .append("circle");

sinkCircles.attr("cx", "0").attr("cy", "0").attr("r", "10")
  .attr("stroke", function (d, i) {
    return (i == data.sinks.length - 1) ? "steelblue" : "white";
  })
  .attr("stroke-width", "2")
  .attr("fill", "steelblue")
  .on("mouseover", function (d, i) {
    sinkCircles.attr("stroke", "white");
    d3.select(this).attr("stroke", "steelblue");
    sinkText.text(d);
  });

var sinkText = svg
  .append("text")
    .attr("y", height - 25).attr("x", 24 + data.sinks.length * 24)
    .attr("class", "label sinkText")
    .text(function(d) { return data.sources[data.sinks.length - 1]; });

var rowsPerSecondOut = svg
  .append("text")
    .attr("y", height - 50).attr("x", 24 + data.sinks.length * 24)
    .attr("class", "rps")
    .text("0 rps");

rowsPerSecondOut.append("title").text("rows per second");

function StreamStats() {
  var rpmIn = [];
  var rpmOut = [];

  var maxIn = 100;
  var maxOut = 100;

  this.add = function(rps) {
    if (maxIn < rps.in) maxIn = rps.in;
    if (maxOut < rps.out) maxOut = rps.out;

    rpmIn.unshift(rps.in);
    rpmOut.unshift(rps.out);
  };

  this.rpmIn = function() {
    return rpmIn;
  };

  this.rpmOut = function() {
    return rpmOut;
  };

  this.maxIn = function() {
    return maxIn;
  };

  this.maxOut = function() {
    return maxOut;
  };
}

var chartIn = svg.append("g");
var chartOut = svg.append("g");

function udpateChart(stats) {
  var chartHeight = height - 2 * remotesHeight;

  var dataIn = chartIn.selectAll('rect')
    .data(stats.rpmIn());

  dataIn
    .enter()
      .append('rect')
    .merge(dataIn)
      .attr('width', barWidth)
      .attr('height', function(d, i) { return chartHeight / 2 * d / stats.maxIn();})
      .attr('x', function(d, i) { return margin + i * barWidth; })
      .attr('y', function(d, i) { return remotesHeight + chartHeight / 2 - chartHeight / 2 * d / stats.maxIn(); })
      .attr('fill', 'orange');

  var dataOut = chartOut.selectAll('rect')
    .data(stats.rpmOut());

  dataOut.
    enter()
      .append('rect')
    .merge(dataOut)
      .attr('width', barWidth)
      .attr('height', function(d, i) { return chartHeight / 2 * d / stats.maxOut();})
      .attr('x', function(d, i) { return margin + i * barWidth; })
      .attr('y', function(d, i) { return remotesHeight + chartHeight / 2; })
      .attr('fill', 'steelblue');
}

var stats = new StreamStats();

function updateStats(data) {
  stats.add(data.rps);

  rowsPerSecondIn.text(data.rps.in + " rps");
  rowsPerSecondOut.text(data.rps.out + " rps");

  udpateChart(stats);
}

if (typeof(Shiny) !== "undefined") {
  Shiny.addCustomMessageHandler("sparklyr_stream_view", updateStats);
}

if (data.demo) {
  setInterval(function() {
    var stats = {
      rps: {
        in: Math.floor(Math.random() * 100),
        out: Math.floor(Math.random() * 100)
      }
    };

    updateStats(stats);
  }, 1000);
}
<commit_msg>add support to resize stream rendering<commit_after>// !preview r2d3 data=list(sources = list("FileStreamSource[file]", "Other[file]"), sinks = list("FileSink[file]")), options = list(demo = TRUE)

function StreamStats() {
  var rpmIn = [0];
  var rpmOut = [0];

  var maxIn = 100;
  var maxOut = 100;

  this.add = function(rps) {
    if (maxIn < rps.in) maxIn = rps.in;
    if (maxOut < rps.out) maxOut = rps.out;

    rpmIn.unshift(rps.in);
    rpmOut.unshift(rps.out);
  };

  this.rpmIn = function() {
    return rpmIn;
  };

  this.rpmOut = function() {
    return rpmOut;
  };

  this.maxIn = function() {
    return maxIn;
  };

  this.maxOut = function() {
    return maxOut;
  };

  this.latestRpsIn = function() {
    return rpmIn[0];
  };

  this.latestRpsOut = function() {
    return rpmOut[0];
  };
}

var stats = new StreamStats();

function StreamRenderer(stats) {
  var width = 0;
  var height = 0;

  var root;
  var chartIn;
  var chartOut;
  var rowsPerSecondIn;
  var rowsPerSecondOut;

  var barWidth = 10;
  var margin = 20;
  var remotesCircle = 10;
  var remotesMargin = margin + remotesCircle;
  var remotesHeight = 80;

  this.setSize = function(newWidth, newHeight) {
    width = newWidth;
    height =  newHeight;
  };

  this.update = function() {
    rowsPerSecondIn.text(stats.latestRpsIn() + " rps");
    rowsPerSecondOut.text(stats.latestRpsOut() + " rps");

    var chartHeight = height - 2 * remotesHeight;

    var dataIn = chartIn.selectAll('rect')
      .data(stats.rpmIn());

    dataIn
      .enter()
        .append('rect')
      .merge(dataIn)
        .attr('width', barWidth)
        .attr('height', function(d, i) { return chartHeight / 2 * d / stats.maxIn();})
        .attr('x', function(d, i) { return margin + i * barWidth; })
        .attr('y', function(d, i) { return remotesHeight + chartHeight / 2 - chartHeight / 2 * d / stats.maxIn(); })
        .attr('fill', 'orange');

    var dataOut = chartOut.selectAll('rect')
      .data(stats.rpmOut());

    dataOut.
      enter()
        .append('rect')
      .merge(dataOut)
        .attr('width', barWidth)
        .attr('height', function(d, i) { return chartHeight / 2 * d / stats.maxOut();})
        .attr('x', function(d, i) { return margin + i * barWidth; })
        .attr('y', function(d, i) { return remotesHeight + chartHeight / 2; })
        .attr('fill', 'steelblue');
  };

  this.render = function() {
    svg.selectAll("g").remove();
    root = svg.append("g");

    chartIn = root.append("g");
    chartOut = root.append("g");

    rowsPerSecondIn = root.append("text");
    rowsPerSecondOut = root.append("text");

    var sourceCircles = root.selectAll(".source")
      .data(data.sources)
      .enter()
      .append("g")
        .attr("class", "source")
        .attr("transform", function(d, i) {
          return "translate(" + (remotesMargin + i * 24) + ", " + remotesMargin + ")";
        })
        .append("circle");

    sourceCircles.attr("cx", "0").attr("cy", "0").attr("r", "10")
      .attr("stroke", function (d, i) {
        return (i == data.sources.length - 1) ? "orange" : "white";
      })
      .attr("stroke-width", "2")
      .attr("fill", "orange")
      .on("mouseover", function (d, i) {
        sourceCircles.attr("stroke", "white");
        d3.select(this).attr("stroke", "orange");
        sourceText.text(d);
      });

    var sourceText = root
      .append("text")
        .attr("y", 35).attr("x", 24 + data.sources.length * 24)
        .attr("class", "label")
        .text(function(d) { return data.sources[data.sources.length - 1]; });

    rowsPerSecondIn
      .attr("y", 60).attr("x", 24 + data.sources.length * 24)
      .attr("class", "rps")
      .text(stats.latestRpsIn() + " rps");

    rowsPerSecondIn.append("title").text("rows per second");

    var sinkCircles = root.selectAll(".sink")
      .data(data.sinks)
      .enter()
      .append("g")
        .attr("class", "sink")
        .attr("transform", function(d, i) {
          return "translate(" + (remotesMargin + i * 24) + ","  + (height - remotesMargin) + ")";
        })
        .append("circle");

    sinkCircles.attr("cx", "0").attr("cy", "0").attr("r", "10")
      .attr("stroke", function (d, i) {
        return (i == data.sinks.length - 1) ? "steelblue" : "white";
      })
      .attr("stroke-width", "2")
      .attr("fill", "steelblue")
      .on("mouseover", function (d, i) {
        sinkCircles.attr("stroke", "white");
        d3.select(this).attr("stroke", "steelblue");
        sinkText.text(d);
      });

    var sinkText = root
      .append("text")
        .attr("y", height - 25).attr("x", 24 + data.sinks.length * 24)
        .attr("class", "label sinkText")
        .text(function(d) { return data.sources[data.sinks.length - 1]; });

    rowsPerSecondOut
      .attr("y", height - 50).attr("x", 24 + data.sinks.length * 24)
      .attr("class", "rps")
      .text(stats.latestRpsOut() + " rps");

    rowsPerSecondOut.append("title").text("rows per second");

    this.update();
  };
}

var renderer = new StreamRenderer(stats);

r2d3.onRender(function(data, svg, width, height, options) {
  renderer.setSize(width, height);
  renderer.render();
});

r2d3.onResize(function(width, height) {
  renderer.setSize(width, height);
  renderer.render();
});

if (options.demo) {
  setInterval(function() {
    var data = {
      rps: {
        in: Math.floor(Math.random() * 100),
        out: Math.floor(Math.random() * 100)
      }
    };

    stats.add(data.rps);

    renderer.update();
  }, 1000);
}
<|endoftext|>


<commit_before>function setContextMenu(menu, event)
{
  var top = event.clientY + window.scrollY;
  var left = event.clientX;

  $('body').append(nunjucks.render('static/templates/context-menu.html', menu));
  var currentContextMenu = $('#context-menu');

  currentContextMenu.css({
    top: top,
    left: left
  });

  // Move focus to the context menu
  currentContextMenu[0].focus();

  currentContextMenu.on('mousedown', function(event) {
    // Prevent clicks on the menu to propagate
    // to the window, so that the menu is not
    // removed and links will be followed.
    event.stopPropagation();
  });
}

// Remove the menu when a user clicks outside it.
window.addEventListener('mousedown', function() {
  //toggleSymbolHighlights();
  $('#context-menu').remove();
}, false);

var hovered = $();

$("#file").on("mousemove", function(event) {
  if ($('#context-menu').length) {
    return;
  }

  var y = event.clientY;
  var x = event.clientX;

  var elt = document.elementFromPoint(x, y);
  while (!elt.hasAttribute("data-id")) {
    elt = elt.parentNode;
    if (!elt || !(elt instanceof Element)) {
      hovered.removeClass("hovered");
      hovered = $();
      return;
    }
  }

  elt = $(elt);
  var id = elt.attr("data-id");

  if (id == "?") {
    hovered.removeClass("hovered");
    hovered = $();
    return;
  }

  hovered.removeClass("hovered");
  hovered = $(`span[data-id="${id}"]`);
  hovered.addClass("hovered");
});

$("#file").on("click", "span[data-i]", function(event) {
  var elt = $(event.target);
  while (!elt.attr("data-i")) {
    elt = elt.parent();
  }
  var index = elt.attr("data-i");

  function fmt(s, data) {
    return s.replace("_", data);
  }

  // Comes from the generated page.
  var [jumps, searches] = ANALYSIS_DATA[index];

  var menuItems = [];

  for (var i = 0; i < jumps.length; i++) {
    var sym = jumps[i].sym;
    var pretty = jumps[i].pretty;
    menuItems.push({html: fmt("Goto definition of _", pretty),
                    href: "/mozilla-central/define?q=" + encodeURIComponent(sym),
                    icon: "search"});
  }

  for (var i = 0; i < searches.length; i++) {
    var sym = searches[i].sym;
    var pretty = searches[i].pretty;
    menuItems.push({html: fmt("Search for _", pretty),
                    href: "/mozilla-central/search?q=symbol:" + encodeURIComponent(sym),
                    icon: "search"});
  }

  setContextMenu({menuItems: menuItems}, event);
});
<commit_msg>Hide context menu after use<commit_after>function setContextMenu(menu, event)
{
  var top = event.clientY + window.scrollY;
  var left = event.clientX;

  $('body').append(nunjucks.render('static/templates/context-menu.html', menu));
  var currentContextMenu = $('#context-menu');

  currentContextMenu.css({
    top: top,
    left: left
  });

  // Move focus to the context menu
  currentContextMenu[0].focus();

  currentContextMenu.on('mousedown', function(event) {
    // Prevent clicks on the menu to propagate
    // to the window, so that the menu is not
    // removed and links will be followed.
    event.stopPropagation();
  });
}

// Remove the menu when a user clicks outside it.
window.addEventListener('mousedown', function() {
  $('#context-menu').remove();
}, false);

window.addEventListener("pageshow", function() {
  $('#context-menu').remove();
}, false);

var hovered = $();

$("#file").on("mousemove", function(event) {
  if ($('#context-menu').length) {
    return;
  }

  var y = event.clientY;
  var x = event.clientX;

  var elt = document.elementFromPoint(x, y);
  while (!elt.hasAttribute("data-id")) {
    elt = elt.parentNode;
    if (!elt || !(elt instanceof Element)) {
      hovered.removeClass("hovered");
      hovered = $();
      return;
    }
  }

  elt = $(elt);
  var id = elt.attr("data-id");

  if (id == "?") {
    hovered.removeClass("hovered");
    hovered = $();
    return;
  }

  hovered.removeClass("hovered");
  hovered = $(`span[data-id="${id}"]`);
  hovered.addClass("hovered");
});

$("#file").on("click", "span[data-i]", function(event) {
  var elt = $(event.target);
  while (!elt.attr("data-i")) {
    elt = elt.parent();
  }
  var index = elt.attr("data-i");

  function fmt(s, data) {
    return s.replace("_", data);
  }

  // Comes from the generated page.
  var [jumps, searches] = ANALYSIS_DATA[index];

  var menuItems = [];

  for (var i = 0; i < jumps.length; i++) {
    var sym = jumps[i].sym;
    var pretty = jumps[i].pretty;
    menuItems.push({html: fmt("Goto definition of _", pretty),
                    href: "/mozilla-central/define?q=" + encodeURIComponent(sym),
                    icon: "search"});
  }

  for (var i = 0; i < searches.length; i++) {
    var sym = searches[i].sym;
    var pretty = searches[i].pretty;
    menuItems.push({html: fmt("Search for _", pretty),
                    href: "/mozilla-central/search?q=symbol:" + encodeURIComponent(sym),
                    icon: "search"});
  }

  setContextMenu({menuItems: menuItems}, event);
});
<|endoftext|>