<commit_before>'use strict';

angular.module('sumaAnalysis')
  .directive('sumaTimeSeriesChart', function () {
    var TimeSeries = function () {
      var margin,   // Margin of svg canvas
        margin2,    // Margin of scroll/zoom chart
        width,      // Width of Canvas
        height,     // Height of primary area chart
        height2;    // Height of scroll/zoom chart

      // Set Margins
      margin  = {top: 10, right: 10, bottom: 100, left: 40};
      margin2 = {top: 330, right: 10, bottom: 20, left: 40};
      width   = 940 - margin.left - margin.right;
      height  = 400 - margin.top - margin.bottom;
      height2 = 400 - margin2.top - margin2.bottom;

      function setColor(value) {
        if (value === null || value === undefined) {
          return 'darkred';
        }

        return 'steelblue';
      }

      function chart(selection) {
          var x,          // Scale of x-axis of primary area chart
              x2,         // Scale of x-axis of scroll/zoom chart
              y,          // Scale of y-axis of primary area chart
              y2,         // Scale of y-axis of scroll/zoom chart
              xAxis,      // x-axis of primary area chart
              xAxis2,     // x-axis of scroll/zoom chart
              yAxis,      // y-axis for both charts
              area,       // Area setup for primary chart
              area2,      // Area setup for scroll/zoom chart
              formatDate, // Create date object with specified format
              daysOfWeek; // Array of weekday names for legend

          // Create scales
          x  = d3.scaleTime().range([0, width]);
          x2 = d3.scaleTime().range([0, width]);
          y  = d3.scaleLinear().range([height, 10]); // Change second value to 10 to add padding at top of chart
          y2 = d3.scaleLinear().range([height2, 0]);

          // Create axes using scales
          xAxis = d3.axisBottom(x);     //xAxis  = d3.svg.axis().scale(x).orient('bottom');
          xAxis2 = d3.axisBottom(x2);   //xAxis2 = d3.svg.axis().scale(x2).orient('bottom');
          yAxis = d3.axisLeft(y);       //yAxis  = d3.svg.axis().scale(y).orient('left');

          // Create primary area path
          area = d3.svg.area()
            .interpolate('linear')
            .x(function (d) {return x(d.fDate); })
            .y0(height)
            .y1(function (d) {return y(d.count); });

          // Create scroll/zoom path
          area2 = d3.svg.area()
            .interpolate('linear')
            .x(function (d) {return x2(d.fDate); })
            .y0(height2)
            .y1(function (d) {return y2(d.count); });

          // Date formatter
          formatDate = d3.time.format('%Y-%m-%d');

          // Days of the week for display
          daysOfWeek = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];

          selection.each(function (data) {
            var brush,          // Brush, or selector, object for controlling scroll/zoom chart
                context,        // SVG container for scroll/zoom chart
                dateMap,        // Array of sorted date values, used during interaction.on()
                focus,          // SVG container for primary area chart
                interaction,    // SVG container for interaction layer (detects mouse position)
                legend,         // SVG container for chart legend
                svg;            // Principle SVG element for entire chart

            // Path to display handles on brush
            function resizePath(d) {
              var e = +(d === 'e'),
                  x = e ? 1 : -1,
                  y = height2 / 3;

              return 'M' + (0.5 * x) + ',' + y +
                'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6) +
                'V' + (2 * y - 6) +
                'A6,6 0 0 ' + e + ' ' + (0.5 * x) + ',' + (2 * y) +
                'Z' +
                'M' + (2.5 * x) + ',' + (y + 8) +
                'V' + (2 * y - 8) +
                'M' + (4.5 * x) + ',' + (y + 8) +
                'V' + (2 * y - 8);
            }

            // Function called by brush event handler
            function chartBrush() {
              x.domain(brush.empty() ? x2.domain() : brush.extent());

              focus.select('path').attr('d', area);
              focus.select('.x.axis').call(xAxis);
              focus.selectAll('.dot')
                .attr('cx', function (d) {return x(d.fDate); })
                .attr('cy', function (d) {return y(d.count); });
            }

            data.forEach(function (d) {
              d.title = d.date;
              d.fDate  = formatDate.parse(d.date);
              d.day   = daysOfWeek[d.fDate.getDay()];
              d.count = d.count;
            });

            // Set domains and ranges
            x.domain(d3.extent(data.map(function (d) {return d.fDate; })));
            y.domain([0, d3.max(data.map(function (d) {return d.count; }))]);
            x2.domain(x.domain());
            y2.domain(y.domain());

            // Create array of date objects using dates in dataset (used during interaction.on())
            dateMap = data.map(function (d) {return d.fDate; });

            brush = d3.svg.brush()
              .x(x2)
              .on('brush', chartBrush);

            svg = d3.select(this).append('svg')
              .attr('width', width + margin.left + margin.right)
              .attr('height', height + margin.top + margin.bottom);

            svg.append('defs').append('clipPath')
              .attr('id', 'clip')
              .append('rect')
              .attr('width', width)
              .attr('height', height);

            svg.append('defs').append('clipPath')
              .attr('id', 'dot-clip')
              .append('rect')
              .attr('x', -5)
              .attr('width', width + 10)
              .attr('height', height + 10);

            // Focus is the main graph
            focus = svg.append('g')
              .attr('class', 'main-graph')
              .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

            focus.append('path')
              .data([data])
              .attr('clip-path', 'url(#clip)')
              .attr('fill', 'steelblue')
              .attr('d', area);

            focus.append('g')
              .attr('class', 'x axis')
              .attr('transform', 'translate(0,' + height + ')')
              .call(xAxis);

            focus.append('g')
              .attr('class', 'y axis')
              .call(yAxis);

            focus.append('g')
              .selectAll('.dot')
              .data(data)
              .enter().append('circle')
              .attr('clip-path', 'url(#dot-clip)')
              .attr('class', 'dot')
              .attr('cx', function (d) {return x(d.fDate); })
              .attr('cy', function (d) {return y(d.count); })
              .attr('r', 5)
              .attr('fill', function (d) {return setColor(d.count); })
              .attr('opacity', 0)
              .attr('data-tooltip', function (d) {return d.title + ' : ' + d.count; });

            // Interaction is an overlay layer for mouse position
            interaction = svg.append('g')
              .attr('id', 'interaction')
              .append('rect')
              .attr('opacity', 0)
              .attr('x', margin.left)
              .attr('y', margin.top)
              .attr('height', height)
              .attr('width', width)
              .on('mousemove', function () {
                  var xCoord      = d3.mouse(this)[0] - margin.left,  // X coordinate of mouse over primary graph
                      xInvert     = x.invert(xCoord),                 // Convert xCoord to date value using x scale
                      cut         = d3.bisectLeft(dateMap, xInvert),  // Return index to right of xInvert
                      cut2        = (cut > 0) ? cut - 1 : 0,          // Return index to left of xInvert
                      leftPoint   = data[cut2].fDate,                  // Convert cut2 to date (milliseconds from Epoch)
                      rightPoint  = data[cut].fDate,                   // Convert cut to date (milliseconds from Epoch)
                      midpoint    = (leftPoint.getTime() + rightPoint.getTime()) / 2; // Calculate midpoint

                  // Is current postion less than the midpoint?
                  if (xInvert.getTime() < midpoint) {
                    cut -= 1;
                  }

                  // Display closest dot
                  d3.selectAll('.dot')
                    .attr('opacity', 0)
                    .filter(function (d) {return d.fDate === data[cut].fDate; })
                    .attr('opacity', 1);

                  // Display legend
                  d3.select('#legend')
                    .attr('opacity', 1);

                  // Update legend text
                  d3.select('#legend-text')
                    .text(function () {
                      var val;

                      if (data[cut].count === null) {
                        val = 'No Data Found';
                      } else {
                        val = data[cut].count;
                      }

                      return data[cut].day + ' : ' + data[cut].title + ' : ' + val;
                    });

                  d3.select('#legend-circle')
                    .attr('fill', function () {return setColor(data[cut].count); });
                })
              .on('mouseout', function () {
                d3.selectAll('.dot')
                  .attr('opacity', 0);

                d3.select('#legend')
                  .attr('opacity', 0);
              });

            // Legend display
            legend = svg.append('g')
              .attr('id', 'legend')
              .attr('opacity', 0);

            legend.append('svg:circle')
              .attr('id', 'legend-circle')
              .attr('cx', 60)
              .attr('cy', 10)
              .attr('r', 5);

            legend.append('svg:text')
              .attr('id', 'legend-text')
              .attr('x', 70)
              .attr('y', 13);

            // Context is the 'brush' graph
            context = svg.append('g')
              .attr('class', 'sub-graph')
              .attr('transform', 'translate(' + margin2.left + ',' + margin2.top + ')');

            context.append('path')
              .data([data])
              .attr('fill', 'steelblue')
              .attr('d', area2);

            context.append('g')
              .attr('class', 'x axis')
              .attr('transform', 'translate(0,' + height2 + ')')
              .call(xAxis2);

            context.append('g')
              .attr('class', 'x brush')
              .call(brush)
              .selectAll('rect')
              .attr('y', -6)
              .attr('height', height2 + 7);

            context.selectAll('.resize')
              .append('path')
              .attr('d', resizePath);

            // Axis/Tick Stylings
            d3.selectAll('.axis path')
              .attr('fill', 'none')
              .attr('stroke', '#000');
          });
        }
      // Accessor method to customize width
      chart.width = function (value) {
        if (!arguments.length) {
          return width;
        }

        width = value - margin.left - margin.right;
        return chart;
      };
      // TODO Add additional accessors for other properties

      return chart;
    };

    return {
      restrict: 'A',
      scope: {data: '='},
      link: function postLink(scope, element, attrs) {
        var chart = new TimeSeries();

        scope.render = function (data) {
          if (data.data.length > 1) {
            d3.select(element[0]).select('svg, div').remove();
            d3.select(element[0])
              .datum(data.data)
              .call(chart);
          } else {
            d3.select(element[0]).select('svg, div').remove();
            d3.select(element[0])
              .append('div')
              .attr('class', 'alert alert-warning alert-block')
              .html('
Notice!

Limited data is available for this query.

Message: Not enough data was found to display a time series. However, summary data is shown below.
');
          }
        };

        scope.$watch('data', function (newData) {
          if (!newData) {
            return;
          }

          scope.render(newData);
        });
      }
    };
  });
<commit_msg>Modified areas in sumaTimeSeriesCharts.js<commit_after>'use strict';

