<commit_before>'use strict';

/**
 * @module ov.player
 */

(function(angular, app) {

  /**
   * Creates a Youtube player factory which observes OplPlayer interface.
   * More information on Youtube player can be found at https://developers.google.com/youtube/v3/docs/.
   * The Youtube embeded player exposes a JavaSript API to interact with
   * (https://developers.google.com/youtube/iframe_api_reference).
   *
   * @class OplYoutubePlayer
   */
  function OplYoutubePlayer(OplPlayer, $window, oplPlayerErrors) {

    /**
     * Handles all player post messages.
     *
     * @param {MessageEvent|Event} event The post message
     * @param {Object} data If post messages are not implemented, a simple
     * event can by used with data as second parameter instead of data
     * inside the MessageEvent object (event.data)
     */
    function handlePlayerEvents(event, data) {
      data = event.data ? JSON.parse(event.data) : data;

      // Events
      switch (data.event) {
        case 'infoDelivery':
          if (data.info.currentTime !== undefined && this.duration)
            this.jPlayerElement.triggerHandler('oplPlayProgress', {
              time: data.info.currentTime * 1000,
              percent: data.info.currentTime / this.duration * 100
            });

          if (data.info.videoLoadedFraction)
            this.jPlayerElement.triggerHandler('oplLoadProgress', {
              loadedStart: 0,
              loadedPercent: data.info.videoLoadedFraction * 100
            });
          break;

        case 'onStateChange':
          if (data.info != YT.PlayerState.PLAYING) {
            this.playing = 0;
            this.jPlayerElement.triggerHandler('oplPause');

            if (data.info == YT.PlayerState.ENDED) {
              this.jPlayerElement.triggerHandler('oplEnd');
            } else if (data.info == -1) {
              this.jPlayerElement.triggerHandler('oplReady');
            }
          } else {
            this.playing = 1;
            this.jPlayerElement.triggerHandler('oplPlay');

            if (this.requestPause) {
              this.requestPause = false;
              this.player.pauseVideo();
            }

            // As described in Youtube player API the video duration is only available when player have started
            // (https://developers.google.com/youtube/iframe_api_reference#getDuration)
            var duration = this.player.getDuration();
            if (!this.duration && duration) {
              this.duration = duration;
              this.jPlayerElement.triggerHandler('oplDurationChange', this.duration * 1000);
            }

          }
          break;

        default:
          break;
      }
    }

    // All Youtube player events
    var events = [
      'onStateChange',
      'infoDelivery',
      'infoDelivery',
      'onPlaybackQualityChange',
      'loadeddata',
      'progress',
      'playing',
      'ended',
      'timeupdate',
      'error'
    ];

    /**
     * Initializes the player.
     *
     * Youtube player uses postMessage API to be able to communicate with the player.
     * Before doing anything on the Youtube player the ready event must be handled.
     */
    function initialize() {
      if (this.ressourceLoaded && this.apiLoaded) {

        var self = this;
        this.loaded = false;

        this.player = new YT.Player(this.playerId, {
          playerVars: {
            fs: 0,
            rel: 0,
            autoplay: 0,
            html5: 1,
            theme: 'light',
            modesbranding: 1,
            color: 'white',
            iv_load_policy: 3,
            showinfo: 0,
            controls: 0,
            hl: this.media.language
          },
          events: {
            onReady: function() { // Youtube API is ready to be called

              // Handle post messages
              self.handlePlayerEventsFn = angular.bind(self, handlePlayerEvents);
              angular.element($window).on('message', self.handlePlayerEventsFn);

              // Set media events listeners
              var jPlayer = angular.element(self.player);
              for (var i = 0; i < events.length; i++)
                jPlayer.on(events[i], self.handlePlayerEventsFn);

              self.loaded = true;
              self.playing = 0;

              self.load();
            },
            onError: function(error) {
              switch (error.data) {
                case 100:
                case 101:
                case 150:
                  self.jPlayerElement.triggerHandler('oplError', oplPlayerErrors.MEDIA_ERR_PERMISSION);
                  break;
                default:
                  self.jPlayerElement.triggerHandler('oplError', oplPlayerErrors.MEDIA_ERR_UNKNOWN);
                  break;
              }
            }
          },
          videoId: this.media.mediaId[this.selectedSourceIndex]
        });
      }
    }

    /**
     * Creates a new YoutubePlayer.
     *
     * @constructor
     * @extends Player
     * @param {Object} jPlayerElement The JQLite HTML element corresponding to the element which will receive events
     * dispatched by the player
     * @param {Object} media Media to load
     * @param {Array} media.mediaId The list of media sources ids (for different viewpoint)
     * @param {String} media.language The language code for the Youtube player
     * @param {String} id The player id to use as the "id" attribute
     */
    function YoutubePlayer(jPlayerElement, media, id) {
      var self = this;
      var youtubeApiScriptId = 'youtube-iframe-api';
      OplPlayer.prototype.init.call(this, jPlayerElement, id);
      this.setMedia(media);
      this.requestPause = false;

      var tag = document.createElement('script');
      tag.setAttribute('id', youtubeApiScriptId);
      tag.src = 'https://www.youtube.com/iframe_api';

      $window.onYouTubeIframeAPIReady = function() {
        self.apiLoaded = true;
        initialize.call(self);
      };

      // Add youtube API script if not already included
      if (!document.getElementById(youtubeApiScriptId)) {
        var firstScriptTag = document.getElementsByTagName('script')[0];
        firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
      } else {
        this.apiLoaded = true;
        initialize.call(self);
      }
    }

    YoutubePlayer.prototype = new OplPlayer();
    YoutubePlayer.prototype.constructor = YoutubePlayer;

    /**
     * Gets media thumbnail.
     *
     * No need to manage the thumbnail it is already done by the Youtube player.
     *
     * @method getMediaThumbnail
     * @return {Null} null
     */
    YoutubePlayer.prototype.getMediaThumbnail = function() {
      return null;
    };

    /**
     * Intitializes the player.
     *
     * Nothing to do, the player is already initialized.
     *
     * @method initialize
     */
    YoutubePlayer.prototype.initialize = function() {
      this.ressourceLoaded = true;
      initialize.call(this);
    };

    /**
     * Plays or pauses the media depending on media actual state.
     *
     * @method playPause
     */
    YoutubePlayer.prototype.playPause = function() {
      if (this.player.getPlayerState() === YT.PlayerState.PLAYING)
        this.player.pauseVideo();
      else {
        this.requestPause = false;
        this.player.playVideo();
      }
    };

    /**
     * Sets volume.
     *
     * @method setVolume
     * @param {Number} volume The new volume from 0 to 100.
     */
    YoutubePlayer.prototype.setVolume = function(volume) {
      this.player.setVolume(volume);
    };

    /**
     * Sets time.
     *
     * @method setTime
     * @param {Number} time The time to seek to in milliseconds
     */
    YoutubePlayer.prototype.setTime = function(time) {
      var playerState = this.player.getPlayerState();
      time = parseInt(time) || 0;

      // Youtube workaround to deactivate autoplay.
      // Youtube seekTo method autoplay the video even if the video is in cued state with autoplay deactivated
      // as described in Youtube player API (https://developers.google.com/youtube/iframe_api_reference#seekTo).
      // Thus, if the video is in cued state, we have to wait for the video to start and then make a pause to have
      // the excpected behavior.
      if (!this.playing && playerState === YT.PlayerState.CUED)
        this.requestPause = true;

      this.player.seekTo(time / 1000, true);

      // Send a playProgress event because the Youtube flash player (old
      // browsers) does not trigger the playProgress event while in pause
      // as the HTML5 player does
      if (!this.playing)
        this.jPlayerElement.triggerHandler('oplPlayProgress', {
          time: time,
          percent: (time / this.duration) * 100
        });
    };

    /**
     * Gets player type.
     *
     * @method getPlayerType
     * @return {String} "youtube"
     */
    YoutubePlayer.prototype.getPlayerType = function() {
      return 'youtube';
    };

    /**
     * Tests if player actual state is pause.
     *
     * @method isPaused
     * @return {Boolean} true if paused, false otherwise
     */
    YoutubePlayer.prototype.isPaused = function() {
      return this.player.getPlayerState() === YT.PlayerState.PAUSED;
    };

    /**
     * Tests if player actual state is playing.
     *
     * @method isPlaying
     * @return {Boolean} true if playing, false otherwise
     */
    YoutubePlayer.prototype.isPlaying = function() {
      return this.player.getPlayerState() === YT.PlayerState.PLAYING;
    };

    /**
     * Loads player with selected source.
     *
     * @method load
     */
    YoutubePlayer.prototype.load = function() {
      this.player.cueVideoById(this.media.mediaId[this.selectedSourceIndex]);
    };

    /**
     * Gets media definitions.
     *
     * @method getAvailableDefinitions
     * @return {Array} The list of available definitions
     */
    YoutubePlayer.prototype.getAvailableDefinitions = function() {
      var qualities = [];
      if (!this.player) return qualities;

      var youtubeQualities = this.player.getAvailableQualityLevels();

      youtubeQualities.forEach(function(quality) {
        qualities.push({
          id: quality,
          label: quality,
          hd: (quality === 'hd720' || quality === 'hd1080' || quality === 'highres')
        });
      });

      return qualities;
    };

    /**
     * Sets current media.
     *
     * Also reset duration.
     *
     * @method setMedia
     * @param {Objet} media New media
     * @param {Array} media.mediaId The list of media sources ids (for different viewpoint)
     * @param {String} media.language The language code for the Youtube player
     */
    YoutubePlayer.prototype.setMedia = function(media) {
      OplPlayer.prototype.setMedia.call(this, media);

      // New media, duration has to be retrieved again
      this.duration = null;
    };

    /**
     * Gets current definition of the current source.
     *
     * @method getDefinition
     * @return {String} The definition id
     */
    YoutubePlayer.prototype.getDefinition = function() {
      if (!this.player) return null;
      return this.player.getPlaybackQuality();
    };

    /**
     * Changes definition of the current source.
     *
     * @method setDefinition
     * @param {String} id The definition id
     */
    YoutubePlayer.prototype.setDefinition = function(id) {
      this.player.setPlaybackQuality(id);
      this.jPlayerElement.triggerHandler('oplReady');
    };

    /**
     * Indicates that the Youtube player supports overlay play / pause button.
     *
     * @method isOverlayPlayPauseSupported
     * @return {Boolean} true
     */
    YoutubePlayer.prototype.isOverlayPlayPauseSupported = function() {
      return true;
    };

    /**
     * Destroys the player.
     *
     * Remove all events listeners.
     *
     * @method destroy
     */
    YoutubePlayer.prototype.destroy = function() {
      angular.element($window).off('message', this.handlePlayerEventsFn);
      this.player.destroy();
      this.loaded = false;
      this.playing = 0;
      this.player = null;
      this.duration = null;
    };

    return YoutubePlayer;
  }

  app.factory('OplYoutubePlayer', OplYoutubePlayer);
  OplYoutubePlayer.$inject = ['OplPlayer', '$window', 'oplPlayerErrors'];

})(angular, angular.module('ov.player'));
<commit_msg>Fix modestbranding Youtube option<commit_after>'use strict';

/**
 * @module ov.player
 */

(function(angular, app) {

  /**
   * Creates a Youtube player factory which observes OplPlayer interface.
   * More information on Youtube player can be found at https://developers.google.com/youtube/v3/docs/.
   * The Youtube embeded player exposes a JavaSript API to interact with
   * (https://developers.google.com/youtube/iframe_api_reference).
   *
   * @class OplYoutubePlayer
   */
  function OplYoutubePlayer(OplPlayer, $window, oplPlayerErrors) {

    /**
     * Handles all player post messages.
     *
     * @param {MessageEvent|Event} event The post message
     * @param {Object} data If post messages are not implemented, a simple
     * event can by used with data as second parameter instead of data
     * inside the MessageEvent object (event.data)
     */
    function handlePlayerEvents(event, data) {
      data = event.data ? JSON.parse(event.data) : data;

      // Events
      switch (data.event) {
        case 'infoDelivery':
          if (data.info.currentTime !== undefined && this.duration)
            this.jPlayerElement.triggerHandler('oplPlayProgress', {
              time: data.info.currentTime * 1000,
              percent: data.info.currentTime / this.duration * 100
            });

          if (data.info.videoLoadedFraction)
            this.jPlayerElement.triggerHandler('oplLoadProgress', {
              loadedStart: 0,
              loadedPercent: data.info.videoLoadedFraction * 100
            });
          break;

        case 'onStateChange':
          if (data.info != YT.PlayerState.PLAYING) {
            this.playing = 0;
            this.jPlayerElement.triggerHandler('oplPause');

            if (data.info == YT.PlayerState.ENDED) {
              this.jPlayerElement.triggerHandler('oplEnd');
            } else if (data.info == -1) {
              this.jPlayerElement.triggerHandler('oplReady');
            }
          } else {
            this.playing = 1;
            this.jPlayerElement.triggerHandler('oplPlay');

            if (this.requestPause) {
              this.requestPause = false;
              this.player.pauseVideo();
            }

            // As described in Youtube player API the video duration is only available when player have started
            // (https://developers.google.com/youtube/iframe_api_reference#getDuration)
            var duration = this.player.getDuration();
            if (!this.duration && duration) {
              this.duration = duration;
              this.jPlayerElement.triggerHandler('oplDurationChange', this.duration * 1000);
            }

          }
          break;

        default:
          break;
      }
    }

    // All Youtube player events
    var events = [
      'onStateChange',
      'infoDelivery',
      'infoDelivery',
      'onPlaybackQualityChange',
      'loadeddata',
      'progress',
      'playing',
      'ended',
      'timeupdate',
      'error'
    ];

    /**
     * Initializes the player.
     *
     * Youtube player uses postMessage API to be able to communicate with the player.
     * Before doing anything on the Youtube player the ready event must be handled.
     */
    function initialize() {
      if (this.ressourceLoaded && this.apiLoaded) {

        var self = this;
        this.loaded = false;

        this.player = new YT.Player(this.playerId, {
          playerVars: {
            fs: 0,
            rel: 0,
            autoplay: 0,
            html5: 1,
            theme: 'light',
            modestbranding: 1,
            color: 'white',
            iv_load_policy: 3,
            showinfo: 0,
            controls: 0,
            hl: this.media.language
          },
          events: {
            onReady: function() { // Youtube API is ready to be called

              // Handle post messages
              self.handlePlayerEventsFn = angular.bind(self, handlePlayerEvents);
              angular.element($window).on('message', self.handlePlayerEventsFn);

              // Set media events listeners
              var jPlayer = angular.element(self.player);
              for (var i = 0; i < events.length; i++)
                jPlayer.on(events[i], self.handlePlayerEventsFn);

              self.loaded = true;
              self.playing = 0;

              self.load();
            },
            onError: function(error) {
              switch (error.data) {
                case 100:
                case 101:
                case 150:
                  self.jPlayerElement.triggerHandler('oplError', oplPlayerErrors.MEDIA_ERR_PERMISSION);
                  break;
                default:
                  self.jPlayerElement.triggerHandler('oplError', oplPlayerErrors.MEDIA_ERR_UNKNOWN);
                  break;
              }
            }
          },
          videoId: this.media.mediaId[this.selectedSourceIndex]
        });
      }
    }

    /**
     * Creates a new YoutubePlayer.
     *
     * @constructor
     * @extends Player
     * @param {Object} jPlayerElement The JQLite HTML element corresponding to the element which will receive events
     * dispatched by the player
     * @param {Object} media Media to load
     * @param {Array} media.mediaId The list of media sources ids (for different viewpoint)
     * @param {String} media.language The language code for the Youtube player
     * @param {String} id The player id to use as the "id" attribute
     */
    function YoutubePlayer(jPlayerElement, media, id) {
      var self = this;
      var youtubeApiScriptId = 'youtube-iframe-api';
      OplPlayer.prototype.init.call(this, jPlayerElement, id);
      this.setMedia(media);
      this.requestPause = false;

      var tag = document.createElement('script');
      tag.setAttribute('id', youtubeApiScriptId);
      tag.src = 'https://www.youtube.com/iframe_api';

      $window.onYouTubeIframeAPIReady = function() {
        self.apiLoaded = true;
        initialize.call(self);
      };

      // Add youtube API script if not already included
      if (!document.getElementById(youtubeApiScriptId)) {
        var firstScriptTag = document.getElementsByTagName('script')[0];
        firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
      } else {
        this.apiLoaded = true;
        initialize.call(self);
      }
    }

    YoutubePlayer.prototype = new OplPlayer();
    YoutubePlayer.prototype.constructor = YoutubePlayer;

    /**
     * Gets media thumbnail.
     *
     * No need to manage the thumbnail it is already done by the Youtube player.
     *
     * @method getMediaThumbnail
     * @return {Null} null
     */
    YoutubePlayer.prototype.getMediaThumbnail = function() {
      return null;
    };

    /**
     * Intitializes the player.
     *
     * Nothing to do, the player is already initialized.
     *
     * @method initialize
     */
    YoutubePlayer.prototype.initialize = function() {
      this.ressourceLoaded = true;
      initialize.call(this);
    };

    /**
     * Plays or pauses the media depending on media actual state.
     *
     * @method playPause
     */
    YoutubePlayer.prototype.playPause = function() {
      if (this.player.getPlayerState() === YT.PlayerState.PLAYING)
        this.player.pauseVideo();
      else {
        this.requestPause = false;
        this.player.playVideo();
      }
    };

    /**
     * Sets volume.
     *
     * @method setVolume
     * @param {Number} volume The new volume from 0 to 100.
     */
    YoutubePlayer.prototype.setVolume = function(volume) {
      this.player.setVolume(volume);
    };

    /**
     * Sets time.
     *
     * @method setTime
     * @param {Number} time The time to seek to in milliseconds
     */
    YoutubePlayer.prototype.setTime = function(time) {
      var playerState = this.player.getPlayerState();
      time = parseInt(time) || 0;

      // Youtube workaround to deactivate autoplay.
      // Youtube seekTo method autoplay the video even if the video is in cued state with autoplay deactivated
      // as described in Youtube player API (https://developers.google.com/youtube/iframe_api_reference#seekTo).
      // Thus, if the video is in cued state, we have to wait for the video to start and then make a pause to have
      // the excpected behavior.
      if (!this.playing && playerState === YT.PlayerState.CUED)
        this.requestPause = true;

      this.player.seekTo(time / 1000, true);

      // Send a playProgress event because the Youtube flash player (old
      // browsers) does not trigger the playProgress event while in pause
      // as the HTML5 player does
      if (!this.playing)
        this.jPlayerElement.triggerHandler('oplPlayProgress', {
          time: time,
          percent: (time / this.duration) * 100
        });
    };

    /**
     * Gets player type.
     *
     * @method getPlayerType
     * @return {String} "youtube"
     */
    YoutubePlayer.prototype.getPlayerType = function() {
      return 'youtube';
    };

    /**
     * Tests if player actual state is pause.
     *
     * @method isPaused
     * @return {Boolean} true if paused, false otherwise
     */
    YoutubePlayer.prototype.isPaused = function() {
      return this.player.getPlayerState() === YT.PlayerState.PAUSED;
    };

    /**
     * Tests if player actual state is playing.
     *
     * @method isPlaying
     * @return {Boolean} true if playing, false otherwise
     */
    YoutubePlayer.prototype.isPlaying = function() {
      return this.player.getPlayerState() === YT.PlayerState.PLAYING;
    };

    /**
     * Loads player with selected source.
     *
     * @method load
     */
    YoutubePlayer.prototype.load = function() {
      this.player.cueVideoById(this.media.mediaId[this.selectedSourceIndex]);
    };

    /**
     * Gets media definitions.
     *
     * @method getAvailableDefinitions
     * @return {Array} The list of available definitions
     */
    YoutubePlayer.prototype.getAvailableDefinitions = function() {
      var qualities = [];
      if (!this.player) return qualities;

      var youtubeQualities = this.player.getAvailableQualityLevels();

      youtubeQualities.forEach(function(quality) {
        qualities.push({
          id: quality,
          label: quality,
          hd: (quality === 'hd720' || quality === 'hd1080' || quality === 'highres')
        });
      });

      return qualities;
    };

    /**
     * Sets current media.
     *
     * Also reset duration.
     *
     * @method setMedia
     * @param {Objet} media New media
     * @param {Array} media.mediaId The list of media sources ids (for different viewpoint)
     * @param {String} media.language The language code for the Youtube player
     */
    YoutubePlayer.prototype.setMedia = function(media) {
      OplPlayer.prototype.setMedia.call(this, media);

      // New media, duration has to be retrieved again
      this.duration = null;
    };

    /**
     * Gets current definition of the current source.
     *
     * @method getDefinition
     * @return {String} The definition id
     */
    YoutubePlayer.prototype.getDefinition = function() {
      if (!this.player) return null;
      return this.player.getPlaybackQuality();
    };

    /**
     * Changes definition of the current source.
     *
     * @method setDefinition
     * @param {String} id The definition id
     */
    YoutubePlayer.prototype.setDefinition = function(id) {
      this.player.setPlaybackQuality(id);
      this.jPlayerElement.triggerHandler('oplReady');
    };

    /**
     * Indicates that the Youtube player supports overlay play / pause button.
     *
     * @method isOverlayPlayPauseSupported
     * @return {Boolean} true
     */
    YoutubePlayer.prototype.isOverlayPlayPauseSupported = function() {
      return true;
    };

    /**
     * Destroys the player.
     *
     * Remove all events listeners.
     *
     * @method destroy
     */
    YoutubePlayer.prototype.destroy = function() {
      angular.element($window).off('message', this.handlePlayerEventsFn);
      this.player.destroy();
      this.loaded = false;
      this.playing = 0;
      this.player = null;
      this.duration = null;
    };

    return YoutubePlayer;
  }

  app.factory('OplYoutubePlayer', OplYoutubePlayer);
  OplYoutubePlayer.$inject = ['OplPlayer', '$window', 'oplPlayerErrors'];

})(angular, angular.module('ov.player'));
<|endoftext|>