angular.module('sumaAnalysis')
  .directive('sumaTimeSeriesChart', function () {
    var TimeSeries = function () {
      var margin,   // Margin of svg canvas
        margin2,    // Margin of scroll/zoom chart
        width,      // Width of Canvas
        height,     // Height of primary area chart
        height2;    // Height of scroll/zoom chart

      // Set Margins
      margin  = {top: 10, right: 10, bottom: 100, left: 40};
      margin2 = {top: 330, right: 10, bottom: 20, left: 40};
      width   = 940 - margin.left - margin.right;
      height  = 400 - margin.top - margin.bottom;
      height2 = 400 - margin2.top - margin2.bottom;

      function setColor(value) {
        if (value === null || value === undefined) {
          return 'darkred';
        }

        return 'steelblue';
      }

      function chart(selection) {
          var x,          // Scale of x-axis of primary area chart
              x2,         // Scale of x-axis of scroll/zoom chart
              y,          // Scale of y-axis of primary area chart
              y2,         // Scale of y-axis of scroll/zoom chart
              xAxis,      // x-axis of primary area chart
              xAxis2,     // x-axis of scroll/zoom chart
              yAxis,      // y-axis for both charts
              area,       // Area setup for primary chart
              area2,      // Area setup for scroll/zoom chart
              formatDate, // Create date object with specified format
              daysOfWeek; // Array of weekday names for legend

          // Create scales
          x  = d3.scaleTime().range([0, width]);
          x2 = d3.scaleTime().range([0, width]);
          y  = d3.scaleLinear().range([height, 10]); // Change second value to 10 to add padding at top of chart
          y2 = d3.scaleLinear().range([height2, 0]);

          // Create axes using scales
          xAxis = d3.axisBottom(x);
          xAxis2 = d3.axisBottom(x2);
          yAxis = d3.axisLeft(y);

          // Create primary area path
          area = d3.area()
            .x(function (d) {return x(d.fDate); })
            .y0(height)
            .y1(function (d) {return y(d.count); });

          // Create scroll/zoom path
          area2 = d3.area()
            .x(function (d) {return x2(d.fDate); })
            .y0(height2)
            .y1(function (d) {return y2(d.count); });

          // Date formatter
          formatDate = d3.time.format('%Y-%m-%d');

          // Days of the week for display
          daysOfWeek = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];

          selection.each(function (data) {
            var brush,          // Brush, or selector, object for controlling scroll/zoom chart
                context,        // SVG container for scroll/zoom chart
                dateMap,        // Array of sorted date values, used during interaction.on()
                focus,          // SVG container for primary area chart
                interaction,    // SVG container for interaction layer (detects mouse position)
                legend,         // SVG container for chart legend
                svg;            // Principle SVG element for entire chart

            // Path to display handles on brush
            function resizePath(d) {
              var e = +(d === 'e'),
                  x = e ? 1 : -1,
                  y = height2 / 3;

              return 'M' + (0.5 * x) + ',' + y +
                'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6) +
                'V' + (2 * y - 6) +
                'A6,6 0 0 ' + e + ' ' + (0.5 * x) + ',' + (2 * y) +
                'Z' +
                'M' + (2.5 * x) + ',' + (y + 8) +
                'V' + (2 * y - 8) +
                'M' + (4.5 * x) + ',' + (y + 8) +
                'V' + (2 * y - 8);
            }

            // Function called by brush event handler
            function chartBrush() {
              x.domain(brush.empty() ? x2.domain() : brush.extent());

              focus.select('path').attr('d', area);
              focus.select('.x.axis').call(xAxis);
              focus.selectAll('.dot')
                .attr('cx', function (d) {return x(d.fDate); })
                .attr('cy', function (d) {return y(d.count); });
            }

            data.forEach(function (d) {
              d.title = d.date;
              d.fDate  = formatDate.parse(d.date);
              d.day   = daysOfWeek[d.fDate.getDay()];
              d.count = d.count;
            });

            // Set domains and ranges
            x.domain(d3.extent(data.map(function (d) {return d.fDate; })));
            y.domain([0, d3.max(data.map(function (d) {return d.count; }))]);
            x2.domain(x.domain());
            y2.domain(y.domain());

            // Create array of date objects using dates in dataset (used during interaction.on())
            dateMap = data.map(function (d) {return d.fDate; });

            brush = d3.svg.brush()
              .x(x2)
              .on('brush', chartBrush);

            svg = d3.select(this).append('svg')
              .attr('width', width + margin.left + margin.right)
              .attr('height', height + margin.top + margin.bottom);

            svg.append('defs').append('clipPath')
              .attr('id', 'clip')
              .append('rect')
              .attr('width', width)
              .attr('height', height);

            svg.append('defs').append('clipPath')
              .attr('id', 'dot-clip')
              .append('rect')
              .attr('x', -5)
              .attr('width', width + 10)
              .attr('height', height + 10);

            // Focus is the main graph
            focus = svg.append('g')
              .attr('class', 'main-graph')
              .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

            focus.append('path')
              .data([data])
              .attr('clip-path', 'url(#clip)')
              .attr('fill', 'steelblue')
              .attr('d', area);

            focus.append('g')
              .attr('class', 'x axis')
              .attr('transform', 'translate(0,' + height + ')')
              .call(xAxis);

            focus.append('g')
              .attr('class', 'y axis')
              .call(yAxis);

            focus.append('g')
              .selectAll('.dot')
              .data(data)
              .enter().append('circle')
              .attr('clip-path', 'url(#dot-clip)')
              .attr('class', 'dot')
              .attr('cx', function (d) {return x(d.fDate); })
              .attr('cy', function (d) {return y(d.count); })
              .attr('r', 5)
              .attr('fill', function (d) {return setColor(d.count); })
              .attr('opacity', 0)
              .attr('data-tooltip', function (d) {return d.title + ' : ' + d.count; });

            // Interaction is an overlay layer for mouse position
            interaction = svg.append('g')
              .attr('id', 'interaction')
              .append('rect')
              .attr('opacity', 0)
              .attr('x', margin.left)
              .attr('y', margin.top)
              .attr('height', height)
              .attr('width', width)
              .on('mousemove', function () {
                  var xCoord      = d3.mouse(this)[0] - margin.left,  // X coordinate of mouse over primary graph
                      xInvert     = x.invert(xCoord),                 // Convert xCoord to date value using x scale
                      cut         = d3.bisectLeft(dateMap, xInvert),  // Return index to right of xInvert
                      cut2        = (cut > 0) ? cut - 1 : 0,          // Return index to left of xInvert
                      leftPoint   = data[cut2].fDate,                  // Convert cut2 to date (milliseconds from Epoch)
                      rightPoint  = data[cut].fDate,                   // Convert cut to date (milliseconds from Epoch)
                      midpoint    = (leftPoint.getTime() + rightPoint.getTime()) / 2; // Calculate midpoint

                  // Is current postion less than the midpoint?
                  if (xInvert.getTime() < midpoint) {
                    cut -= 1;
                  }

                  // Display closest dot
                  d3.selectAll('.dot')
                    .attr('opacity', 0)
                    .filter(function (d) {return d.fDate === data[cut].fDate; })
                    .attr('opacity', 1);

                  // Display legend
                  d3.select('#legend')
                    .attr('opacity', 1);

                  // Update legend text
                  d3.select('#legend-text')
                    .text(function () {
                      var val;

                      if (data[cut].count === null) {
                        val = 'No Data Found';
                      } else {
                        val = data[cut].count;
                      }

                      return data[cut].day + ' : ' + data[cut].title + ' : ' + val;
                    });

                  d3.select('#legend-circle')
                    .attr('fill', function () {return setColor(data[cut].count); });
                })
              .on('mouseout', function () {
                d3.selectAll('.dot')
                  .attr('opacity', 0);

                d3.select('#legend')
                  .attr('opacity', 0);
              });

            // Legend display
            legend = svg.append('g')
              .attr('id', 'legend')
              .attr('opacity', 0);

            legend.append('svg:circle')
              .attr('id', 'legend-circle')
              .attr('cx', 60)
              .attr('cy', 10)
              .attr('r', 5);

            legend.append('svg:text')
              .attr('id', 'legend-text')
              .attr('x', 70)
              .attr('y', 13);

            // Context is the 'brush' graph
            context = svg.append('g')
              .attr('class', 'sub-graph')
              .attr('transform', 'translate(' + margin2.left + ',' + margin2.top + ')');

            context.append('path')
              .data([data])
              .attr('fill', 'steelblue')
              .attr('d', area2);

            context.append('g')
              .attr('class', 'x axis')
              .attr('transform', 'translate(0,' + height2 + ')')
              .call(xAxis2);

            context.append('g')
              .attr('class', 'x brush')
              .call(brush)
              .selectAll('rect')
              .attr('y', -6)
              .attr('height', height2 + 7);

            context.selectAll('.resize')
              .append('path')
              .attr('d', resizePath);

            // Axis/Tick Stylings
            d3.selectAll('.axis path')
              .attr('fill', 'none')
              .attr('stroke', '#000');
          });
        }
      // Accessor method to customize width
      chart.width = function (value) {
        if (!arguments.length) {
          return width;
        }

        width = value - margin.left - margin.right;
        return chart;
      };
      // TODO Add additional accessors for other properties

      return chart;
    };

    return {
      restrict: 'A',
      scope: {data: '='},
      link: function postLink(scope, element, attrs) {
        var chart = new TimeSeries();

        scope.render = function (data) {
          if (data.data.length > 1) {
            d3.select(element[0]).select('svg, div').remove();
            d3.select(element[0])
              .datum(data.data)
              .call(chart);
          } else {
            d3.select(element[0]).select('svg, div').remove();
            d3.select(element[0])
              .append('div')
              .attr('class', 'alert alert-warning alert-block')
              .html('
Notice!

Limited data is available for this query.

Message: Not enough data was found to display a time series. However, summary data is shown below.
');
          }
        };

        scope.$watch('data', function (newData) {
          if (!newData) {
            return;
          }

          scope.render(newData);
        });
      }
    };
  });
<|endoftext|>


<commit_before>/**
 *  Copyright 2012 Tomaz Muraus
 *
 *  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.
 *
 */

var path = require('path');

var httpProxy = require('http-proxy');
var async = require('async');
var log = require('logmagic').local('lib.server');

var constants = require('./constants');
var fsUtil = require('./util/fs');
var ProxyError = require('./util/errors').ProxyError;

/**
 * Reverse proxy server.
 *
 * @param {Object} options Options object.
 */
function ReverseProxyServer(options) {
  this._options = options;
  this._server = httpProxy.createServer(this.processRequest.bind(this));

  this._middleware = {'request': {}, 'response': {}};
}

/**
 * Run the server.
 *
 * @param {Function} callback Callback called when the server has been started
 * and bound to the port.
 */
ReverseProxyServer.prototype.run = function(callback) {
  var self = this;

  async.waterfall([
    this._initialize.bind(this),
    this._listen.bind(this, this._options.server.host, this._options.server.port)
  ],

  function(err) {
    if (err) {
      log.errorf('Failed to start the server: ${err}', {'err': err});
    }
    else {
      log.infof('Server listening on ${host}:${port}',
                {'host': self._options.server.host, 'port': self._options.server.port});
    }

    callback(err);
  });
};

ReverseProxyServer.prototype._initialize = function(callback) {
  async.waterfall([
    this._loadMiddleware.bind(this)
  ], callback);
};

/**
 * Load and register all the configured middleware.
 *
 * @param {Function} callback Callback called when all the modules have been
 * loaded.
 */
ReverseProxyServer.prototype._loadMiddleware = function(callback) {
  var self = this;

  async.waterfall([
    function getFiles(callback) {
      async.parallel([
        fsUtil.getMatchingFiles.bind(null, constants.REQUEST_MIDDLEWARE_PATH, /\.js$/, {}),
        fsUtil.getMatchingFiles.bind(null, constants.RESPONSE_MIDDLEWARE_PATH, /\.js$/, {})
      ],

      function(err, result) {
        if (err) {
          callback(err);
          return;
        }

        callback(null, {'request': result[0], 'response': result[1]});
      });
    },

    function load(paths, callback) {
      async.parallel([
        function loadRequestMiddleware(callback) {
          async.forEach(paths.request, self._loadAndRegisterMiddleware.bind(self, 'request'), callback);

        },

        function loadResponseMiddleware(callback) {
          async.forEach(paths.response, self._loadAndRegisterMiddleware.bind(self, 'response'), callback);
        }
      ],

      function() {
        callback();
      });
    }
  ], callback);
};