<commit_before>'use strict';

//set configs
import config from './config.js';
global.Config = new config();

import path from 'path';
import express from 'express';
import signInRoutes from './routes/sign-in-routes.js';
import signOutRoutes from './routes/sign-out-routes.js';
import signUpRoutes from './routes/sign-up-routes.js';
import userRoutes from './routes/user-routes.js';
import passport from './middleware/passport.js';
import helmet from 'helmet';
import express_enforces_ssl from 'express-enforces-ssl';

var server = express();

// ======== *** VIEWS AND TEMPLATES ***

//Set port
server.set('port', (process.env.PORT || 5000));
server.set('views', path.join(__dirname, 'templates'));
server.set('view engine', 'jade');

//Setup location to static assets
server.use(express.static(path.join(__dirname)));

// ======== *** SECURITY MIDDLEWARE ***

//setup helmet js
server.use(helmet());

//setting CSP
var scriptSources = ["'self'", "'unsafe-inline'", "'unsafe-eval'", "ajax.googleapis.com", "www.google-analytics.com"];
var styleSources = ["'self'", "'unsafe-inline'", "ajax.googleapis.com"];
var connectSources = ["'self'"];
server.use(helmet.contentSecurityPolicy({
    defaultSrc: ["'self'"],
    scriptSrc: scriptSources,
    styleSrc: styleSources,
    connectSrc: connectSources,
    reportOnly: false,
    setAllHeaders: false,
    safari5: false
}));

//enforcing SSL for production environments
if(process.env.NODE_ENV == 'production'){
  server.use(express_enforces_ssl());
}

//passport setup
server.use(passport.initialize());
server.use(passport.session());

// ========= *** ROUTES ***
server.use('/auth', signInRoutes);
server.use('/auth', signOutRoutes);
server.use('/auth', signUpRoutes);
server.use('/', userRoutes);

// ========= *** SERVER LOAD ***

//Initial SPA load
server.get('/*', function (req, res) {
  res.render('index');
});

//Run up the server
server.listen(server.get('port'), function() {
  if (process.send) {
    process.send('online');
  } else {
    console.log('The server is running at http://localhost:' + server.get('port'));
  }
});
<commit_msg>comments<commit_after>'use strict';

//set configs
import config from './config.js';
global.Config = new config();

import path from 'path';
import express from 'express';
import signInRoutes from './routes/sign-in-routes.js';
import signOutRoutes from './routes/sign-out-routes.js';
import signUpRoutes from './routes/sign-up-routes.js';
import userRoutes from './routes/user-routes.js';
import passport from './middleware/passport.js';
import helmet from 'helmet';
import express_enforces_ssl from 'express-enforces-ssl';

var server = express();

// ======== *** VIEWS AND TEMPLATES ***

//Set port
server.set('port', (process.env.PORT || 5000));
server.set('views', path.join(__dirname, 'templates'));
server.set('view engine', 'jade');

//Setup location to static assets
server.use(express.static(path.join(__dirname)));

// ======== *** SECURITY MIDDLEWARE ***

//setup helmet js
server.use(helmet());

//setting CSP
var scriptSources = ["'self'", "'unsafe-inline'", "'unsafe-eval'", "ajax.googleapis.com", "www.google-analytics.com"];
var styleSources = ["'self'", "'unsafe-inline'", "ajax.googleapis.com"];
var connectSources = ["'self'"];
server.use(helmet.contentSecurityPolicy({
    defaultSrc: ["'self'"],
    scriptSrc: scriptSources,
    styleSrc: styleSources,
    connectSrc: connectSources,
    reportOnly: false,
    setAllHeaders: false,
    safari5: false
}));

//enforcing SSL for production environments
if(process.env.NODE_ENV == 'production'){
  server.use(express_enforces_ssl());
}

//passport setup
server.use(passport.initialize());
server.use(passport.session());

// ========= *** ROUTES ***
server.use('/auth', signInRoutes);
server.use('/auth', signOutRoutes);
server.use('/auth', signUpRoutes);
server.use('/', userRoutes);

// ========= *** SERVER LOAD ***

//Initial SPA load
server.get('/*', function (req, res) {
  res.render('index');
});

//Run up the server
server.listen(server.get('port'), function() {
  if (process.send) {
    process.send('online');
  } else {
    console.log('The server is running at http://localhost:' + server.get('port'));
  }
});
<|endoftext|>


<commit_before>/*
Copyright 2015, 2016 OpenMarket Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

'use strict';

var React = require('react');

var sdk = require('../../../index');
var dis = require('../../../dispatcher');
var Signup = require("../../../Signup");
var ServerConfig = require("../../views/login/ServerConfig");
var MatrixClientPeg = require("../../../MatrixClientPeg");
var RegistrationForm = require("../../views/login/RegistrationForm");
var CaptchaForm = require("../../views/login/CaptchaForm");

var MIN_PASSWORD_LENGTH = 6;

/**
 * TODO: It would be nice to make use of the InteractiveAuthEntryComponents
 * here, rather than inventing our own.
 */
module.exports = React.createClass({
    displayName: 'Registration',

    propTypes: {
        onLoggedIn: React.PropTypes.func.isRequired,
        clientSecret: React.PropTypes.string,
        sessionId: React.PropTypes.string,
        registrationUrl: React.PropTypes.string,
        idSid: React.PropTypes.string,
        customHsUrl: React.PropTypes.string,
        customIsUrl: React.PropTypes.string,
        defaultHsUrl: React.PropTypes.string,
        defaultIsUrl: React.PropTypes.string,
        brand: React.PropTypes.string,
        email: React.PropTypes.string,
        username: React.PropTypes.string,
        guestAccessToken: React.PropTypes.string,
        teamsConfig: React.PropTypes.shape({
            // Email address to request new teams
            supportEmail: React.PropTypes.string,
            teams: React.PropTypes.arrayOf(React.PropTypes.shape({
                // The displayed name of the team
                "name": React.PropTypes.string,
                // The suffix with which every team email address ends
                "emailSuffix": React.PropTypes.string,
            })).required,
        }),

        defaultDeviceDisplayName: React.PropTypes.string,

        // registration shouldn't know or care how login is done.
        onLoginClick: React.PropTypes.func.isRequired,
        onCancelClick: React.PropTypes.func
    },

    getInitialState: function() {
        return {
            busy: false,
            errorText: null,
            // We remember the values entered by the user because
            // the registration form will be unmounted during the
            // course of registration, but if there's an error we
            // want to bring back the registration form with the
            // values the user entered still in it. We can keep
            // them in this component's state since this component
            // persist for the duration of the registration process.
            formVals: {
                email: this.props.email,
            },
        };
    },

    componentWillMount: function() {
        this.dispatcherRef = dis.register(this.onAction);
        // attach this to the instance rather than this.state since it isn't UI
        this.registerLogic = new Signup.Register(
            this.props.customHsUrl, this.props.customIsUrl, {
                defaultDeviceDisplayName: this.props.defaultDeviceDisplayName,
            }
        );
        this.registerLogic.setClientSecret(this.props.clientSecret);
        this.registerLogic.setSessionId(this.props.sessionId);
        this.registerLogic.setRegistrationUrl(this.props.registrationUrl);
        this.registerLogic.setIdSid(this.props.idSid);
        this.registerLogic.setGuestAccessToken(this.props.guestAccessToken);
        this.registerLogic.recheckState();
    },

    componentWillUnmount: function() {
        dis.unregister(this.dispatcherRef);
    },

    componentDidMount: function() {
        // may have already done an HTTP hit (e.g. redirect from an email) so
        // check for any pending response
        var promise = this.registerLogic.getPromise();
        if (promise) {
            this.onProcessingRegistration(promise);
        }
    },

    onHsUrlChanged: function(newHsUrl) {
        this.registerLogic.setHomeserverUrl(newHsUrl);
    },

    onIsUrlChanged: function(newIsUrl) {
        this.registerLogic.setIdentityServerUrl(newIsUrl);
    },

    onAction: function(payload) {
        if (payload.action !== "registration_step_update") {
            return;
        }
        // If the registration state has changed, this means the
        // user now needs to do something. It would be better
        // to expose the explicitly in the register logic.
        this.setState({
            busy: false
        });
    },

    onFormSubmit: function(formVals) {
        var self = this;
        this.setState({
            errorText: "",
            busy: true,
            formVals: formVals,
        });

        if (formVals.username !== this.props.username) {
            // don't try to upgrade if we changed our username
            this.registerLogic.setGuestAccessToken(null);
        }

        this.onProcessingRegistration(this.registerLogic.register(formVals));
    },

    // Promise is resolved when the registration process is FULLY COMPLETE
    onProcessingRegistration: function(promise) {
        var self = this;
        promise.done(function(response) {
            self.setState({
                busy: false
            });
            if (!response || !response.access_token) {
                console.warn(
                    "FIXME: Register fulfilled without a final response, " +
                    "did you break the promise chain?"
                );
                // no matter, we'll grab it direct
                response = self.registerLogic.getCredentials();
            }
            if (!response || !response.user_id || !response.access_token) {
                console.error("Final response is missing keys.");
                self.setState({
                    errorText: "Registration failed on server"
                });
                return;
            }
            self.props.onLoggedIn({
                userId: response.user_id,
                deviceId: response.device_id,
                homeserverUrl: self.registerLogic.getHomeserverUrl(),
                identityServerUrl: self.registerLogic.getIdentityServerUrl(),
                accessToken: response.access_token
            });

            // Auto-join rooms
            if (self.props.teamsConfig) {
                for (let i = 0; i < self.props.teamsConfig.teams.length; i++) {
                    let team = self.props.teamsConfig.teams[i];
                    if (self.state.formVals.email.endsWith(team.emailSuffix)) {
                        console.log("User successfully registered with team " + team.name);
                        team.rooms.forEach((room) => {
                            if (room.autoJoin) {
                                console.log("Auto-joining " + room.id);
                                MatrixClientPeg.get().joinRoom(room.id);
                            }
                        });
                        break;
                    }
                }
            }

            if (self.props.brand) {
                MatrixClientPeg.get().getPushers().done((resp)=>{
                    var pushers = resp.pushers;
                    for (var i = 0; i < pushers.length; ++i) {
                        if (pushers[i].kind == 'email') {
                            var emailPusher = pushers[i];
                            emailPusher.data = { brand: self.props.brand };
                            MatrixClientPeg.get().setPusher(emailPusher).done(() => {
                                console.log("Set email branding to " + self.props.brand);
                            }, (error) => {
                                console.error("Couldn't set email branding: " + error);
                            });
                        }
                    }
                }, (error) => {
                    console.error("Couldn't get pushers: " + error);
                });
            }

        }, function(err) {
            if (err.message) {
                self.setState({
                    errorText: err.message
                });
            }
            self.setState({
                busy: false
            });
            console.log(err);
        });
    },

    onFormValidationFailed: function(errCode) {
        var errMsg;
        switch (errCode) {
            case "RegistrationForm.ERR_PASSWORD_MISSING":
                errMsg = "Missing password.";
                break;
            case "RegistrationForm.ERR_PASSWORD_MISMATCH":
                errMsg = "Passwords don't match.";
                break;
            case "RegistrationForm.ERR_PASSWORD_LENGTH":
                errMsg = `Password too short (min ${MIN_PASSWORD_LENGTH}).`;
                break;
            case "RegistrationForm.ERR_EMAIL_INVALID":
                errMsg = "This doesn't look like a valid email address";
                break;
            case "RegistrationForm.ERR_USERNAME_INVALID":
                errMsg = "User names may only contain letters, numbers, dots, hyphens and underscores.";
                break;
            case "RegistrationForm.ERR_USERNAME_BLANK":
                errMsg = "You need to enter a user name";
                break;
            default:
                console.error("Unknown error code: %s", errCode);
                errMsg = "An unknown error occurred.";
                break;
        }
        this.setState({
            errorText: errMsg
        });
    },

    onCaptchaResponse: function(response) {
        this.registerLogic.tellStage("m.login.recaptcha", {
            response: response
        });
    },

    _getRegisterContentJsx: function() {
        var currStep = this.registerLogic.getStep();
        var registerStep;
        switch (currStep) {
            case "Register.COMPLETE":
                break; // NOP
            case "Register.START":
            case "Register.STEP_m.login.dummy":
                // NB. Our 'username' prop is specifically for upgrading
                // a guest account
                registerStep = (
                    <RegistrationForm
                        showEmail={true}
                        defaultUsername={this.state.formVals.username}
                        defaultEmail={this.state.formVals.email}
                        defaultPassword={this.state.formVals.password}
                        teamsConfig={this.props.teamsConfig}
                        guestUsername={this.props.username}
                        minPasswordLength={MIN_PASSWORD_LENGTH}
                        onError={this.onFormValidationFailed}
                        onRegisterClick={this.onFormSubmit} />
                );
                break;
            case "Register.STEP_m.login.email.identity":
                registerStep = (

                        Please check your email to continue registration.

                );
                break;
            case "Register.STEP_m.login.recaptcha":
                var publicKey;
                var serverParams = this.registerLogic.getServerData().params;
                if (serverParams && serverParams["m.login.recaptcha"]) {
                    publicKey = serverParams["m.login.recaptcha"].public_key;
                }

                registerStep = (
                    <CaptchaForm sitePublicKey={publicKey}
                        onCaptchaResponse={this.onCaptchaResponse}
                    />
                );
                break;
            default:
                console.error("Unknown register state: %s", currStep);
                break;
        }
        var busySpinner;
        if (this.state.busy) {
            var Spinner = sdk.getComponent("elements.Spinner");
            busySpinner = (
                <Spinner />
            );
        }

        var returnToAppJsx;
        if (this.props.onCancelClick) {
            returnToAppJsx =

                    Return to app

        }

        return (

Create an account

                {registerStep}

{this.state.errorText}

                {busySpinner}
                <ServerConfig ref="serverConfig"
                    withToggleButton={ this.registerLogic.getStep() === "Register.START" }
                    customHsUrl={this.props.customHsUrl}
                    customIsUrl={this.props.customIsUrl}
                    defaultHsUrl={this.props.defaultHsUrl}
                    defaultIsUrl={this.props.defaultIsUrl}
                    onHsUrlChanged={this.onHsUrlChanged}
                    onIsUrlChanged={this.onIsUrlChanged}
                    delayTimeMs={1000} />

                    I already have an account

                { returnToAppJsx }

        );
    },

    render: function() {
        var LoginHeader = sdk.getComponent('login.LoginHeader');
        var LoginFooter = sdk.getComponent('login.LoginFooter');
        return (

                    <LoginHeader />
                    {this._getRegisterContentJsx()}
                    <LoginFooter />

        );
    }
});
<commit_msg>Update propTypes and do null checks<commit_after>/*
Copyright 2015, 2016 OpenMarket Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

'use strict';

var React = require('react');

var sdk = require('../../../index');
var dis = require('../../../dispatcher');
var Signup = require("../../../Signup");
var ServerConfig = require("../../views/login/ServerConfig");
var MatrixClientPeg = require("../../../MatrixClientPeg");
var RegistrationForm = require("../../views/login/RegistrationForm");
var CaptchaForm = require("../../views/login/CaptchaForm");

var MIN_PASSWORD_LENGTH = 6;

/**
 * TODO: It would be nice to make use of the InteractiveAuthEntryComponents
 * here, rather than inventing our own.
 */
module.exports = React.createClass({
    displayName: 'Registration',

    propTypes: {
        onLoggedIn: React.PropTypes.func.isRequired,
        clientSecret: React.PropTypes.string,
        sessionId: React.PropTypes.string,
        registrationUrl: React.PropTypes.string,
        idSid: React.PropTypes.string,
        customHsUrl: React.PropTypes.string,
        customIsUrl: React.PropTypes.string,
        defaultHsUrl: React.PropTypes.string,
        defaultIsUrl: React.PropTypes.string,
        brand: React.PropTypes.string,
        email: React.PropTypes.string,
        username: React.PropTypes.string,
        guestAccessToken: React.PropTypes.string,
        teamsConfig: React.PropTypes.shape({
            // Email address to request new teams
            supportEmail: React.PropTypes.string,
            teams: React.PropTypes.arrayOf(React.PropTypes.shape({
                // The displayed name of the team
                "name": React.PropTypes.string,
                // The suffix with which every team email address ends
                "emailSuffix": React.PropTypes.string,
                // The rooms to use during auto-join
                "rooms": React.PropTypes.arrayOf(React.PropTypes.shape({
                    "id": React.PropTypes.string,
                    "autoJoin": React.PropTypes.bool,
                })),
            })).required,
        }),

        defaultDeviceDisplayName: React.PropTypes.string,

        // registration shouldn't know or care how login is done.
        onLoginClick: React.PropTypes.func.isRequired,
        onCancelClick: React.PropTypes.func
    },

    getInitialState: function() {
        return {
            busy: false,
            errorText: null,
            // We remember the values entered by the user because
            // the registration form will be unmounted during the
            // course of registration, but if there's an error we
            // want to bring back the registration form with the
            // values the user entered still in it. We can keep
            // them in this component's state since this component
            // persist for the duration of the registration process.
            formVals: {
                email: this.props.email,
            },
        };
    },

    componentWillMount: function() {
        this.dispatcherRef = dis.register(this.onAction);
        // attach this to the instance rather than this.state since it isn't UI
        this.registerLogic = new Signup.Register(
            this.props.customHsUrl, this.props.customIsUrl, {
                defaultDeviceDisplayName: this.props.defaultDeviceDisplayName,
            }
        );
        this.registerLogic.setClientSecret(this.props.clientSecret);
        this.registerLogic.setSessionId(this.props.sessionId);
        this.registerLogic.setRegistrationUrl(this.props.registrationUrl);
        this.registerLogic.setIdSid(this.props.idSid);
        this.registerLogic.setGuestAccessToken(this.props.guestAccessToken);
        this.registerLogic.recheckState();
    },

    componentWillUnmount: function() {
        dis.unregister(this.dispatcherRef);
    },

    componentDidMount: function() {
        // may have already done an HTTP hit (e.g. redirect from an email) so
        // check for any pending response
        var promise = this.registerLogic.getPromise();
        if (promise) {
            this.onProcessingRegistration(promise);
        }
    },

    onHsUrlChanged: function(newHsUrl) {
        this.registerLogic.setHomeserverUrl(newHsUrl);
    },

    onIsUrlChanged: function(newIsUrl) {
        this.registerLogic.setIdentityServerUrl(newIsUrl);
    },

    onAction: function(payload) {
        if (payload.action !== "registration_step_update") {
            return;
        }
        // If the registration state has changed, this means the
        // user now needs to do something. It would be better
        // to expose the explicitly in the register logic.
        this.setState({
            busy: false
        });
    },

    onFormSubmit: function(formVals) {
        var self = this;
        this.setState({
            errorText: "",
            busy: true,
            formVals: formVals,
        });

        if (formVals.username !== this.props.username) {
            // don't try to upgrade if we changed our username
            this.registerLogic.setGuestAccessToken(null);
        }

        this.onProcessingRegistration(this.registerLogic.register(formVals));
    },

    // Promise is resolved when the registration process is FULLY COMPLETE
    onProcessingRegistration: function(promise) {
        var self = this;
        promise.done(function(response) {
            self.setState({
                busy: false
            });
            if (!response || !response.access_token) {
                console.warn(
                    "FIXME: Register fulfilled without a final response, " +
                    "did you break the promise chain?"
                );
                // no matter, we'll grab it direct
                response = self.registerLogic.getCredentials();
            }
            if (!response || !response.user_id || !response.access_token) {
                console.error("Final response is missing keys.");
                self.setState({
                    errorText: "Registration failed on server"
                });
                return;
            }
            self.props.onLoggedIn({
                userId: response.user_id,
                deviceId: response.device_id,
                homeserverUrl: self.registerLogic.getHomeserverUrl(),
                identityServerUrl: self.registerLogic.getIdentityServerUrl(),
                accessToken: response.access_token
            });

            // Auto-join rooms
            if (self.props.teamsConfig && self.props.teamsConfig.teams) {
                for (let i = 0; i < self.props.teamsConfig.teams.length; i++) {
                    let team = self.props.teamsConfig.teams[i];
                    if (self.state.formVals.email.endsWith(team.emailSuffix)) {
                        console.log("User successfully registered with team " + team.name);
                        if (!team.rooms) {
                            break;
                        }
                        team.rooms.forEach((room) => {
                            if (room.autoJoin) {
                                console.log("Auto-joining " + room.id);
                                MatrixClientPeg.get().joinRoom(room.id);
                            }
                        });
                        break;
                    }
                }
            }

            if (self.props.brand) {
                MatrixClientPeg.get().getPushers().done((resp)=>{
                    var pushers = resp.pushers;
                    for (var i = 0; i < pushers.length; ++i) {
                        if (pushers[i].kind == 'email') {
                            var emailPusher = pushers[i];
                            emailPusher.data = { brand: self.props.brand };
                            MatrixClientPeg.get().setPusher(emailPusher).done(() => {
                                console.log("Set email branding to " + self.props.brand);
                            }, (error) => {
                                console.error("Couldn't set email branding: " + error);
                            });
                        }
                    }
                }, (error) => {
                    console.error("Couldn't get pushers: " + error);
                });
            }

        }, function(err) {
            if (err.message) {
                self.setState({
                    errorText: err.message
                });
            }
            self.setState({
                busy: false
            });
            console.log(err);
        });
    },

    onFormValidationFailed: function(errCode) {
        var errMsg;
        switch (errCode) {
            case "RegistrationForm.ERR_PASSWORD_MISSING":
                errMsg = "Missing password.";
                break;
            case "RegistrationForm.ERR_PASSWORD_MISMATCH":
                errMsg = "Passwords don't match.";
                break;
            case "RegistrationForm.ERR_PASSWORD_LENGTH":
                errMsg = `Password too short (min ${MIN_PASSWORD_LENGTH}).`;
                break;
            case "RegistrationForm.ERR_EMAIL_INVALID":
                errMsg = "This doesn't look like a valid email address";
                break;
            case "RegistrationForm.ERR_USERNAME_INVALID":
                errMsg = "User names may only contain letters, numbers, dots, hyphens and underscores.";
                break;
            case "RegistrationForm.ERR_USERNAME_BLANK":
                errMsg = "You need to enter a user name";
                break;
            default:
                console.error("Unknown error code: %s", errCode);
                errMsg = "An unknown error occurred.";
                break;
        }
        this.setState({
            errorText: errMsg
        });
    },

    onCaptchaResponse: function(response) {
        this.registerLogic.tellStage("m.login.recaptcha", {
            response: response
        });
    },

    _getRegisterContentJsx: function() {
        var currStep = this.registerLogic.getStep();
        var registerStep;
        switch (currStep) {
            case "Register.COMPLETE":
                break; // NOP
            case "Register.START":
            case "Register.STEP_m.login.dummy":
                // NB. Our 'username' prop is specifically for upgrading
                // a guest account
                registerStep = (
                    <RegistrationForm
                        showEmail={true}
                        defaultUsername={this.state.formVals.username}
                        defaultEmail={this.state.formVals.email}
                        defaultPassword={this.state.formVals.password}
                        teamsConfig={this.props.teamsConfig}
                        guestUsername={this.props.username}
                        minPasswordLength={MIN_PASSWORD_LENGTH}
                        onError={this.onFormValidationFailed}
                        onRegisterClick={this.onFormSubmit} />
                );
                break;
            case "Register.STEP_m.login.email.identity":
                registerStep = (

                        Please check your email to continue registration.

                );
                break;
            case "Register.STEP_m.login.recaptcha":
                var publicKey;
                var serverParams = this.registerLogic.getServerData().params;
                if (serverParams && serverParams["m.login.recaptcha"]) {
                    publicKey = serverParams["m.login.recaptcha"].public_key;
                }

                registerStep = (
                    <CaptchaForm sitePublicKey={publicKey}
                        onCaptchaResponse={this.onCaptchaResponse}
                    />
                );
                break;
            default:
                console.error("Unknown register state: %s", currStep);
                break;
        }
        var busySpinner;
        if (this.state.busy) {
            var Spinner = sdk.getComponent("elements.Spinner");
            busySpinner = (
                <Spinner />
            );
        }

        var returnToAppJsx;
        if (this.props.onCancelClick) {
            returnToAppJsx =

                    Return to app

        }

        return (

Create an account

                {registerStep}

{this.state.errorText}

                {busySpinner}
                <ServerConfig ref="serverConfig"
                    withToggleButton={ this.registerLogic.getStep() === "Register.START" }
                    customHsUrl={this.props.customHsUrl}
                    customIsUrl={this.props.customIsUrl}
                    defaultHsUrl={this.props.defaultHsUrl}
                    defaultIsUrl={this.props.defaultIsUrl}
                    onHsUrlChanged={this.onHsUrlChanged}
                    onIsUrlChanged={this.onIsUrlChanged}
                    delayTimeMs={1000} />

                    I already have an account

                { returnToAppJsx }

        );
    },

    render: function() {
        var LoginHeader = sdk.getComponent('login.LoginHeader');
        var LoginFooter = sdk.getComponent('login.LoginFooter');
        return (

                    <LoginHeader />
                    {this._getRegisterContentJsx()}
                    <LoginFooter />

        );
    }
});
<|endoftext|>