ReverseProxyServer.prototype._loadAndRegisterMiddleware = function(type, filePath, callback) {
  var moduleName, module, requiredFunctionName;

  if (type === 'request') {
    requiredFunctionName = 'processRequest';
  }
  else if (type === 'response') {
    requiredFunctionName = 'processResponse';
  }
  else {
    throw new Error('Invalid middleware type: ' + type);
  }

  moduleName = path.basename(filePath).replace(/\.js$/, '');

  try {
    module = require(filePath);
  }
  catch (e) {
    log.errorf('Failed to load ${type} middleware "${module}": ' + e.toString(), {'type': type, 'module': moduleName});
    callback();
    return;
  }

  if (!module.hasOwnProperty(requiredFunctionName)) {
    log.errorf('Module "${module}" is missing "${name}" function, ignoring...',
               {'name': requiredFunctionName, 'module': moduleName});
    callback();
    return;
  }

  this._middleware[type][moduleName] = module;

  log.infof('Sucesfully loaded module "${module}"', {'module': moduleName});
  callback();
};

ReverseProxyServer.prototype.processRequest = function(req, res, proxy) {
  var self = this, i, len, name, dependencies, func, buffer = httpProxy.buffer(req),
      oldWriteHead = res.writeHead, opsRequest = {}, opsResponse = {},
      proxyOptions = {'host': this._options.target.host, 'port': this._options.target.port, 'buffer': buffer};

  log.debug('Received request', {'url': req.url, 'headers': req.headers});

  // TODO: Don't build the list on every request
  for (i = 0, len = this._options.target.middleware_run_list.request.length; i < len; i++) {
    name = this._options.target.middleware_run_list.request[i];
    dependencies = this._middleware.request[name].dependencies || [];
    func = this._middleware.request[name].processRequest.bind(null, req, res);

    if (dependencies.length === 0) {
      opsRequest[name] = func;
    }
    else {
      opsRequest[name] = dependencies.concat([func]);
    }
  }

  // TODO: Don't build the list on every request
  for (i = 0, len = this._options.target.middleware_run_list.response.length; i < len; i++) {
    name = this._options.target.middleware_run_list.response[i];
    dependencies = this._middleware.response[name].dependencies || [];
    func = this._middleware.response[name].processResponse.bind(null, req, res);

    if (dependencies.length === 0) {
      opsResponse[name] = func;
    }
    else {
      opsResponse[name] = dependencies.concat([func]);
    }
  }

  // Hacky MVPish way to detect when backend has sent the response
  // TODO: fork http-proxy and add better support to it
  res.writeHead = function(code, headers) {
    res.headers = headers || {};

    async.auto(opsResponse, function(err) {
      oldWriteHead.call(res, code, res.headers);
    });
  };

  async.auto(opsRequest, function(err) {
    if (err) {
      log.error('Middleware returned an error, sending special headers to the backend...', {'error': err});

      if (err instanceof ProxyError) {
        req.headers['X-RP-Error-Code'] = err.code;
      }
      else {
        req.headers['X-RP-Error-Code'] = 'NR-5000';
      }

      req.headers['X-RP-Error-Message'] = err.message;
    }

    log.debug('Proxying request to the worker...', {'url': req.url, 'headers': req.headers,
                                                    'worker_host': self._options.target.host,
                                                    'worker_port': self._options.target.port});
    proxy.proxyRequest(req, res, proxyOptions);
  });
};

ReverseProxyServer.prototype._listen = function(host, port, callback) {
  this._server.listen(port, host, callback || function() {});
};

exports.ReverseProxyServer = ReverseProxyServer;
<commit_msg>Explicitly assign statusCode to the response object inside the res.writeHead wrapper.<commit_after>/**
 *  Copyright 2012 Tomaz Muraus
 *
 *  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.
 *
 */

var path = require('path');

var httpProxy = require('http-proxy');
var async = require('async');
var log = require('logmagic').local('lib.server');

var constants = require('./constants');
var fsUtil = require('./util/fs');
var ProxyError = require('./util/errors').ProxyError;

/**
 * Reverse proxy server.
 *
 * @param {Object} options Options object.
 */
function ReverseProxyServer(options) {
  this._options = options;
  this._server = httpProxy.createServer(this.processRequest.bind(this));

  this._middleware = {'request': {}, 'response': {}};
}

/**
 * Run the server.
 *
 * @param {Function} callback Callback called when the server has been started
 * and bound to the port.
 */
ReverseProxyServer.prototype.run = function(callback) {
  var self = this;

  async.waterfall([
    this._initialize.bind(this),
    this._listen.bind(this, this._options.server.host, this._options.server.port)
  ],

  function(err) {
    if (err) {
      log.errorf('Failed to start the server: ${err}', {'err': err});
    }
    else {
      log.infof('Server listening on ${host}:${port}',
                {'host': self._options.server.host, 'port': self._options.server.port});
    }

    callback(err);
  });
};

ReverseProxyServer.prototype._initialize = function(callback) {
  async.waterfall([
    this._loadMiddleware.bind(this)
  ], callback);
};

/**
 * Load and register all the configured middleware.
 *
 * @param {Function} callback Callback called when all the modules have been
 * loaded.
 */
ReverseProxyServer.prototype._loadMiddleware = function(callback) {
  var self = this;

  async.waterfall([
    function getFiles(callback) {
      async.parallel([
        fsUtil.getMatchingFiles.bind(null, constants.REQUEST_MIDDLEWARE_PATH, /\.js$/, {}),
        fsUtil.getMatchingFiles.bind(null, constants.RESPONSE_MIDDLEWARE_PATH, /\.js$/, {})
      ],

      function(err, result) {
        if (err) {
          callback(err);
          return;
        }

        callback(null, {'request': result[0], 'response': result[1]});
      });
    },

    function load(paths, callback) {
      async.parallel([
        function loadRequestMiddleware(callback) {
          async.forEach(paths.request, self._loadAndRegisterMiddleware.bind(self, 'request'), callback);

        },

        function loadResponseMiddleware(callback) {
          async.forEach(paths.response, self._loadAndRegisterMiddleware.bind(self, 'response'), callback);
        }
      ],

      function() {
        callback();
      });
    }
  ], callback);
};

ReverseProxyServer.prototype._loadAndRegisterMiddleware = function(type, filePath, callback) {
  var moduleName, module, requiredFunctionName;

  if (type === 'request') {
    requiredFunctionName = 'processRequest';
  }
  else if (type === 'response') {
    requiredFunctionName = 'processResponse';
  }
  else {
    throw new Error('Invalid middleware type: ' + type);
  }

  moduleName = path.basename(filePath).replace(/\.js$/, '');

  try {
    module = require(filePath);
  }
  catch (e) {
    log.errorf('Failed to load ${type} middleware "${module}": ' + e.toString(), {'type': type, 'module': moduleName});
    callback();
    return;
  }

  if (!module.hasOwnProperty(requiredFunctionName)) {
    log.errorf('Module "${module}" is missing "${name}" function, ignoring...',
               {'name': requiredFunctionName, 'module': moduleName});
    callback();
    return;
  }

  this._middleware[type][moduleName] = module;

  log.infof('Sucesfully loaded module "${module}"', {'module': moduleName});
  callback();
};

ReverseProxyServer.prototype.processRequest = function(req, res, proxy) {
  var self = this, i, len, name, dependencies, func, buffer = httpProxy.buffer(req),
      oldWriteHead = res.writeHead, opsRequest = {}, opsResponse = {},
      proxyOptions = {'host': this._options.target.host, 'port': this._options.target.port, 'buffer': buffer};

  log.debug('Received request', {'url': req.url, 'headers': req.headers});

  // TODO: Don't build the list on every request
  for (i = 0, len = this._options.target.middleware_run_list.request.length; i < len; i++) {
    name = this._options.target.middleware_run_list.request[i];
    dependencies = this._middleware.request[name].dependencies || [];
    func = this._middleware.request[name].processRequest.bind(null, req, res);

    if (dependencies.length === 0) {
      opsRequest[name] = func;
    }
    else {
      opsRequest[name] = dependencies.concat([func]);
    }
  }

  // TODO: Don't build the list on every request
  for (i = 0, len = this._options.target.middleware_run_list.response.length; i < len; i++) {
    name = this._options.target.middleware_run_list.response[i];
    dependencies = this._middleware.response[name].dependencies || [];
    func = this._middleware.response[name].processResponse.bind(null, req, res);

    if (dependencies.length === 0) {
      opsResponse[name] = func;
    }
    else {
      opsResponse[name] = dependencies.concat([func]);
    }
  }

  // Hacky MVPish way to detect when backend has sent the response
  // TODO: fork http-proxy and add better support to it
  res.writeHead = function(code, headers) {
    res.statusCode = code;
    res.headers = headers || {};

    async.auto(opsResponse, function(err) {
      oldWriteHead.call(res, code, res.headers);
    });
  };

  async.auto(opsRequest, function(err) {
    if (err) {
      log.error('Middleware returned an error, sending special headers to the backend...', {'error': err});

      if (err instanceof ProxyError) {
        req.headers['X-RP-Error-Code'] = err.code;
      }
      else {
        req.headers['X-RP-Error-Code'] = 'NR-5000';
      }

      req.headers['X-RP-Error-Message'] = err.message;
    }

    log.debug('Proxying request to the worker...', {'url': req.url, 'headers': req.headers,
                                                    'worker_host': self._options.target.host,
                                                    'worker_port': self._options.target.port});
    proxy.proxyRequest(req, res, proxyOptions);
  });
};

ReverseProxyServer.prototype._listen = function(host, port, callback) {
  this._server.listen(port, host, callback || function() {});
};

exports.ReverseProxyServer = ReverseProxyServer;
<|endoftext|>


<commit_before>angular.module( 'App.Views' ).controller( 'CheckoutCtrl', function( $window, App, Environment, Sellable, Game, payload )
{
	this.cards = payload.cards || [];
	this.sellable = new Sellable( payload.sellable );
	this.order = payload.order;
	this.game = new Game( payload.game );

	$window.Stripe.setPublishableKey( payload.stripePublishableKey );

	this.onSubmit = function( $response )
	{
		$window.location = $response.redirectUrl;
	};
} );
<commit_msg>Adding a title to the checkout page.<commit_after>angular.module( 'App.Views' ).controller( 'CheckoutCtrl', function( $window, App, Environment, Sellable, Game, payload )
{
	this.cards = payload.cards || [];
	this.sellable = new Sellable( payload.sellable );
	this.order = payload.order;
	this.game = new Game( payload.game );

	App.title = 'Buy ' + this.sellable.title;

	$window.Stripe.setPublishableKey( payload.stripePublishableKey );

	this.onSubmit = function( $response )
	{
		$window.location = $response.redirectUrl;
	};
} );
<|endoftext|>


<commit_before>const { remote } = require('electron');
const React = require('react');
const _ = require('lodash');

const Utils = require('../utils');

let isPlaylistPlaying = false;