<commit_before>import isNative from './isNative';

const longerTimeoutBrowsers = ['Edge', 'Trident', 'Firefox'];
let timeoutDuration = 0;
for (let i = 0; i < longerTimeoutBrowsers.length; i += 1) {
    if (navigator.userAgent.indexOf(longerTimeoutBrowsers[i]) >= 0) {
        timeoutDuration = 1;
        break;
    }
}

export function microtaskDebounce(fn) {
    let scheduled = false;
    let i = 0;
    const elem = document.createElement('span');

    // MutationObserver provides a mechanism for scheduling microtasks, which
    // are scheduled *before* the next task. This gives us a way to debounce
    // a function but ensure it's called *before* the next paint.
    const observer = new MutationObserver(() => {
        fn();
        scheduled = false;
    });

    observer.observe(elem, { attributes: true });

    return () => {
        if (!scheduled) {
            scheduled = true;
            elem.setAttribute('x-index', i);
            i = i + 1; // don't use compund (+=) because it doesn't get optimized in V8
        }
    };
}

export function taskDebounce(fn) {
    let scheduled = false;
    return () => {
        if (!scheduled) {
            scheduled = true;
            setTimeout(() => {
                scheduled = false;
                fn();
            }, timeoutDuration);
        }
    };
}

// It's common for MutationObserver polyfills to be seen in the wild, however
// these rely on Mutation Events which only occur when an element is connected
// to the DOM. The algorithm used in this module does not use a connected element,
// and so we must ensure that a *native* MutationObserver is available.
const supportsNativeMutationObserver = isNative(window.MutationObserver);