const Player = React.createClass({
  getInitialState() {
    return {
      playlist: [],
      player: null,
      isReplayingVideo: false,
    };
  },
  onStateChange(event) {
    if (event.data === YT.PlayerState.UNSTARTED) return;
    if (event.data === YT.PlayerState.CUED) return;

    if (event.data === YT.PlayerState.ENDED) {
      if (this.state.isReplayingVideo)
        this.state.player.loadVideoById(this.state.playlist[0]);
      else if (this.isVideoEnded()) {
        window.dispatchEvent(new CustomEvent('playlist.endVideo', { detail: { video: this.state.playlist[0] } }));

        this.removeVideo();
      }
    } else {
      isPlaylistPlaying = true;
    }
  },
  removeVideo() {
    // No more video to play
    // (1 because we will remove the played video)
    if (this.state.playlist.length === 1)
      isPlaylistPlaying = false;

    // Remove last played video
    window.dispatchEvent(new CustomEvent('playlist.removeVideo', { detail: { video: this.state.playlist[0] } }));
  },
  updatePlaylist(forceNextVideo) {
    // ToDo gérer anciennes vidéos
    if (this.state.playlist.length === 0) return;

    const statesThatNeedCue = [
      YT.PlayerState.CUED,
    ];

    const playerState = this.state.player.getPlayerState();

    if (statesThatNeedCue.indexOf(playerState) > -1) {
      this.state.player.cueVideoById(this.state.playlist[0]);
    } else if (forceNextVideo) {
      this.state.player.loadVideoById(this.state.playlist[0]);
    }
  },
  componentWillReceiveProps(nextProps) {
    let updatePlaylist;
    if (this.state.playlist[0] === nextProps.playlist[0]) {
      updatePlaylist = this.updatePlaylist.bind(null, false);
    } else {
      updatePlaylist = this.updatePlaylist.bind(null, true);
    }

    this.setState({
      playlist: nextProps.playlist,
    }, updatePlaylist);
  },
  componentDidMount() {
    window.addEventListener('player.playNextVideo', this.playNextVideo);
    window.addEventListener('player.replayCurrentVideo', this.replayCurrentVideo);
    window.addEventListener('player.stopReplayCurrentVideo', this.stopReplayCurrentVideo);

    document.addEventListener('webkitfullscreenchange', () => {
      const currentWindow = remote.getCurrentWindow();
      const isFullScreen = Boolean(document.querySelector('#player:-webkit-full-screen'));

      Utils.Socket.emit('player/fullscreen', isFullScreen);

      if (isFullScreen) {
        document.body.classList.add('fullscreen');
        currentWindow.setMinimumSize(160, 90);
      } else {
        document.body.classList.remove('fullscreen');
        currentWindow.setMinimumSize(880, 370);

        // Resize window if too small
        const currentSize = currentWindow.getSize();
        let needResize = false;
        let newWidth = currentSize[0];
        let newHeight = currentSize[1];
        if (newWidth < 880) {
          needResize = true;
          newWidth = 880;
        }
        if (newHeight < 370) {
          needResize = true;
          newHeight = 370;
        }

        if (needResize) {
          currentWindow.setSize(newWidth, newHeight, true);
        }
      }

      currentWindow.setAlwaysOnTop(isFullScreen);
      currentWindow.setHasShadow(!isFullScreen);
      currentWindow.setVisibleOnAllWorkspaces(isFullScreen);
    }, false);

    this.setState({
      playlist: this.state.playlist,
      isReplayingVideo: this.state.isReplayingVideo,

      // YT may not be loaded at this time, need to find a solution...
      // That's probably why I can't put this in a getInitialState method
      player: new YT.Player('player', {
        width: 480,
        height: 270,
        events: {
          onStateChange: this.onStateChange,
        },
      }),
    });
  },
  componentWillUnmount() {
    window.removeEventListener('player.playNextVideo', this.playNextVideo);
    window.removeEventListener('player.replayCurrentVideo', this.replayCurrentVideo);
    window.removeEventListener('player.stopReplayCurrentVideo', this.stopReplayCurrentVideo);
  },
  playNextVideo() {
    this.removeVideo();
  },
  replayCurrentVideo() {
    this.setState({
      isReplayingVideo: true,
      playlist: this.state.playlist,
      player: this.state.player,
    });
  },
  stopReplayCurrentVideo() {
    this.setState({
      isReplayingVideo: false,
      playlist: this.state.playlist,
      player: this.state.player,
    });
  },
  isVideoEnded() {
    return this.state.player.getCurrentTime() === this.state.player.getDuration();
  },
  render() {
    return
;
  },
});

const PlaylistItem = React.createClass({
  propTypes: {
    id: React.PropTypes.string,
    title: React.PropTypes.string,
    channel: React.PropTypes.string,
  },
  raise() {
    if (this.props.id) {
      window.dispatchEvent(new CustomEvent('playlist.raiseVideo', { detail: { video: this.props } }));
    }
  },
  remove() {
    if (this.props.id) {
      window.dispatchEvent(new CustomEvent('playlist.removeVideo', { detail: { video: this.props } }));
    }
  },
  render() {
    return (

            ×

          {this.props.title}

          {this.props.channel}

    );
  },
});

const Playlist = React.createClass({
  propTypes: {
    videos: React.PropTypes.array,
  },
  render() {
    const videos = [];
    for (const video of this.props.videos) {
      videos.push(
        <PlaylistItem
          key={video.id}
          id={video.id}
          thumbnail={video.thumbnail}
          title={video.title}
          channel={video.channel}
          />
        );
    }

    return (

        {videos}

    );
  },
});

const Controls = React.createClass({
  propTypes: {
    numberOfPlayedVideos: React.PropTypes.number,
    numberOfVideos: React.PropTypes.number,
  },
  getInitialState() {
    return { isReplayingVideo: false };
  },
  replayCurrentVideo() {
    this.setState({ isReplayingVideo: true });

    window.dispatchEvent(new CustomEvent('player.replayCurrentVideo'));
  },
  stopReplayCurrentVideo() {
    this.setState({ isReplayingVideo: false });

    window.dispatchEvent(new CustomEvent('player.stopReplayCurrentVideo'));
  },
  isPreviousVideoDisabled() {
    return this.props.numberOfPlayedVideos === 0;
  },
  playPreviousVideo() {
    window.dispatchEvent(new CustomEvent('playlist.playPreviousVideo'));
  },
  isNextVideoDisabled() {
    return this.props.numberOfVideos <= 1;
  },
  playNextVideo() {
    window.dispatchEvent(new CustomEvent('player.playNextVideo'));
  },
  togglePlaylistVisibility() {
    window.dispatchEvent(new CustomEvent('playlist.togglePlaylistVisibility'));
  },
  render() {
    let replayVideoButton;
    if (this.state.isReplayingVideo) {
      replayVideoButton = <button className="btn" onClick={this.stopReplayCurrentVideo}></button>;
    } else {
      replayVideoButton = <button className="btn" onClick={this.replayCurrentVideo}></button>;
    }

    return (

        <button className="btn" onClick={this.playPreviousVideo} disabled={this.isPreviousVideoDisabled()}></button>
        {replayVideoButton}
        <button className="btn" onClick={this.playNextVideo} disabled={this.isNextVideoDisabled()}></button>
        <button className="btn" onClick={this.togglePlaylistVisibility}></button>

    );
  },
});

const CurrentPlaylist = React.createClass({
  getInitialState() {
    return {
      videos: [],
      playedVideos: [],
      playlistVisible: false,
    };
  },
  componentDidMount() {
    // ToDo retrieve playlist from backend

    window.addEventListener('playlist.addVideo', this.addVideo);
    window.addEventListener('playlist.cueVideo', this.cueVideo);
    window.addEventListener('playlist.endVideo', this.endVideo);
    window.addEventListener('playlist.playPreviousVideo', this.playPreviousVideo);
    window.addEventListener('playlist.removeVideo', this.removeVideo);
    window.addEventListener('playlist.raiseVideo', this.raiseVideo);
    window.addEventListener('playlist.togglePlaylistVisibility', this.togglePlaylistVisibility);
  },
  componentWillUnmount() {
    window.removeEventListener('playlist.addVideo', this.addVideo);
    window.removeEventListener('playlist.cueVideo', this.cueVideo);
    window.removeEventListener('playlist.endVideo', this.endVideo);
    window.removeEventListener('playlist.playPreviousVideo', this.playPreviousVideo);
    window.removeEventListener('playlist.removeVideo', this.removeVideo);
    window.removeEventListener('playlist.raiseVideo', this.raiseVideo);
    window.removeEventListener('playlist.togglePlaylistVisibility', this.togglePlaylistVisibility);
  },
  addVideo(event) {
    // Add the video in first position if no video playing
    // Else add it in second position

    const video = this.normalizeVideo(event.detail.video);

    if (this.isInPlaylist(video))
      return;

    this.setState((state) => {
      if (isPlaylistPlaying) {
        state.videos.splice(1, 0, video);
      } else {
        state.videos.splice(0, 0, video);
      }

      return state;
    });
  },
  cueVideo(event) {
    // Add the video in last position
    const video = this.normalizeVideo(event.detail.video);

    if (this.isInPlaylist(video))
      return;

    this.setState((state) => {
      state.videos.push(video);

      return state;
    });
  },
  endVideo(event) {
    const video = this.hydrateVideoFromPlaylist(event.detail.video);

    this.setState((state) => {
      state.playedVideos.push(video);

      return state;
    });
  },
  playPreviousVideo() {
    this.setState((state) => {
      const latestVideo = _.last(state.playedVideos);

      state.playedVideos = _.reject(state.playedVideos, latestVideo);
      state.videos.splice(0, 0, latestVideo);

      return state;
    });
  },
  removeVideo(event) {
    const video = this.normalizeVideo(event.detail.video);

    if (!this.isInPlaylist(video))
      return;

    this.setState((state) => {
      state.videos = _.reject(state.videos, video);

      return state;
    });
  },
  raiseVideo(event) {
    const video = this.normalizeVideo(event.detail.video);

    if (!this.isInPlaylist(video))
      return;

    this.setState((state) => {
      state.videos = _.reject(state.videos, video);
      if (isPlaylistPlaying) {
        state.videos.splice(1, 0, video);
      } else {
        state.videos.splice(0, 0, video);
      }

      return state;
    });
  },
  normalizeVideo(video) {
    if (_.isObject(video))
      return video;

    return { id: video };
  },
  hydrateVideoFromPlaylist(videoId) {
    return this.state.videos[_.findIndex(this.state.videos, { id: videoId })];
  },
  isInPlaylist(video) {
    return _.some(this.state.videos, video);
  },
  togglePlaylistVisibility() {
    this.setState({
      videos: this.state.videos,
      playedVideos: this.state.playedVideos,
      playlistVisible: document.querySelector('#playlist').classList.toggle('visible'),
    });
  },
  render() {
    return (

        <Playlist videos={this.state.videos} />
        <Controls numberOfPlayedVideos={this.state.playedVideos.length} numberOfVideos={this.state.videos.length} />
        <Player playlist={_.map(this.state.videos, 'id')} />

    );
  },
});

module.exports = CurrentPlaylist;
<commit_msg>Make the config button work, remove auto resize if too small when quitting fullscreen mode<commit_after>const { remote } = require('electron');
const React = require('react');
const _ = require('lodash');

const Utils = require('../utils');

let isPlaylistPlaying = false;

const Player = React.createClass({
  getInitialState() {
    return {
      playlist: [],
      player: null,
      isReplayingVideo: false,
    };
  },
  onStateChange(event) {
    if (event.data === YT.PlayerState.UNSTARTED) return;
    if (event.data === YT.PlayerState.CUED) return;

    if (event.data === YT.PlayerState.ENDED) {
      if (this.state.isReplayingVideo)
        this.state.player.loadVideoById(this.state.playlist[0]);
      else if (this.isVideoEnded()) {
        window.dispatchEvent(new CustomEvent('playlist.endVideo', { detail: { video: this.state.playlist[0] } }));

        this.removeVideo();
      }
    } else {
      isPlaylistPlaying = true;
    }
  },
  removeVideo() {
    // No more video to play
    // (1 because we will remove the played video)
    if (this.state.playlist.length === 1)
      isPlaylistPlaying = false;

    // Remove last played video
    window.dispatchEvent(new CustomEvent('playlist.removeVideo', { detail: { video: this.state.playlist[0] } }));
  },
  updatePlaylist(forceNextVideo) {
    // ToDo gérer anciennes vidéos
    if (this.state.playlist.length === 0) return;

    const statesThatNeedCue = [
      YT.PlayerState.CUED,
    ];

    const playerState = this.state.player.getPlayerState();

    if (statesThatNeedCue.indexOf(playerState) > -1) {
      this.state.player.cueVideoById(this.state.playlist[0]);
    } else if (forceNextVideo) {
      this.state.player.loadVideoById(this.state.playlist[0]);
    }
  },
  componentWillReceiveProps(nextProps) {
    let updatePlaylist;
    if (this.state.playlist[0] === nextProps.playlist[0]) {
      updatePlaylist = this.updatePlaylist.bind(null, false);
    } else {
      updatePlaylist = this.updatePlaylist.bind(null, true);
    }

    this.setState({
      playlist: nextProps.playlist,
    }, updatePlaylist);
  },
  componentDidMount() {
    window.addEventListener('player.playNextVideo', this.playNextVideo);
    window.addEventListener('player.replayCurrentVideo', this.replayCurrentVideo);
    window.addEventListener('player.stopReplayCurrentVideo', this.stopReplayCurrentVideo);

    document.addEventListener('webkitfullscreenchange', () => {
      const currentWindow = remote.getCurrentWindow();
      const isFullScreen = Boolean(document.querySelector('#player:-webkit-full-screen'));

      if (Utils.shouldResize())
        Utils.Socket.emit('player/fullscreen', isFullScreen);

      if (isFullScreen) {
        document.body.classList.add('fullscreen');
        currentWindow.setMinimumSize(160, 90);
      } else {
        document.body.classList.remove('fullscreen');
        currentWindow.setMinimumSize(880, 370);
      }

      currentWindow.setAlwaysOnTop(isFullScreen);
      currentWindow.setHasShadow(!isFullScreen);
      currentWindow.setVisibleOnAllWorkspaces(isFullScreen);
    }, false);

    this.setState({
      playlist: this.state.playlist,
      isReplayingVideo: this.state.isReplayingVideo,

      // YT may not be loaded at this time, need to find a solution...
      // That's probably why I can't put this in a getInitialState method
      player: new YT.Player('player', {
        width: 480,
        height: 270,
        events: {
          onStateChange: this.onStateChange,
        },
      }),
    });
  },
  componentWillUnmount() {
    window.removeEventListener('player.playNextVideo', this.playNextVideo);
    window.removeEventListener('player.replayCurrentVideo', this.replayCurrentVideo);
    window.removeEventListener('player.stopReplayCurrentVideo', this.stopReplayCurrentVideo);
  },
  playNextVideo() {
    this.removeVideo();
  },
  replayCurrentVideo() {
    this.setState({
      isReplayingVideo: true,
      playlist: this.state.playlist,
      player: this.state.player,
    });
  },
  stopReplayCurrentVideo() {
    this.setState({
      isReplayingVideo: false,
      playlist: this.state.playlist,
      player: this.state.player,
    });
  },
  isVideoEnded() {
    return this.state.player.getCurrentTime() === this.state.player.getDuration();
  },
  render() {
    return
;
  },
});

const PlaylistItem = React.createClass({
  propTypes: {
    id: React.PropTypes.string,
    title: React.PropTypes.string,
    channel: React.PropTypes.string,
  },
  raise() {
    if (this.props.id) {
      window.dispatchEvent(new CustomEvent('playlist.raiseVideo', { detail: { video: this.props } }));
    }
  },
  remove() {
    if (this.props.id) {
      window.dispatchEvent(new CustomEvent('playlist.removeVideo', { detail: { video: this.props } }));
    }
  },
  render() {
    return (

            ×

          {this.props.title}

          {this.props.channel}

    );
  },
});

const Playlist = React.createClass({
  propTypes: {
    videos: React.PropTypes.array,
  },
  render() {
    const videos = [];
    for (const video of this.props.videos) {
      videos.push(
        <PlaylistItem
          key={video.id}
          id={video.id}
          thumbnail={video.thumbnail}
          title={video.title}
          channel={video.channel}
          />
        );
    }

    return (

        {videos}

    );
  },
});

const Controls = React.createClass({
  propTypes: {
    numberOfPlayedVideos: React.PropTypes.number,
    numberOfVideos: React.PropTypes.number,
  },
  getInitialState() {
    return { isReplayingVideo: false };
  },
  replayCurrentVideo() {
    this.setState({ isReplayingVideo: true });

    window.dispatchEvent(new CustomEvent('player.replayCurrentVideo'));
  },
  stopReplayCurrentVideo() {
    this.setState({ isReplayingVideo: false });

    window.dispatchEvent(new CustomEvent('player.stopReplayCurrentVideo'));
  },
  isPreviousVideoDisabled() {
    return this.props.numberOfPlayedVideos === 0;
  },
  playPreviousVideo() {
    window.dispatchEvent(new CustomEvent('playlist.playPreviousVideo'));
  },
  isNextVideoDisabled() {
    return this.props.numberOfVideos <= 1;
  },
  playNextVideo() {
    window.dispatchEvent(new CustomEvent('player.playNextVideo'));
  },
  togglePlaylistVisibility() {
    window.dispatchEvent(new CustomEvent('playlist.togglePlaylistVisibility'));
  },
  render() {
    let replayVideoButton;
    if (this.state.isReplayingVideo) {
      replayVideoButton = <button className="btn" onClick={this.stopReplayCurrentVideo}></button>;
    } else {
      replayVideoButton = <button className="btn" onClick={this.replayCurrentVideo}></button>;
    }

    return (

        <button className="btn" onClick={this.playPreviousVideo} disabled={this.isPreviousVideoDisabled()}></button>
        {replayVideoButton}
        <button className="btn" onClick={this.playNextVideo} disabled={this.isNextVideoDisabled()}></button>
        <button className="btn" onClick={this.togglePlaylistVisibility}></button>

    );
  },
});

const CurrentPlaylist = React.createClass({
  getInitialState() {
    return {
      videos: [],
      playedVideos: [],
      playlistVisible: false,
    };
  },
  componentDidMount() {
    // ToDo retrieve playlist from backend

    window.addEventListener('playlist.addVideo', this.addVideo);
    window.addEventListener('playlist.cueVideo', this.cueVideo);
    window.addEventListener('playlist.endVideo', this.endVideo);
    window.addEventListener('playlist.playPreviousVideo', this.playPreviousVideo);
    window.addEventListener('playlist.removeVideo', this.removeVideo);
    window.addEventListener('playlist.raiseVideo', this.raiseVideo);
    window.addEventListener('playlist.togglePlaylistVisibility', this.togglePlaylistVisibility);
  },
  componentWillUnmount() {
    window.removeEventListener('playlist.addVideo', this.addVideo);
    window.removeEventListener('playlist.cueVideo', this.cueVideo);
    window.removeEventListener('playlist.endVideo', this.endVideo);
    window.removeEventListener('playlist.playPreviousVideo', this.playPreviousVideo);
    window.removeEventListener('playlist.removeVideo', this.removeVideo);
    window.removeEventListener('playlist.raiseVideo', this.raiseVideo);
    window.removeEventListener('playlist.togglePlaylistVisibility', this.togglePlaylistVisibility);
  },
  addVideo(event) {
    // Add the video in first position if no video playing
    // Else add it in second position

    const video = this.normalizeVideo(event.detail.video);

    if (this.isInPlaylist(video))
      return;

    this.setState((state) => {
      if (isPlaylistPlaying) {
        state.videos.splice(1, 0, video);
      } else {
        state.videos.splice(0, 0, video);
      }

      return state;
    });
  },
  cueVideo(event) {
    // Add the video in last position
    const video = this.normalizeVideo(event.detail.video);

    if (this.isInPlaylist(video))
      return;

    this.setState((state) => {
      state.videos.push(video);

      return state;
    });
  },
  endVideo(event) {
    const video = this.hydrateVideoFromPlaylist(event.detail.video);

    this.setState((state) => {
      state.playedVideos.push(video);

      return state;
    });
  },
  playPreviousVideo() {
    this.setState((state) => {
      const latestVideo = _.last(state.playedVideos);

      state.playedVideos = _.reject(state.playedVideos, latestVideo);
      state.videos.splice(0, 0, latestVideo);

      return state;
    });
  },
  removeVideo(event) {
    const video = this.normalizeVideo(event.detail.video);

    if (!this.isInPlaylist(video))
      return;

    this.setState((state) => {
      state.videos = _.reject(state.videos, video);

      return state;
    });
  },
  raiseVideo(event) {
    const video = this.normalizeVideo(event.detail.video);

    if (!this.isInPlaylist(video))
      return;

    this.setState((state) => {
      state.videos = _.reject(state.videos, video);
      if (isPlaylistPlaying) {
        state.videos.splice(1, 0, video);
      } else {
        state.videos.splice(0, 0, video);
      }

      return state;
    });
  },
  normalizeVideo(video) {
    if (_.isObject(video))
      return video;

    return { id: video };
  },
  hydrateVideoFromPlaylist(videoId) {
    return this.state.videos[_.findIndex(this.state.videos, { id: videoId })];
  },
  isInPlaylist(video) {
    return _.some(this.state.videos, video);
  },
  togglePlaylistVisibility() {
    this.setState({
      videos: this.state.videos,
      playedVideos: this.state.playedVideos,
      playlistVisible: document.querySelector('#playlist').classList.toggle('visible'),
    });
  },
  render() {
    return (

        <Playlist videos={this.state.videos} />
        <Controls numberOfPlayedVideos={this.state.playedVideos.length} numberOfVideos={this.state.videos.length} />
        <Player playlist={_.map(this.state.videos, 'id')} />

    );
  },
});

module.exports = CurrentPlaylist;
<|endoftext|>


<commit_before>var util = require('./util');
var express = require('express');
var http = require('http');
var EventEmitter = require('events').EventEmitter;
var WebSocketServer = require('ws').Server;
var url = require('url');

var allowCrossDomain = function(req, res, next) {
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type');

  next();
}

function PeerServer(options) {
  if (!(this instanceof PeerServer)) return new PeerServer(options);
  EventEmitter.call(this);

  this._app = express();
  this._httpServer = http.createServer(this._app);
  this._app.use(express.bodyParser());
  this._app.use(allowCrossDomain);

  options = util.extend({
    port: 80
  }, options);

  util.debug = options.debug;

  // Listen on user-specified port and create WebSocket server as well.
  this._httpServer.listen(options.port);
  this._wss = new WebSocketServer({ path: '/ws', server: this._httpServer });

  // WebSockets that are opened or HTTP responses (which are paired with
  // something in timeouts.
  this._clients = {};
  // Timeouts for HTTP responses.
  this._timeouts = {};
  // Connections waiting for another peer.
  this._outstandingOffers = {};

  // Initailize WebSocket server handlers.
  this._initializeWSS();

  // Initialize HTTP routes. This is only used for the first few milliseconds
  // before a socket is opened for a Peer.
  this._initializeHTTP();
};