/**
* Create a debounced version of a method, that's asynchronously deferred
* but called in the minimum time possible.
*
* @method
* @memberof Popper.Utils
* @argument {Function} fn
* @returns {Function}
*/
export default supportsNativeMutationObserver ? microtaskDebounce : taskDebounce;
<commit_msg>[fix] Importing into universal apps (#166) (thank @HHogg)<commit_after>import isNative from './isNative';

const isBrowser = typeof window !== 'undefined';
const longerTimeoutBrowsers = ['Edge', 'Trident', 'Firefox'];
let timeoutDuration = 0;
for (let i = 0; i < longerTimeoutBrowsers.length; i += 1) {
    if (isBrowser && navigator.userAgent.indexOf(longerTimeoutBrowsers[i]) >= 0) {
        timeoutDuration = 1;
        break;
    }
}

export function microtaskDebounce(fn) {
    let scheduled = false;
    let i = 0;
    const elem = document.createElement('span');

    // MutationObserver provides a mechanism for scheduling microtasks, which
    // are scheduled *before* the next task. This gives us a way to debounce
    // a function but ensure it's called *before* the next paint.
    const observer = new MutationObserver(() => {
        fn();
        scheduled = false;
    });

    observer.observe(elem, { attributes: true });

    return () => {
        if (!scheduled) {
            scheduled = true;
            elem.setAttribute('x-index', i);
            i = i + 1; // don't use compund (+=) because it doesn't get optimized in V8
        }
    };
}

export function taskDebounce(fn) {
    let scheduled = false;
    return () => {
        if (!scheduled) {
            scheduled = true;
            setTimeout(() => {
                scheduled = false;
                fn();
            }, timeoutDuration);
        }
    };
}

// It's common for MutationObserver polyfills to be seen in the wild, however
// these rely on Mutation Events which only occur when an element is connected
// to the DOM. The algorithm used in this module does not use a connected element,
// and so we must ensure that a *native* MutationObserver is available.
const supportsNativeMutationObserver = isBrowser && isNative(window.MutationObserver);

/**
* Create a debounced version of a method, that's asynchronously deferred
* but called in the minimum time possible.
*
* @method
* @memberof Popper.Utils
* @argument {Function} fn
* @returns {Function}
*/
export default supportsNativeMutationObserver ? microtaskDebounce : taskDebounce;
<|endoftext|>


<commit_before>/**
 * @fileoverview Creates new issues after specified old issues are closed
 * @author Teddy Katz
 */

"use strict";

const moment = require("moment-timezone");

/**
 * Gets the desired issue body for a release issue, given the date of the release.
 * @param {Moment} releaseDate The date of the release, as Moment UTC date
 * @returns {Promise<string>} The text of the issue
 */
async function getReleaseIssueBody(releaseDate) {
    return `

The next scheduled release will occur on ${releaseDate.format("dddd, MMMM Do, YYYY")}.

## Release Day Checklist

- [ ] Remove the 'tsc agenda' label on this issue
- [ ] Review open pull requests and merge any that are [ready](https://eslint.org/docs/maintainer-guide/pullrequests#when-to-merge-a-pull-request)
- [ ] Start the release on [Jenkings](https://jenkins.eslint.org)
- [ ] Update the release blog post with a "Highlights" section
- [ ] Make a release announcement on Twitter
- [ ] Make a release announcement in the Discord '#announcements' channel
- [ ] Add a comment to this issue saying the release is out
- [ ] Add the 'patch release pending' label to this issue

## Two Days After Release Day Checklist

Typically Monday for regular releases; two days after patch releases.

- [ ] Check the issues list for any regression issues

## No Regressions Checklist

- [ ] Remove the 'patch release pending' label from this issue
- [ ] Close this issue

## Patch Release Checklist

- [ ] Resolve the regression by merging any necessary fixes
- [ ] Start the release on [Jenkings](https://jenkins.eslint.org)
- [ ] Update the release blog post with a "Highlights" section
- [ ] Make a release announcement on Twitter
- [ ] Make a release announcement in the Discord '#announcements' channel
- [ ] Add a comment to this issue saying the release is out
- [ ] Wait two days and repeat the Two Days After a Release checklist
- [ ] Close this issue

## Followup

Please use this issue to document how the release went, any problems during the release, and anything the team might want to know about the release process. This issue should be closed after all patch releases have been completed (or there was no patch release needed).

Resources:

* [Release guidelines](https://eslint.org/docs/maintainer-guide/releases)

`.trim();
}

/**
 * Gets the members of a particular team on GitHub
 * @param {GitHub} github A GitHub API client
 * @returns {{login: string, name: (string|null)}} A list of team member login names and full names
 */
async function getTeamMembers({ github, organizationName, teamName }) {

    /*
     * NOTE: This will fail if the organization contains more than 100 teams. This isn't
     * close to being a problem right now, so it hasn't been worth figuring out a good
     * way to paginate yet, but that would be a good enhancement in the future.
     */
    const teams = await github.teams.list({ org: organizationName, per_page: 100 }).then(res => res.data);
    const desiredTeam = teams.find(team => team.slug === teamName);

    if (!desiredTeam) {
        throw new Error(`No team with name ${teamName} found`);
    }

    const teamMembers = await github.teams.listMembers({ team_id: desiredTeam.id, per_page: 100 }).then(res => res.data);

    return Promise.all(teamMembers.map(async member => ({
        login: member.login,
        name: await github.users.getById({ id: member.id }).then(res => res.data.name)
    })));
}

/**
 * Formats a list of team members' names and GitHub usernames into a bulleted Markdown list
 * @param {{name: string, login: string}[]} teamMembers Information about team members
 * @returns {string} Markdown text containing a bulleted list of names
 */
function formatTeamMembers(teamMembers) {
    return teamMembers.map(({ login, name }) => `- ${name || login} (@${login}) - TSC`).join("\n");
}

/**
 * Gets the desired issue body for a release issue, given the date and of the meeting
 * @param {Moment} meetingDate The date and time when the meeting will take place, as a Moment date
 * @param {GitHub} github A GitHub API client
 * @returns {Promise<string>} The text of the issue
 */
async function getTscMeetingIssueBody({ meetingDate, github, organizationName, tscTeamName }) {
    const timeFormatString = "ddd DD-MMM-YYYY HH:mm";

    return `

# Time

UTC ${moment.utc(meetingDate).format(timeFormatString)}:
- Los Angeles: ${moment.tz(meetingDate, "America/Los_Angeles").format(timeFormatString)}
- Chicago: ${moment.tz(meetingDate, "America/Chicago").format(timeFormatString)}
- New York: ${moment.tz(meetingDate, "America/New_York").format(timeFormatString)}
- Madrid: ${moment.tz(meetingDate, "Europe/Madrid").format(timeFormatString)}
- Moscow: ${moment.tz(meetingDate, "Europe/Moscow").format(timeFormatString)}
- Tokyo: ${moment.tz(meetingDate, "Asia/Tokyo").format(timeFormatString)}
- Sydney: ${moment.tz(meetingDate, "Australia/Sydney").format(timeFormatString)}

# Location

https://eslint.org/chat/tsc-meetings

# Agenda

Extracted from:

* Issues and pull requests from the ESLint organization with the ["tsc agenda" label](https://github.com/issues?utf8=%E2%9C%93&q=org%3Aeslint+label%3A%22tsc+agenda%22)
* Comments on this issue

# Invited

${await getTeamMembers({ github, organizationName, teamName: tscTeamName }).then(formatTeamMembers)}

# Public participation

Anyone is welcome to attend the meeting as observers. We ask that you refrain from interrupting the meeting once it begins and only participate if invited to do so.

    `.trim();
}

/**
 * A function that determines whether an issue on GitHub was closed multiple times in the past.
 * @param {GitHub} github A GitHub API client
 * @param {Object} issueInfo information about the issue
 * @param {string} issueInfo.owner The owner of the repository
 * @param {string} issueInfo.repo The repo name
 * @param {number} issueInfo.number The issue number on GitHub
 * @returns {Promise<boolean>} A Promise that fulfills with `true` if the issue was closed multiple times
 */
async function issueWasClosedMultipleTimes(github, { owner, repo, number }) {
    const issueEvents = await github.issues.listEvents({
        owner,
        repo,
        number,
        per_page: 100
    }).then(res => res.data);

    return issueEvents.filter(eventObj => eventObj.event === "closed").length > 1;
}

/**
 * Creates a webhook handler that responds when an issue is closed for the first time
 * by creating a new issue.
 * @param {string} labelTrigger A label that the closed issue must have for this webhook
 * to run
 * @param {string[]} newLabels The labels that the newly-created issue should be given
 * @param {Function} shouldCreateNewIssue A function that accepts an object with `title` and
 * `body` properties as an argument, and returns a Promise for a boolean indicating whether
 * a new issue should be created. If the Promise fulfills with `false`, creating a new issue
 * will be cancelled.
 * @param {Function} getNewIssueInfo A function to get the title and body of the new issue.
 * Accepts a single parameter with `title` and `body` properties for the old issue, as well as
 * a `github` property containing a GitHub API client and an `organizationName` property containing
 * the name of the organization that owns the repo where the issue was filed. Returns a promise
 * for an object with `title` and `body` properties for the new issue.
 * @returns {function(probot.Context): Promise<void>} A Probot event listener
 */
function createIssueHandler({ labelTrigger, newLabels, shouldCreateNewIssue, getNewIssueInfo }) {
    return async context => {
        const { title: oldTitle, body: oldBody, labels: oldLabels } = context.payload.issue;

        // If the issue does not have the correct label, skip it.
        if (!oldLabels.some(label => label.name === labelTrigger)) {
            return;
        }

        // If the issue was previously closed and then reopened, skip it.
        if (await issueWasClosedMultipleTimes(context.github, context.issue())) {
            return;
        }

        if (!await shouldCreateNewIssue({ title: oldTitle, body: oldBody })) {
            return;
        }

        const { title: newTitle, body: newBody } = await getNewIssueInfo({
            title: oldTitle,
            body: oldBody,
            github: context.github,
            organizationName: context.repo().owner
        });

        // Create a new issue.
        await context.github.issues.create(
            context.repo({
                title: newTitle,
                body: newBody,
                labels: newLabels
            })
        );
    };
}

const RELEASE_ISSUE_TITLE_FORMAT = "[Scheduled release for ]MMMM Do, YYYY";
const releaseIssueHandler = createIssueHandler({
    labelTrigger: "release",
    newLabels: ["release", "tsc agenda"],
    async shouldCreateNewIssue({ title }) {
        return moment.utc(title, RELEASE_ISSUE_TITLE_FORMAT, true).isValid();
    },
    async getNewIssueInfo({ title }) {
        const oldReleaseDate = moment.utc(title, RELEASE_ISSUE_TITLE_FORMAT, true);
        const newReleaseDate = oldReleaseDate.clone().add({ weeks: 2 });

        return {
            title: newReleaseDate.format(RELEASE_ISSUE_TITLE_FORMAT),
            body: await getReleaseIssueBody(newReleaseDate)
        };
    }
});

const TSC_MEETING_TITLE_FORMAT = "[TSC meeting ]DD-MMMM-YYYY";
const tscMeetingIssueHandler = createIssueHandler({
    labelTrigger: "tsc meeting",
    newLabels: ["tsc meeting"],
    async shouldCreateNewIssue({ title }) {
        return moment.utc(title, TSC_MEETING_TITLE_FORMAT, true).isValid();
    },
    async getNewIssueInfo({ title, github, organizationName }) {
        const meetingDate = moment.tz(title, TSC_MEETING_TITLE_FORMAT, "America/New_York")
            .hour(16)
            .add({ weeks: 2 });

        const newTitle = meetingDate.format(TSC_MEETING_TITLE_FORMAT);
        const newBody = await getTscMeetingIssueBody({
            meetingDate,
            github,
            organizationName,
            tscTeamName: "eslint-tsc"
        });

        return { title: newTitle, body: newBody };
    }
});

module.exports = robot => {
    robot.on("issues.closed", releaseIssueHandler);
    robot.on("issues.closed", tscMeetingIssueHandler);
};
<commit_msg>Docs: Fix typo in release issue template (#135)<commit_after>/**
 * @fileoverview Creates new issues after specified old issues are closed
 * @author Teddy Katz
 */

"use strict";

const moment = require("moment-timezone");

/**
 * Gets the desired issue body for a release issue, given the date of the release.
 * @param {Moment} releaseDate The date of the release, as Moment UTC date
 * @returns {Promise<string>} The text of the issue
 */
async function getReleaseIssueBody(releaseDate) {
    return `

The next scheduled release will occur on ${releaseDate.format("dddd, MMMM Do, YYYY")}.

## Release Day Checklist

- [ ] Remove the 'tsc agenda' label on this issue
- [ ] Review open pull requests and merge any that are [ready](https://eslint.org/docs/maintainer-guide/pullrequests#when-to-merge-a-pull-request)
- [ ] Start the release on [Jenkins](https://jenkins.eslint.org)
- [ ] Update the release blog post with a "Highlights" section
- [ ] Make a release announcement on Twitter
- [ ] Make a release announcement in the Discord '#announcements' channel
- [ ] Add a comment to this issue saying the release is out
- [ ] Add the 'patch release pending' label to this issue

## Two Days After Release Day Checklist

Typically Monday for regular releases; two days after patch releases.

- [ ] Check the issues list for any regression issues

## No Regressions Checklist

- [ ] Remove the 'patch release pending' label from this issue
- [ ] Close this issue

## Patch Release Checklist

- [ ] Resolve the regression by merging any necessary fixes
- [ ] Start the release on [Jenkings](https://jenkins.eslint.org)
- [ ] Update the release blog post with a "Highlights" section
- [ ] Make a release announcement on Twitter
- [ ] Make a release announcement in the Discord '#announcements' channel
- [ ] Add a comment to this issue saying the release is out
- [ ] Wait two days and repeat the Two Days After a Release checklist
- [ ] Close this issue

## Followup

Please use this issue to document how the release went, any problems during the release, and anything the team might want to know about the release process. This issue should be closed after all patch releases have been completed (or there was no patch release needed).

Resources:

* [Release guidelines](https://eslint.org/docs/maintainer-guide/releases)

`.trim();
}

/**
 * Gets the members of a particular team on GitHub
 * @param {GitHub} github A GitHub API client
 * @returns {{login: string, name: (string|null)}} A list of team member login names and full names
 */
async function getTeamMembers({ github, organizationName, teamName }) {

    /*
     * NOTE: This will fail if the organization contains more than 100 teams. This isn't
     * close to being a problem right now, so it hasn't been worth figuring out a good
     * way to paginate yet, but that would be a good enhancement in the future.
     */
    const teams = await github.teams.list({ org: organizationName, per_page: 100 }).then(res => res.data);
    const desiredTeam = teams.find(team => team.slug === teamName);

    if (!desiredTeam) {
        throw new Error(`No team with name ${teamName} found`);
    }

    const teamMembers = await github.teams.listMembers({ team_id: desiredTeam.id, per_page: 100 }).then(res => res.data);

    return Promise.all(teamMembers.map(async member => ({
        login: member.login,
        name: await github.users.getById({ id: member.id }).then(res => res.data.name)
    })));
}

/**
 * Formats a list of team members' names and GitHub usernames into a bulleted Markdown list
 * @param {{name: string, login: string}[]} teamMembers Information about team members
 * @returns {string} Markdown text containing a bulleted list of names
 */
function formatTeamMembers(teamMembers) {
    return teamMembers.map(({ login, name }) => `- ${name || login} (@${login}) - TSC`).join("\n");
}

/**
 * Gets the desired issue body for a release issue, given the date and of the meeting
 * @param {Moment} meetingDate The date and time when the meeting will take place, as a Moment date
 * @param {GitHub} github A GitHub API client
 * @returns {Promise<string>} The text of the issue
 */
async function getTscMeetingIssueBody({ meetingDate, github, organizationName, tscTeamName }) {
    const timeFormatString = "ddd DD-MMM-YYYY HH:mm";

    return `

# Time

UTC ${moment.utc(meetingDate).format(timeFormatString)}:
- Los Angeles: ${moment.tz(meetingDate, "America/Los_Angeles").format(timeFormatString)}
- Chicago: ${moment.tz(meetingDate, "America/Chicago").format(timeFormatString)}
- New York: ${moment.tz(meetingDate, "America/New_York").format(timeFormatString)}
- Madrid: ${moment.tz(meetingDate, "Europe/Madrid").format(timeFormatString)}
- Moscow: ${moment.tz(meetingDate, "Europe/Moscow").format(timeFormatString)}
- Tokyo: ${moment.tz(meetingDate, "Asia/Tokyo").format(timeFormatString)}
- Sydney: ${moment.tz(meetingDate, "Australia/Sydney").format(timeFormatString)}

# Location

https://eslint.org/chat/tsc-meetings

# Agenda

Extracted from:

* Issues and pull requests from the ESLint organization with the ["tsc agenda" label](https://github.com/issues?utf8=%E2%9C%93&q=org%3Aeslint+label%3A%22tsc+agenda%22)
* Comments on this issue

# Invited

${await getTeamMembers({ github, organizationName, teamName: tscTeamName }).then(formatTeamMembers)}

# Public participation

Anyone is welcome to attend the meeting as observers. We ask that you refrain from interrupting the meeting once it begins and only participate if invited to do so.

    `.trim();
}

/**
 * A function that determines whether an issue on GitHub was closed multiple times in the past.
 * @param {GitHub} github A GitHub API client
 * @param {Object} issueInfo information about the issue
 * @param {string} issueInfo.owner The owner of the repository
 * @param {string} issueInfo.repo The repo name
 * @param {number} issueInfo.number The issue number on GitHub
 * @returns {Promise<boolean>} A Promise that fulfills with `true` if the issue was closed multiple times
 */
async function issueWasClosedMultipleTimes(github, { owner, repo, number }) {
    const issueEvents = await github.issues.listEvents({
        owner,
        repo,
        number,
        per_page: 100
    }).then(res => res.data);

    return issueEvents.filter(eventObj => eventObj.event === "closed").length > 1;
}

/**
 * Creates a webhook handler that responds when an issue is closed for the first time
 * by creating a new issue.
 * @param {string} labelTrigger A label that the closed issue must have for this webhook
 * to run
 * @param {string[]} newLabels The labels that the newly-created issue should be given
 * @param {Function} shouldCreateNewIssue A function that accepts an object with `title` and
 * `body` properties as an argument, and returns a Promise for a boolean indicating whether
 * a new issue should be created. If the Promise fulfills with `false`, creating a new issue
 * will be cancelled.
 * @param {Function} getNewIssueInfo A function to get the title and body of the new issue.
 * Accepts a single parameter with `title` and `body` properties for the old issue, as well as
 * a `github` property containing a GitHub API client and an `organizationName` property containing
 * the name of the organization that owns the repo where the issue was filed. Returns a promise
 * for an object with `title` and `body` properties for the new issue.
 * @returns {function(probot.Context): Promise<void>} A Probot event listener
 */
function createIssueHandler({ labelTrigger, newLabels, shouldCreateNewIssue, getNewIssueInfo }) {
    return async context => {
        const { title: oldTitle, body: oldBody, labels: oldLabels } = context.payload.issue;

        // If the issue does not have the correct label, skip it.
        if (!oldLabels.some(label => label.name === labelTrigger)) {
            return;
        }

        // If the issue was previously closed and then reopened, skip it.
        if (await issueWasClosedMultipleTimes(context.github, context.issue())) {
            return;
        }

        if (!await shouldCreateNewIssue({ title: oldTitle, body: oldBody })) {
            return;
        }

        const { title: newTitle, body: newBody } = await getNewIssueInfo({
            title: oldTitle,
            body: oldBody,
            github: context.github,
            organizationName: context.repo().owner
        });

        // Create a new issue.
        await context.github.issues.create(
            context.repo({
                title: newTitle,
                body: newBody,
                labels: newLabels
            })
        );
    };
}

const RELEASE_ISSUE_TITLE_FORMAT = "[Scheduled release for ]MMMM Do, YYYY";
const releaseIssueHandler = createIssueHandler({
    labelTrigger: "release",
    newLabels: ["release", "tsc agenda"],
    async shouldCreateNewIssue({ title }) {
        return moment.utc(title, RELEASE_ISSUE_TITLE_FORMAT, true).isValid();
    },
    async getNewIssueInfo({ title }) {
        const oldReleaseDate = moment.utc(title, RELEASE_ISSUE_TITLE_FORMAT, true);
        const newReleaseDate = oldReleaseDate.clone().add({ weeks: 2 });

        return {
            title: newReleaseDate.format(RELEASE_ISSUE_TITLE_FORMAT),
            body: await getReleaseIssueBody(newReleaseDate)
        };
    }
});

const TSC_MEETING_TITLE_FORMAT = "[TSC meeting ]DD-MMMM-YYYY";
const tscMeetingIssueHandler = createIssueHandler({
    labelTrigger: "tsc meeting",
    newLabels: ["tsc meeting"],
    async shouldCreateNewIssue({ title }) {
        return moment.utc(title, TSC_MEETING_TITLE_FORMAT, true).isValid();
    },
    async getNewIssueInfo({ title, github, organizationName }) {
        const meetingDate = moment.tz(title, TSC_MEETING_TITLE_FORMAT, "America/New_York")
            .hour(16)
            .add({ weeks: 2 });

        const newTitle = meetingDate.format(TSC_MEETING_TITLE_FORMAT);
        const newBody = await getTscMeetingIssueBody({
            meetingDate,
            github,
            organizationName,
            tscTeamName: "eslint-tsc"
        });

        return { title: newTitle, body: newBody };
    }
});

module.exports = robot => {
    robot.on("issues.closed", releaseIssueHandler);
    robot.on("issues.closed", tscMeetingIssueHandler);
};
<|endoftext|>


<commit_before>import Rollup from "../pages/rollup.page"

describe("Print rollup page", () => {
  beforeEach("Open the rollup page", () => {
    Rollup.open()
    Rollup.rollup.waitForExist()
    Rollup.rollup.waitForDisplayed()
  })

  describe("When clicking the print button, the daily rollup should be generated", () => {
    it("Should show the correct header for the rollup", () => {
      Rollup.printButton.waitForDisplayed(5000)
      Rollup.printButton.click()

      const handles = browser.getWindowHandles()
      const switchHandle = handles.pop()
      browser.switchToWindow(switchHandle)

      browser.waitUntil(
        () => {
          return (
            $("p:nth-of-type(1) i").getText() ===
            "Classification: DEMO USE ONLY"
          )
        },
        3000,
        "Expected classification"
      )
    })
  })
})
<commit_msg>Ensure we find the proper window handle after clicking Print<commit_after>import Rollup from "../pages/rollup.page"

describe("Print rollup page", () => {
  beforeEach("Open the rollup page", () => {
    Rollup.open()
    Rollup.rollup.waitForExist()
    Rollup.rollup.waitForDisplayed()
  })

  describe("When clicking the print button, the daily rollup should be generated", () => {
    it("Should show the correct header for the rollup", () => {
      Rollup.printButton.waitForDisplayed()
      Rollup.printButton.click()

      const currentHandle = browser.getWindowHandle()
      const handles = browser.getWindowHandles()
      // Find new window handle; NOTE: https://w3c.github.io/webdriver/#dfn-get-window-handles
      // "The order in which the window handles are returned is arbitrary."
      let switchHandle
      while ((switchHandle = handles.pop()) === currentHandle) {}
      browser.switchToWindow(switchHandle)

      browser.waitUntil(
        () => {
          return (
            $("p:nth-of-type(1) i").getText() ===
            "Classification: DEMO USE ONLY"
          )
        },
        3000,
        "Expected classification"
      )
      browser.closeWindow()
    })
  })
})
<|endoftext|>


<commit_before>var Key = require("./Key")
  , Attributes = require("./Attributes")
  , Update = require("./Update")

function Item(key, table, database) {
  Object.defineProperty(
    this, "database",
    {value: database, enumerable: false}
  )

  if (key) this.Key = Key(key)
  if (table) this.TableName = table
}

Item.prototype = {
  get: function(attrs) {
    this.AttributesToGet = attrs

    return this
  },

  update: function(key, value) {
    var update = new Update(this.TableName, this.database)
    update.Key = this.Key

    if (!key) return update

    if (typeof key == "function") {
      key.call(update, update)
      return update
    }

    return update.put(key, value)
  },

  fetch: function(opts, cb) {
    if (typeof opts == "function") cb = opts, opts = {}

    var self = this
      , attrs = {}
      , data = {
          TableName: this.TableName,
          Key: this.Key
        }

    if (opts.consistent) data.ConsistentRead = true

    this.database.request(
      "GetItem",
      data,
      function(err, data) {
        if (err || !data.Item) return cb(err)

        cb(null, Attributes.prototype.parse(data.Item))
      }
    )

    return this
  },

  destroy: function(cb) {
    this.database.request(
      "DeleteItem",
      {TableName: this.TableName, Key: this.Key},
      function(err, data) {
        if (err || !data.Item) return cb(err)

        cb(null, Attributes.prototype.parse(data.Item))
      }
    )
  }
}

module.exports = Item
<commit_msg>Add AttributesToGet in GetItem Request<commit_after>var Key = require("./Key")
  , Attributes = require("./Attributes")
  , Update = require("./Update")

function Item(key, table, database) {
  Object.defineProperty(
    this, "database",
    {value: database, enumerable: false}
  )

  if (key) this.Key = Key(key)
  if (table) this.TableName = table
}

Item.prototype = {
  get: function(attrs) {
    this.AttributesToGet = attrs

    return this
  },

  update: function(key, value) {
    var update = new Update(this.TableName, this.database)
    update.Key = this.Key

    if (!key) return update

    if (typeof key == "function") {
      key.call(update, update)
      return update
    }

    return update.put(key, value)
  },

  fetch: function(opts, cb) {
    if (typeof opts == "function") cb = opts, opts = {}

    var self = this
      , attrs = {}
      , data = {
          TableName: this.TableName,
          Key: this.Key
        }

    if (this.AttributesToGet) data.AttributesToGet = this.AttributesToGet

    if (opts.consistent) data.ConsistentRead = true

    this.database.request(
      "GetItem",
      data,
      function(err, data) {
        if (err || !data.Item) return cb(err)

        cb(null, Attributes.prototype.parse(data.Item))
      }
    )

    return this
  },

  destroy: function(cb) {
    this.database.request(
      "DeleteItem",
      {TableName: this.TableName, Key: this.Key},
      function(err, data) {
        if (err || !data.Item) return cb(err)

        cb(null, Attributes.prototype.parse(data.Item))
      }
    )
  }
}

module.exports = Item
<|endoftext|>


<commit_before>/*
Copyright 2016 OpenMarket Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

'use strict';

var React = require('react');
var ReactDOM = require('react-dom');

var sdk = require('../../../index');

var Velociraptor = require('../../../Velociraptor');
require('../../../VelocityBounce');
import { _t } from '../../../languageHandler';

import DateUtils from '../../../DateUtils';

var bounce = false;
try {
    if (global.localStorage) {
        bounce = global.localStorage.getItem('avatar_bounce') == 'true';
    }
} catch (e) {
}

module.exports = React.createClass({
    displayName: 'ReadReceiptMarker',

    propTypes: {
        // the RoomMember to show the RR for
        member: React.PropTypes.object.isRequired,

        // number of pixels to offset the avatar from the right of its parent;
        // typically a negative value.
        leftOffset: React.PropTypes.number,

        // true to hide the avatar (it will still be animated)
        hidden: React.PropTypes.bool,

        // don't animate this RR into position
        suppressAnimation: React.PropTypes.bool,

        // an opaque object for storing information about this user's RR in
        // this room
        readReceiptInfo: React.PropTypes.object,

        // A function which is used to check if the parent panel is being
        // unmounted, to avoid unnecessary work. Should return true if we
        // are being unmounted.
        checkUnmounting: React.PropTypes.func,

        // callback for clicks on this RR
        onClick: React.PropTypes.func,

        // Timestamp when the receipt was read
        timestamp: React.PropTypes.number,

        // True to show twelve hour format, false otherwise
        showTwelveHour: React.PropTypes.bool,
    },

    getDefaultProps: function() {
        return {
            leftOffset: 0,
        };
    },

    getInitialState: function() {
        // if we are going to animate the RR, we don't show it on first render,
        // and instead just add a placeholder to the DOM; once we've been
        // mounted, we start an animation which moves the RR from its old
        // position.
        return {
            suppressDisplay: !this.props.suppressAnimation,
        };
    },

    componentWillUnmount: function() {
        // before we remove the rr, store its location in the map, so that if
        // it reappears, it can be animated from the right place.
        var rrInfo = this.props.readReceiptInfo;
        if (!rrInfo) {
            return;
        }

        // checking the DOM properties can force a re-layout, which can be
        // quite expensive; so if the parent messagepanel is being unmounted,
        // then don't bother with this.
        if (this.props.checkUnmounting && this.props.checkUnmounting()) {
            return;
        }

        var avatarNode = ReactDOM.findDOMNode(this);
        rrInfo.top = avatarNode.offsetTop;
        rrInfo.left = avatarNode.offsetLeft;
        rrInfo.parent = avatarNode.offsetParent;
    },

    componentDidMount: function() {
        if (!this.state.suppressDisplay) {
            // we've already done our display - nothing more to do.
            return;
        }

        // treat new RRs as though they were off the top of the screen
        var oldTop = -15;

        var oldInfo = this.props.readReceiptInfo;
        if (oldInfo && oldInfo.parent) {
            oldTop = oldInfo.top + oldInfo.parent.getBoundingClientRect().top;
        }

        var newElement = ReactDOM.findDOMNode(this);
        var startTopOffset = oldTop - newElement.offsetParent.getBoundingClientRect().top;

        var startStyles = [];
        var enterTransitionOpts = [];

        if (oldInfo && oldInfo.left) {
            // start at the old height and in the old h pos

            var leftOffset = oldInfo.left;
            startStyles.push({ top: startTopOffset+"px",
                               left: oldInfo.left+"px" });

            var reorderTransitionOpts = {
                duration: 100,
                easing: 'easeOut'
            };

            enterTransitionOpts.push(reorderTransitionOpts);
        }

        // then shift to the rightmost column,
        // and then it will drop down to its resting position
        startStyles.push({ top: startTopOffset+'px', left: '0px' });
        enterTransitionOpts.push({
            duration: bounce ? Math.min(Math.log(Math.abs(startTopOffset)) * 200, 3000) : 300,
            easing: bounce ? 'easeOutBounce' : 'easeOutCubic',
        });

        this.setState({
            suppressDisplay: false,
            startStyles: startStyles,
            enterTransitionOpts: enterTransitionOpts,
        });
    },

    render: function() {
        var MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
        if (this.state.suppressDisplay) {
            return <div/>;
        }

        var style = {
            left: this.props.leftOffset+'px',
            top: '0px',
            visibility: this.props.hidden ? 'hidden' : 'visible',
        };

        let title;
        if (this.props.timestamp) {
            title = _t(
                "Seen by %(userName)s at %(dateTime)s",
                {userName: this.props.member.userId, dateTime: DateUtils.formatDate(new Date(this.props.timestamp), this.props.showTwelveHour)}
            );
        }

        return (
            <Velociraptor
                    startStyles={this.state.startStyles}
                    enterTransitionOpts={this.state.enterTransitionOpts} >
                <MemberAvatar
                    member={this.props.member}
                    aria-hidden="true"
                    width={14} height={14} resizeMethod="crop"
                    style={style}
                    title={title}
                    onClick={this.props.onClick}
                />
            </Velociraptor>
        );
    },
});
<commit_msg>Avoid breaking /sync with uncaught exceptions<commit_after>/*
Copyright 2016 OpenMarket Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

'use strict';

var React = require('react');
var ReactDOM = require('react-dom');

var sdk = require('../../../index');

var Velociraptor = require('../../../Velociraptor');
require('../../../VelocityBounce');
import { _t } from '../../../languageHandler';

import DateUtils from '../../../DateUtils';

var bounce = false;
try {
    if (global.localStorage) {
        bounce = global.localStorage.getItem('avatar_bounce') == 'true';
    }
} catch (e) {
}

module.exports = React.createClass({
    displayName: 'ReadReceiptMarker',

    propTypes: {
        // the RoomMember to show the RR for
        member: React.PropTypes.object.isRequired,

        // number of pixels to offset the avatar from the right of its parent;
        // typically a negative value.
        leftOffset: React.PropTypes.number,

        // true to hide the avatar (it will still be animated)
        hidden: React.PropTypes.bool,

        // don't animate this RR into position
        suppressAnimation: React.PropTypes.bool,

        // an opaque object for storing information about this user's RR in
        // this room
        readReceiptInfo: React.PropTypes.object,

        // A function which is used to check if the parent panel is being
        // unmounted, to avoid unnecessary work. Should return true if we
        // are being unmounted.
        checkUnmounting: React.PropTypes.func,

        // callback for clicks on this RR
        onClick: React.PropTypes.func,

        // Timestamp when the receipt was read
        timestamp: React.PropTypes.number,

        // True to show twelve hour format, false otherwise
        showTwelveHour: React.PropTypes.bool,
    },

    getDefaultProps: function() {
        return {
            leftOffset: 0,
        };
    },

    getInitialState: function() {
        // if we are going to animate the RR, we don't show it on first render,
        // and instead just add a placeholder to the DOM; once we've been
        // mounted, we start an animation which moves the RR from its old
        // position.
        return {
            suppressDisplay: !this.props.suppressAnimation,
        };
    },

    componentWillUnmount: function() {
        // before we remove the rr, store its location in the map, so that if
        // it reappears, it can be animated from the right place.
        var rrInfo = this.props.readReceiptInfo;
        if (!rrInfo) {
            return;
        }

        // checking the DOM properties can force a re-layout, which can be
        // quite expensive; so if the parent messagepanel is being unmounted,
        // then don't bother with this.
        if (this.props.checkUnmounting && this.props.checkUnmounting()) {
            return;
        }

        var avatarNode = ReactDOM.findDOMNode(this);
        rrInfo.top = avatarNode.offsetTop;
        rrInfo.left = avatarNode.offsetLeft;
        rrInfo.parent = avatarNode.offsetParent;
    },

    componentDidMount: function() {
        if (!this.state.suppressDisplay) {
            // we've already done our display - nothing more to do.
            return;
        }

        // treat new RRs as though they were off the top of the screen
        var oldTop = -15;

        var oldInfo = this.props.readReceiptInfo;
        if (oldInfo && oldInfo.parent) {
            oldTop = oldInfo.top + oldInfo.parent.getBoundingClientRect().top;
        }

        var newElement = ReactDOM.findDOMNode(this);
        let startTopOffset;
        if (!newElement.offsetParent) {
            // this seems to happen sometimes for reasons I don't understand
            // the docs for `offsetParent` say it may be null if `display` is
            // `none`, but I can't see why that would happen.
            console.warn(
                `ReadReceiptMarker for ${this.props.member.userId} in ` +
                `${this.props.member.roomId} has no offsetParent`,
            );
            startTopOffset = 0;
        } else {
            startTopOffset = oldTop - newElement.offsetParent.getBoundingClientRect().top;
        }

        var startStyles = [];
        var enterTransitionOpts = [];

        if (oldInfo && oldInfo.left) {
            // start at the old height and in the old h pos

            startStyles.push({ top: startTopOffset+"px",
                               left: oldInfo.left+"px" });

            var reorderTransitionOpts = {
                duration: 100,
                easing: 'easeOut',
            };

            enterTransitionOpts.push(reorderTransitionOpts);
        }

        // then shift to the rightmost column,
        // and then it will drop down to its resting position
        startStyles.push({ top: startTopOffset+'px', left: '0px' });
        enterTransitionOpts.push({
            duration: bounce ? Math.min(Math.log(Math.abs(startTopOffset)) * 200, 3000) : 300,
            easing: bounce ? 'easeOutBounce' : 'easeOutCubic',
        });

        this.setState({
            suppressDisplay: false,
            startStyles: startStyles,
            enterTransitionOpts: enterTransitionOpts,
        });
    },

    render: function() {
        var MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
        if (this.state.suppressDisplay) {
            return <div/>;
        }

        var style = {
            left: this.props.leftOffset+'px',
            top: '0px',
            visibility: this.props.hidden ? 'hidden' : 'visible',
        };

        let title;
        if (this.props.timestamp) {
            title = _t(
                "Seen by %(userName)s at %(dateTime)s",
                {userName: this.props.member.userId, dateTime: DateUtils.formatDate(new Date(this.props.timestamp), this.props.showTwelveHour)},
            );
        }

        return (
            <Velociraptor
                    startStyles={this.state.startStyles}
                    enterTransitionOpts={this.state.enterTransitionOpts} >
                <MemberAvatar
                    member={this.props.member}
                    aria-hidden="true"
                    width={14} height={14} resizeMethod="crop"
                    style={style}
                    title={title}
                    onClick={this.props.onClick}
                />
            </Velociraptor>
        );
    },
});
<|endoftext|>