util.inherits(PeerServer, EventEmitter);

/* Initialize WebSocket server. */
PeerServer.prototype._initializeWSS = function() {
  var self = this;
  this._wss.on('connection', function(socket) {
    var id = url.parse(socket.upgradeReq.url, true).query.id;
    if (!!id && !!self._clients[id]) {
      // If response client and timeout exist, overwrite and clear.
      if (!!self._timeouts[id]) {
        clearTimeout(self._timeouts[id]);
        delete self._timeouts[id];
        self._clients[id].end('socket');
      } else {
        socket.send(JSON.stringify({ type: 'ERROR', msg: 'ID is taken' }));
        return;
      }
    } else if (!id) {
      id = self._generateClientId();
      socket.send(JSON.stringify({ type: 'ID', id: id }));
    }

    // Save the socket for this id.
    self._clients[id] = socket;

    self._processOutstandingOffers(id);

    socket.on('message', function(data) {
      try {
        var message = JSON.parse(data);
        util.log(message);

        switch (message.type) {
          case 'LEAVE':
          // ICE candidates
          case 'CANDIDATE':
          // Offer or answer between peers.
          case 'OFFER':
          case 'ANSWER':
          // Firefoxism (connectDataConnection ports)
          case 'PORT':
            self._handleTransmission(message.type, message.src, message.dst, data);

            // Clean up.
            if (message.type === 'LEAVE') {
              delete self._clients[message.src];
              delete self._timeouts[message.src];
            }
            break;
          default:
            util.prettyError('message unrecognized');
        }
      } catch(e) {
        util.log('invalid message');
      }
    });
  });
};

/** Process outstanding peer offers. */
PeerServer.prototype._processOutstandingOffers = function(id) {
  var offers = this._outstandingOffers[id];
  for (var source in offers) {
    if (offers.hasOwnProperty(source)) {
      var messages = offers[source]
      for (var i = 0; i < messages.length; i += 1)
        this._handleTransmission.apply(this, messages[i]);

      delete this._outstandingOffers[id][source];
    }
  }
};

/** Initialize HTTP server routes. */
PeerServer.prototype._initializeHTTP = function() {
  var self = this;

  this._app.options('/*', function(req, res, next) {
    res.send(200);
  });

  // Server sets up HTTP streaming whether you get or post an ID.
  // Retrieve guaranteed random ID.
  this._app.get('/id', function(req, res) {
    var clientId = util.randomId();
    while (!!self._clients[clientId]) {
      clientId = util.randomId();
    }
    self._startStreaming(res, clientId, function() {
      // Chrome hacks.
      res.write('{"id":"' + clientId + '"}\n');
    });
  });

  this._app.post('/id', function(req, res) {
    var id = req.body.id;
    self._startStreaming(res, id);
  });

  this._app.post('/offer', function(req, res) {
    var src = req.body.src;
    var dst = req.body.dst;
    self._handleTransmission('OFFER', src, dst, JSON.stringify(req.body), res);
  });

  this._app.post('/ice', function(req, res) {
    var src = req.body.src;
    var dst = req.body.dst;
    self._handleTransmission('ICE', src, dst, JSON.stringify(req.body), res);
  });

  this._app.post('/answer', function(req, res) {
    var src = req.body.src;
    var dst = req.body.dst;
    self._handleTransmission('ANSWER', src, dst, JSON.stringify(req.body), res);
  });

  this._app.post('/leave', function(req, res) {
    var src = req.body.src;
    var dst = req.body.dst;
    self._handleTransmission('LEAVE', src, dst, JSON.stringify(req.body), res);
  });

  this._app.post('/port', function(req, res) {
    var src = req.body.src;
    var dst = req.body.dst;
    self._handleTransmission('PORT', src, dst, JSON.stringify(req.body), res);
  });
};

/** Saves a streaming response and takes care of timeouts and headers. */
PeerServer.prototype._startStreaming = function(res, id, write) {
  res.writeHead(200, {'Content-Type': 'application/octet-stream'});
  if (!!write) {
    write();
  }

  var pad = '00';
  var iterations = 10;
  for (var i = 0; i < iterations; i++) {
    pad += pad;
  }
  res.write(pad + '\n');

  // Save res so we can write to it.
  if (!this._clients[id]) {
    this._clients[id] = res;
    // Set timeout to expire.
    this._timeouts[id] = setTimeout(function() { res.end('end') }, 10000);
  } else {
    res.write(JSON.stringify({ type: 'ERROR', msg: 'ID is taken' }) + '\n');
    res.end('error');
  }

};

/** Handles passing on a message. */
PeerServer.prototype._handleTransmission = function(type, src, dst, data, res) {
  var destination = this._clients[dst];

  if (!!destination) {
    try {
      if (this._timeouts[dst]) {
        data += '\n';
      }
      // We have to let the source peer know that the offer was sent
      // successfully so that ice can start being processed.
      if (type === 'OFFER') {
        if (!!res) {
          res.send(200);
        } else if (!this._timeouts[src] && !!this._clients[src]) {
          this._clients[src].send(JSON.stringify({ type: 'PEER_READY', src: dst, dst: src }));
        }
      }
      destination.send(data);
    } catch (e) {
      util.prettyError(e);
      // This really shouldn't happen given correct client browsers.
      // 501: Server does not support this functionality.
      if (!!res) res.send(501);
    }
  } else {
    if (type === 'OFFER' && (!this._outstandingOffers[dst] || !this._outstandingOffers[dst][src])) {
      // Wait 5 seconds for this client to connect.
      var self = this;
      if (!this._outstandingOffers[dst])
        this._outstandingOffers[dst] = {};
      this._outstandingOffers[dst][src] = [];
      this._outstandingOffers[dst][src].push(Array.prototype.slice.apply(arguments));

      setTimeout(function() {
        delete self._outstandingOffers[dst][src]
      }, 5000);
    } else if (type === 'ICE' && !!this._outstandingOffers[dst][src]) {
      this._outstandingOffers[dst][src].push(Array.prototype.slice.apply(arguments));
    } else {
      // Assume a disconnect if the client no longer exists.
      util.log('destination does not exist');
      this._handleTransmission('LEAVE', dst, src, JSON.stringify({ type: 'LEAVE', dst: src, src: dst }));
      // 410: Resource not available.
      if (!!res) res.send(410);
    }
  }
};

PeerServer.prototype._generateClientId = function() {
  var clientId = util.randomId();
  while (!!self._clients[clientId]) {
    clientId = util.randomId();
  }
};

exports.PeerServer = PeerServer;
<commit_msg>more flexible leave option for peer v connections<commit_after>var util = require('./util');
var express = require('express');
var http = require('http');
var EventEmitter = require('events').EventEmitter;
var WebSocketServer = require('ws').Server;
var url = require('url');

function PeerServer(options) {
  if (!(this instanceof PeerServer)) return new PeerServer(options);
  EventEmitter.call(this);

  this._app = express();
  this._httpServer = http.createServer(this._app);
  this._app.use(express.bodyParser());
  this._app.use(this._allowCrossDomain);

  options = util.extend({
    port: 80
  }, options);

  util.debug = options.debug;

  // Listen on user-specified port and create WebSocket server as well.
  this._httpServer.listen(options.port);
  this._wss = new WebSocketServer({ path: '/ws', server: this._httpServer });

  // WebSockets that are opened or HTTP responses (which are paired with
  // something in timeouts.
  this._clients = {};
  // Timeouts for HTTP responses.
  this._timeouts = {};
  // Connections waiting for another peer.
  this._outstandingOffers = {};

  // Initailize WebSocket server handlers.
  this._initializeWSS();

  // Initialize HTTP routes. This is only used for the first few milliseconds
  // before a socket is opened for a Peer.
  this._initializeHTTP();
};

util.inherits(PeerServer, EventEmitter);

/** Handle CORS */
PeerServer.prototype._allowCrossDomain = function(req, res, next) {
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type');

  next();
}

/** Initialize WebSocket server. */
PeerServer.prototype._initializeWSS = function() {
  var self = this;
  this._wss.on('connection', function(socket) {
    var id = url.parse(socket.upgradeReq.url, true).query.id;
    if (!!id && !!self._clients[id]) {
      // If response client and timeout exist, overwrite and clear.
      if (!!self._timeouts[id]) {
        clearTimeout(self._timeouts[id]);
        delete self._timeouts[id];
        self._clients[id].end('socket');
      } else {
        socket.send(JSON.stringify({ type: 'ERROR', msg: 'ID is taken' }));
        return;
      }
    } else if (!id) {
      id = self._generateClientId();
      socket.send(JSON.stringify({ type: 'ID', id: id }));
    }

    // Save the socket for this id.
    self._clients[id] = socket;

    self._processOutstandingOffers(id);

    socket.on('message', function(data) {
      try {
        var message = JSON.parse(data);
        util.log(message);

        switch (message.type) {
          case 'LEAVE':
            // Clean up if a Peer sends a LEAVE.
            if (!message.dst) {
              delete self._clients[message.src];
              delete self._timeouts[message.src];
              break;
            }
          // ICE candidates
          case 'CANDIDATE':
          // Offer or answer between peers.
          case 'OFFER':
          case 'ANSWER':
          // Firefoxism (connectDataConnection ports)
          case 'PORT':
            self._handleTransmission(message.type, message.src, message.dst, data);
            break;
          default:
            util.prettyError('message unrecognized');
        }
      } catch(e) {
        util.log('invalid message');
      }
    });
  });
};

/** Process outstanding peer offers. */
PeerServer.prototype._processOutstandingOffers = function(id) {
  var offers = this._outstandingOffers[id];
  for (var source in offers) {
    if (offers.hasOwnProperty(source)) {
      var messages = offers[source]
      for (var i = 0; i < messages.length; i += 1)
        this._handleTransmission.apply(this, messages[i]);

      delete this._outstandingOffers[id][source];
    }
  }
};

/** Initialize HTTP server routes. */
PeerServer.prototype._initializeHTTP = function() {
  var self = this;

  this._app.options('/*', function(req, res, next) {
    res.send(200);
  });

  // Server sets up HTTP streaming whether you get or post an ID.
  // Retrieve guaranteed random ID.
  this._app.get('/id', function(req, res) {
    var clientId = util.randomId();
    while (!!self._clients[clientId]) {
      clientId = util.randomId();
    }
    self._startStreaming(res, clientId, function() {
      // Chrome hacks.
      res.write('{"id":"' + clientId + '"}\n');
    });
  });

  this._app.post('/id', function(req, res) {
    var id = req.body.id;
    self._startStreaming(res, id);
  });

  this._app.post('/offer', function(req, res) {
    var src = req.body.src;
    var dst = req.body.dst;
    self._handleTransmission('OFFER', src, dst, JSON.stringify(req.body), res);
  });

  this._app.post('/ice', function(req, res) {
    var src = req.body.src;
    var dst = req.body.dst;
    self._handleTransmission('ICE', src, dst, JSON.stringify(req.body), res);
  });

  this._app.post('/answer', function(req, res) {
    var src = req.body.src;
    var dst = req.body.dst;
    self._handleTransmission('ANSWER', src, dst, JSON.stringify(req.body), res);
  });

  this._app.post('/leave', function(req, res) {
    var src = req.body.src;
    var dst = req.body.dst;
    self._handleTransmission('LEAVE', src, dst, JSON.stringify(req.body), res);
  });

  this._app.post('/port', function(req, res) {
    var src = req.body.src;
    var dst = req.body.dst;
    self._handleTransmission('PORT', src, dst, JSON.stringify(req.body), res);
  });
};

/** Saves a streaming response and takes care of timeouts and headers. */
PeerServer.prototype._startStreaming = function(res, id, write) {
  res.writeHead(200, {'Content-Type': 'application/octet-stream'});
  if (!!write) {
    write();
  }

  var pad = '00';
  var iterations = 10;
  for (var i = 0; i < iterations; i++) {
    pad += pad;
  }
  res.write(pad + '\n');

  // Save res so we can write to it.
  if (!this._clients[id]) {
    this._clients[id] = res;
    // Set timeout to expire.
    this._timeouts[id] = setTimeout(function() { res.end('end') }, 10000);
  } else {
    res.write(JSON.stringify({ type: 'ERROR', msg: 'ID is taken' }) + '\n');
    res.end('error');
  }

};

/** Handles passing on a message. */
PeerServer.prototype._handleTransmission = function(type, src, dst, data, res) {
  var destination = this._clients[dst];

  if (!!destination) {
    try {
      if (this._timeouts[dst]) {
        data += '\n';
      }
      // We have to let the source peer know that the offer was sent
      // successfully so that ice can start being processed.
      if (type === 'OFFER') {
        if (!!res) {
          res.send(200);
        } else if (!this._timeouts[src] && !!this._clients[src]) {
          this._clients[src].send(JSON.stringify({ type: 'PEER_READY', src: dst, dst: src }));
        }
      }
      destination.send(data);
    } catch (e) {
      util.prettyError(e);
      // This really shouldn't happen given correct client browsers.
      // 501: Server does not support this functionality.
      if (!!res) res.send(501);
    }
  } else {
    if (type === 'OFFER' && (!this._outstandingOffers[dst] || !this._outstandingOffers[dst][src])) {
      // Wait 5 seconds for this client to connect.
      var self = this;
      if (!this._outstandingOffers[dst])
        this._outstandingOffers[dst] = {};
      this._outstandingOffers[dst][src] = [];
      this._outstandingOffers[dst][src].push(Array.prototype.slice.apply(arguments));

      setTimeout(function() {
        delete self._outstandingOffers[dst][src]
      }, 5000);
    } else if (type === 'ICE' && !!this._outstandingOffers[dst][src]) {
      this._outstandingOffers[dst][src].push(Array.prototype.slice.apply(arguments));
    } else {
      // Assume a disconnect if the client no longer exists.
      util.log('destination does not exist');
      this._handleTransmission('LEAVE', dst, src, JSON.stringify({ type: 'LEAVE', dst: src, src: dst }));
      // 410: Resource not available.
      if (!!res) res.send(410);
    }
  }
};

PeerServer.prototype._generateClientId = function() {
  var clientId = util.randomId();
  while (!!self._clients[clientId]) {
    clientId = util.randomId();
  }
};

exports.PeerServer = PeerServer;
<|endoftext|>


<commit_before>var express = require('express'),
    bodyParser = require('body-parser'),
    async = require('async'),
    glob = require('glob'),
    path = require('path'),
    fs = require('fs'),
    fse = require('fs-extra'),
    colors = require('colors');

var root = process.cwd() + '/src';

var app = express();

app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(express.static(root));

app.get('/', function (req, res) {

    render('index.html', res);
});

app.get('/editor', function (req, res) {

    render('weltmeister.html', res);
});

function render (template, res) {

  fs.readFile(template, function (err, html) {

    if (err) { throw err; }

    res.writeHeader(200, {'Content-Type': 'text/html'});
    res.write(html);
    res.end();
  });
}

app.get('/api/glob', function (req, res) {

    async.reduce(req.query.glob, [], function (memo, item, callback) {

        glob(item, {cwd: root}, function (e, matches) {

            for (var i in matches) { memo.push(matches[i]); }

            callback(null, memo);
        });
    },
    function (err, result) { res.send(result); });
});

app.post('/api/save', function (req, res) {

    var path = req.body.path,
        data = req.body.data;

    path && data ?

        /\.js$/.test(path) ?

            fse.outputFile(root + '/' + path, data, function (err) {

                err ? res.send({error: 2, msg: 'Couldn\'t write to file: ' + path}) :
                    res.send({error: 0});
            })

            :

            res.send({error: 3, msg: 'File must have a .js suffix'})

        :

        res.send({error: 1, msg: 'No Data or Path specified'});
});

app.get('/api/browse', function (req, res) {

    var dir = req.query.dir || '',
        type = req.query.type,
        types = {scripts: ['.js'], images: ['.png', '.gif', '.jpg', '.jpeg']},
        result = {parent: false, dirs: [], files: []},
        filter, stats, dirpath;

    filter = (type && types[type]) ? types[type] : false;

    result.parent = req.query.dir ? dir.substring(0, dir.lastIndexOf('/')) : false;

    dir[dir.length - 1] === '/' && (dir = dir.substring(0, dir.length - 1));

    dir += '/';

    dirpath = path.normalize(root + dir);

    fs.readdir(dirpath, function (err, files) {

        for (var i in files) {

            stats = fs.statSync(dirpath + files[i]);

            stats.isDirectory() ? result.dirs.push(dir + files[i]) :

            stats.isFile() &&
                filter ? filter.indexOf(path.extname(files[i])) >= 0 && result.files.push(dir + files[i])
                    : result.files.push(dir + files[i]);
        }

        res.send(result);
    });
});

module.exports = {

    run: function (port, domain) {

        var PORT = Number(port) || 3000;

        app.listen(PORT);

        console.log('node-impact server is listening at '.green +
                    ('http://localhost:' + PORT).yellow +
                    ' Editor: '.green +
                    ('http://localhost:' + PORT + '/editor').yellow);
    }
};
<commit_msg>fix typo<commit_after>var express = require('express'),
    bodyParser = require('body-parser'),
    async = require('async'),
    glob = require('glob'),
    path = require('path'),
    fs = require('fs'),
    fse = require('fs-extra'),
    colors = require('colors');

var root = process.cwd() + '/src';

var app = express();

app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(express.static(root));

app.get('/', function (req, res) {

    render('index.html', res);
});

app.get('/editor', function (req, res) {

    render('weltmeister.html', res);
});

function render (template, res) {

  fs.readFile(template, function (err, html) {

    if (err) { throw err; }

    res.writeHeader(200, {'Content-Type': 'text/html'});
    res.write(html);
    res.end();
  });
}

app.get('/api/glob', function (req, res) {

    async.reduce(req.query.glob, [], function (memo, item, callback) {

        glob(item, {cwd: root}, function (e, matches) {

            for (var i in matches) { memo.push(matches[i]); }

            callback(null, memo);
        });
    },
    function (err, result) { res.send(result); });
});

app.post('/api/save', function (req, res) {

    var path = req.body.path,
        data = req.body.data;

    path && data ?

        /\.js$/.test(path) ?

            fse.outputFile(root + '/' + path, data, function (err) {

                err ? res.send({error: 2, msg: 'Couldn\'t write to file: ' + path}) :
                    res.send({error: 0});
            })

            :

            res.send({error: 3, msg: 'File must have a .js suffix'})

        :

        res.send({error: 1, msg: 'No Data or Path specified'});
});

app.get('/api/browse', function (req, res) {

    var dir = req.query.dir || '',
        type = req.query.type,
        types = {scripts: ['.js'], images: ['.png', '.gif', '.jpg', '.jpeg']},
        result = {parent: false, dirs: [], files: []},
        filter, stats, dirpath;

    filter = (type && types[type]) ? types[type] : false;

    result.parent = req.query.dir ? dir.substring(0, dir.lastIndexOf('/')) : false;

    dir[dir.length - 1] === '/' && (dir = dir.substring(0, dir.length - 1));

    dir += '/';

    dirpath = path.normalize(root + dir);

    fs.readdir(dirpath, function (err, files) {

        for (var i in files) {

            stats = fs.statSync(dirpath + files[i]);

            stats.isDirectory() ? result.dirs.push(dir + files[i]) :

            stats.isFile() &&
                filter ? filter.indexOf(path.extname(files[i])) >= 0 && result.files.push(dir + files[i])
                    : result.files.push(dir + files[i]);
        }

        res.send(result);
    });
});

module.exports = {

    run: function (port, domain) {

        var PORT = Number(port) || 3000;

        app.listen(PORT);

        console.log('impact-node server is listening at '.green +
                    ('http://localhost:' + PORT).yellow +
                    ' Editor: '.green +
                    ('http://localhost:' + PORT + '/editor').yellow);
    }
};
<|endoftext|>


<commit_before>var ws = require('streamws');
var events = require('events');
var util = require('./util');

var BinaryClient = require('./client');

function BinaryServer(options) {
  if (!(this instanceof BinaryServer)) return new BinaryServer(options);

  var self = this;

  this.server  = new ws.Server(options);
  this.server.on('connection', function(socket){
    var binaryClient = new BinaryClient(socket);
    self.emit('connection', binaryClient);
  });
  this.server.on('error', function(){
    self.emit('error', error);
  });
}

util.inherits(BinaryServer, events.EventEmitter);

BinaryServer.prototype.close = function(code, message){
  this.server.close(code, message);
}

exports.BinaryServer = BinaryServer;<commit_msg>error listener error parameter<commit_after>var ws = require('streamws');
var events = require('events');
var util = require('./util');

var BinaryClient = require('./client');

function BinaryServer(options) {
  if (!(this instanceof BinaryServer)) return new BinaryServer(options);

  var self = this;

  this.server  = new ws.Server(options);
  this.server.on('connection', function(socket){
    var binaryClient = new BinaryClient(socket);
    self.emit('connection', binaryClient);
  });
  this.server.on('error', function(error){
    self.emit('error', error);
  });
}

util.inherits(BinaryServer, events.EventEmitter);

BinaryServer.prototype.close = function(code, message){
  this.server.close(code, message);
}

exports.BinaryServer = BinaryServer;<|endoftext|>


<commit_before>// Native
const path = require('path')
const { parse, format } = require('url')

// Packages
const micro = require('micro')
const auth = require('basic-auth')
const { red } = require('chalk')
const fs = require('fs-extra')
const pathType = require('path-type')
const mime = require('mime-types')
const stream = require('send')
const { coroutine } = require('bluebird')
const isPathInside = require('path-is-inside')

// Utilities
const renderDirectory = require('./render')

module.exports = coroutine(function*(req, res, flags, current, ignoredFiles) {
  const headers = {}

  if (flags.cors) {
    headers['Access-Control-Allow-Origin'] = '*'
    headers['Access-Control-Allow-Headers'] =
      'Origin, X-Requested-With, Content-Type, Accept, Range'
  }

  for (const header in headers) {
    if (!{}.hasOwnProperty.call(headers, header)) {
      continue
    }

    res.setHeader(header, headers[header])
  }

  if (flags.auth) {
    const credentials = auth(req)

    if (!process.env.SERVE_USER || !process.env.SERVE_PASSWORD) {
      const error =
        'The environment variables "SERVE_USER" ' +
        'and/or "SERVE_PASSWORD" are missing!'
      console.error(red(error))

      // eslint-disable-next-line unicorn/no-process-exit
      process.exit(1)
    }

    if (
      !credentials ||
      credentials.name !== process.env.SERVE_USER ||
      credentials.pass !== process.env.SERVE_PASSWORD
    ) {
      res.statusCode = 401
      res.setHeader('WWW-Authenticate', 'Basic realm="User Visible Realm"')
      return micro.send(res, 401, 'Access Denied')
    }
  }

  const { pathname } = parse(req.url)
  const { ASSET_DIR } = process.env

  let related = path.parse(path.join(current, pathname))
  let assetRequest = false

  if (path.basename(related.dir) === ASSET_DIR) {
    assetRequest = true
    related = decodeURIComponent(path.join(__dirname, '/../assets/styles.css'))
  } else {
    related = decodeURIComponent(path.format(related))

    const relatedResolved = path.resolve(related)
    const relatedCurrent = path.resolve(current)

    const isSame = relatedResolved === relatedCurrent

    if (!isSame && !isPathInside(relatedResolved, relatedCurrent)) {
      return micro.send(res, 400, 'Bad Request')
    }
  }

  let notFoundResponse = 'Not Found'

  try {
    const custom404Path = path.join(current, '/404.html')
    notFoundResponse = yield fs.readFile(custom404Path, 'utf-8')
  } catch (err) {}

  // Don't allow rendering ignored files
  const ignored = !ignoredFiles.every(item => {
    return !pathname.includes(item)
  })

  if (ignored || (!assetRequest && related.indexOf(current) !== 0)) {
    return micro.send(res, 404, notFoundResponse)
  }

  const relatedExists = yield fs.exists(related)

  if (!relatedExists && !flags.single) {
    return micro.send(res, 404, notFoundResponse)
  }

  const streamOptions = {}

  if (flags.cache) {
    streamOptions.maxAge = flags.cache
  } else if (flags.cache === 0) {
    // Disable the cache control by `send`, as there's no support for `no-cache`.
    // Set header manually.
    streamOptions.cacheControl = false
    res.setHeader('Cache-Control', 'no-cache')
  } else if (flags.single) {
    // Cache assets of single page applications for a day.
    // Later in the code, we'll define that `index.html` never
    // gets cached!
    streamOptions.maxAge = 86400000
  }

  // Check if directory
  if (relatedExists && (yield pathType.dir(related))) {
    // Normalize path to trailing slash
    // Otherwise problems like #70 will occur
    const url = parse(req.url)

    if (url.pathname.substr(-1) !== '/') {
      url.pathname += '/'
      const newPath = format(url)

      res.writeHead(302, {
        Location: newPath
      })

      res.end()
      return
    }

    let indexPath = path.join(related, '/index.html')
    res.setHeader('Content-Type', mime.contentType(path.extname(indexPath)))

    if (!(yield fs.exists(indexPath))) {
      // Try to render the current directory's content
      const port = flags.port || req.socket.localPort
      const renderedDir = yield renderDirectory(
        port,
        current,
        related,
        ignoredFiles
      )

      // If it works, send the directory listing to the user
      // If is treeless, stepover
      if (renderedDir && !flags.treeless) {
        return micro.send(res, 200, renderedDir)
      }

      // And if it doesn't, see if it's a single page application
      // If that's not true either, send an error
      if (!flags.single) {
        return micro.send(res, 404, notFoundResponse)
      }

      // But IF IT IS true, then this is a hit to a directory with no `index.html`
      // Load the SPA's root index file
      indexPath = path.join(current, '/index.html')
    }

    if (flags.single && indexPath === path.join(current, '/index.html')) {
      // Don't cache the `index.html` file for single page applications
      streamOptions.maxAge = 0
    }

    return stream(req, indexPath, streamOptions).pipe(res)
  }

  // Serve `index.html` for single page applications if:
  // - The path does not match any file or directory OR
  // - The path matches exactly `/index.html`
  if (
    flags.single &&
    (!relatedExists || related === path.join(current, '/index.html'))
  ) {
    // Don't cache the `index.html` file for single page applications
    streamOptions.maxAge = 0

    // Load `index.html` and send it to the client
    const indexPath = path.join(current, '/index.html')
    return stream(req, indexPath, streamOptions).pipe(res)
  }

  // TODO remove the change below when https://github.com/zeit/serve/issues/303 is fixed.
  //
  // Details:
  // Register the mime type for `.wasm` files. This change is currently not
  // possible on upstream because of an unrelated issue. Once `mime` is
  // updated, this line can be removed.
  // https://github.com/pillarjs/send/pull/154
  stream.mime.types.wasm = 'application/wasm'
  stream.mime.extensions['application/wasm'] = 'wasm'

  // Serve files without a mime type as html for SPA, and text for non SPA
  // eslint-disable-next-line camelcase
  stream.mime.default_type = flags.single ? 'text/html' : 'text/plain'

  return stream(
    req,
    related,
    Object.assign(
      {
        dotfiles: 'allow'
      },
      streamOptions
    )
  ).pipe(res)
})
<commit_msg>Ensure `0` cache value triggers no cache (#339)<commit_after>// Native
const path = require('path')
const { parse, format } = require('url')

// Packages
const micro = require('micro')
const auth = require('basic-auth')
const { red } = require('chalk')
const fs = require('fs-extra')
const pathType = require('path-type')
const mime = require('mime-types')
const stream = require('send')
const { coroutine } = require('bluebird')
const isPathInside = require('path-is-inside')

// Utilities
const renderDirectory = require('./render')

module.exports = coroutine(function*(req, res, flags, current, ignoredFiles) {
  const headers = {}

  if (flags.cors) {
    headers['Access-Control-Allow-Origin'] = '*'
    headers['Access-Control-Allow-Headers'] =
      'Origin, X-Requested-With, Content-Type, Accept, Range'
  }

  for (const header in headers) {
    if (!{}.hasOwnProperty.call(headers, header)) {
      continue
    }

    res.setHeader(header, headers[header])
  }

  if (flags.auth) {
    const credentials = auth(req)

    if (!process.env.SERVE_USER || !process.env.SERVE_PASSWORD) {
      const error =
        'The environment variables "SERVE_USER" ' +
        'and/or "SERVE_PASSWORD" are missing!'
      console.error(red(error))

      // eslint-disable-next-line unicorn/no-process-exit
      process.exit(1)
    }

    if (
      !credentials ||
      credentials.name !== process.env.SERVE_USER ||
      credentials.pass !== process.env.SERVE_PASSWORD
    ) {
      res.statusCode = 401
      res.setHeader('WWW-Authenticate', 'Basic realm="User Visible Realm"')
      return micro.send(res, 401, 'Access Denied')
    }
  }

  const { pathname } = parse(req.url)
  const { ASSET_DIR } = process.env

  let related = path.parse(path.join(current, pathname))
  let assetRequest = false

  if (path.basename(related.dir) === ASSET_DIR) {
    assetRequest = true
    related = decodeURIComponent(path.join(__dirname, '/../assets/styles.css'))
  } else {
    related = decodeURIComponent(path.format(related))

    const relatedResolved = path.resolve(related)
    const relatedCurrent = path.resolve(current)

    const isSame = relatedResolved === relatedCurrent

    if (!isSame && !isPathInside(relatedResolved, relatedCurrent)) {
      return micro.send(res, 400, 'Bad Request')
    }
  }

  let notFoundResponse = 'Not Found'

  try {
    const custom404Path = path.join(current, '/404.html')
    notFoundResponse = yield fs.readFile(custom404Path, 'utf-8')
  } catch (err) {}

  // Don't allow rendering ignored files
  const ignored = !ignoredFiles.every(item => {
    return !pathname.includes(item)
  })

  if (ignored || (!assetRequest && related.indexOf(current) !== 0)) {
    return micro.send(res, 404, notFoundResponse)
  }

  const relatedExists = yield fs.exists(related)

  if (!relatedExists && !flags.single) {
    return micro.send(res, 404, notFoundResponse)
  }

  const streamOptions = {}
  const cacheValue = parseInt(flags.cache, 10)

  if (cacheValue > 0) {
    streamOptions.maxAge = flags.cache
  } else if (cacheValue === 0) {
    // Disable the cache control by `send`, as there's no support for `no-cache`.
    // Set header manually.
    streamOptions.cacheControl = false
    res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate')
  } else if (flags.single) {
    // Cache assets of single page applications for a day.
    // Later in the code, we'll define that `index.html` never
    // gets cached!
    streamOptions.maxAge = 86400000
  }

  // Check if directory
  if (relatedExists && (yield pathType.dir(related))) {
    // Normalize path to trailing slash
    // Otherwise problems like #70 will occur
    const url = parse(req.url)

    if (url.pathname.substr(-1) !== '/') {
      url.pathname += '/'
      const newPath = format(url)

      res.writeHead(302, {
        Location: newPath
      })

      res.end()
      return
    }

    let indexPath = path.join(related, '/index.html')
    res.setHeader('Content-Type', mime.contentType(path.extname(indexPath)))

    if (!(yield fs.exists(indexPath))) {
      // Try to render the current directory's content
      const port = flags.port || req.socket.localPort
      const renderedDir = yield renderDirectory(
        port,
        current,
        related,
        ignoredFiles
      )

      // If it works, send the directory listing to the user
      // If is treeless, stepover
      if (renderedDir && !flags.treeless) {
        return micro.send(res, 200, renderedDir)
      }

      // And if it doesn't, see if it's a single page application
      // If that's not true either, send an error
      if (!flags.single) {
        return micro.send(res, 404, notFoundResponse)
      }

      // But IF IT IS true, then this is a hit to a directory with no `index.html`
      // Load the SPA's root index file
      indexPath = path.join(current, '/index.html')
    }

    if (flags.single && indexPath === path.join(current, '/index.html')) {
      // Don't cache the `index.html` file for single page applications
      streamOptions.maxAge = 0
    }

    return stream(req, indexPath, streamOptions).pipe(res)
  }

  // Serve `index.html` for single page applications if:
  // - The path does not match any file or directory OR
  // - The path matches exactly `/index.html`
  if (
    flags.single &&
    (!relatedExists || related === path.join(current, '/index.html'))
  ) {
    // Don't cache the `index.html` file for single page applications
    streamOptions.maxAge = 0

    // Load `index.html` and send it to the client
    const indexPath = path.join(current, '/index.html')
    return stream(req, indexPath, streamOptions).pipe(res)
  }

  // TODO remove the change below when https://github.com/zeit/serve/issues/303 is fixed.
  //
  // Details:
  // Register the mime type for `.wasm` files. This change is currently not
  // possible on upstream because of an unrelated issue. Once `mime` is
  // updated, this line can be removed.
  // https://github.com/pillarjs/send/pull/154
  stream.mime.types.wasm = 'application/wasm'
  stream.mime.extensions['application/wasm'] = 'wasm'

  // Serve files without a mime type as html for SPA, and text for non SPA
  // eslint-disable-next-line camelcase
  stream.mime.default_type = flags.single ? 'text/html' : 'text/plain'

  return stream(
    req,
    related,
    Object.assign(
      {
        dotfiles: 'allow'
      },
      streamOptions
    )
  ).pipe(res)
})
<|endoftext|>