text: 'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  
  var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
  
  var _fs = require('fs');
  
  var _fs2 = _interopRequireDefault(_fs);
  
  var _http = require('http');
  
  var _http2 = _interopRequireDefault(_http);
  
  var _https = require('https');
  
  var _https2 = _interopRequireDefault(_https);
  
  var _events = require('events');
  
  var _events2 = _interopRequireDefault(_events);
  
  var _url = require('url');
  
  var _client = require('./client');
  
  var _client2 = _interopRequireDefault(_client);
  
  var _package = require('../package.json');
  
  var _package2 = _interopRequireDefault(_package);
  
  var _any = require('body/any');
  
  var _any2 = _interopRequireDefault(_any);
  
  var _qs = require('qs');
  
  var _qs2 = _interopRequireDefault(_qs);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  
  function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
  
  function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
  
  var debug = require('debug')('tinylr:server');
  
  var CONTENT_TYPE = 'content-type';
  var FORM_TYPE = 'application/x-www-form-urlencoded';
  
  var Server = function (_events$EventEmitter) {
    _inherits(Server, _events$EventEmitter);
  
    function Server() {
      var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
  
      _classCallCheck(this, Server);
  
      var _this = _possibleConstructorReturn(this, (Server.__proto__ || Object.getPrototypeOf(Server)).call(this));
  
      _this.options = options;
  
      options.livereload = options.livereload || require.resolve('livereload-js/dist/livereload.js');
  
      // todo: change falsy check to allow 0 for random port
      options.port = parseInt(options.port || 35729, 10);
  
      if (options.errorListener) {
        _this.errorListener = options.errorListener;
      }
  
      _this.clients = {};
      _this.configure(options.app);
      _this.routes(options.app);
      return _this;
    }
  
    _createClass(Server, [{
      key: 'routes',
      value: function routes() {
        if (!this.options.dashboard) {
          this.on('GET /', this.index.bind(this));
        }
  
        this.on('GET /changed', this.changed.bind(this));
        this.on('POST /changed', this.changed.bind(this));
        this.on('POST /alert', this.alert.bind(this));
        this.on('GET /livereload.js', this.livereload.bind(this));
        this.on('GET /kill', this.close.bind(this));
      }
    }, {
      key: 'configure',
      value: function configure(app) {
        var _this2 = this;
  
        debug('Configuring %s', app ? 'connect / express application' : 'HTTP server');
  
        var handler = this.options.handler || this.handler;
  
        if (!app) {
          if (this.options.key && this.options.cert || this.options.pfx) {
            this.server = _https2.default.createServer(this.options, handler.bind(this));
          } else {
            this.server = _http2.default.createServer(handler.bind(this));
          }
  
          this.server.on('upgrade', this.websocketify.bind(this));
          this.server.on('error', this.error.bind(this));
          return this;
        }
  
        this.app = app;
        this.app.listen = function (port, done) {
          done = done || function () {};
          if (port !== _this2.options.port) {
            debug('Warn: LiveReload port is not standard (%d). You are listening on %d', _this2.options.port, port);
            debug('You\'ll need to rely on the LiveReload snippet');
            debug('> http://feedback.livereload.com/knowledgebase/articles/86180-how-do-i-add-the-script-tag-manually-');
          }
  
          var srv = _this2.server = _http2.default.createServer(app);
          srv.on('upgrade', _this2.websocketify.bind(_this2));
          srv.on('error', _this2.error.bind(_this2));
          srv.on('close', _this2.close.bind(_this2));
          return srv.listen(port, done);
        };
  
        return this;
      }
    }, {
      key: 'handler',
      value: function handler(req, res, next) {
        var _this3 = this;
  
        var middleware = typeof next === 'function';
        debug('LiveReload handler %s (middleware: %s)', req.url, middleware ? 'on' : 'off');
  
        next = next || this.defaultHandler.bind(this, res);
        req.headers[CONTENT_TYPE] = req.headers[CONTENT_TYPE] || FORM_TYPE;
        return (0, _any2.default)(req, res, function (err, body) {
          if (err) return next(err);
          req.body = body;
  
          if (!req.query) {
            req.query = req.url.indexOf('?') !== -1 ? _qs2.default.parse((0, _url.parse)(req.url).query) : {};
          }
  
          return _this3.handle(req, res, next);
        });
      }
    }, {
      key: 'index',
      value: function index(req, res) {
        res.setHeader('Content-Type', 'application/json');
        res.write(JSON.stringify({
          tinylr: 'Welcome',
          version: _package2.default.version
        }));
  
        res.end();
      }
    }, {
      key: 'handle',
      value: function handle(req, res, next) {
        var url = (0, _url.parse)(req.url);
        debug('Request:', req.method, url.href);
        var middleware = typeof next === 'function';
  
        // do the routing
        var route = req.method + ' ' + url.pathname;
        var respond = this.emit(route, req, res);
        if (respond) return;
  
        if (middleware) return next();
  
        // Only apply content-type on non middleware setup #70
        return this.notFound(res);
      }
    }, {
      key: 'defaultHandler',
      value: function defaultHandler(res, err) {
        if (!err) return this.notFound(res);
  
        this.error(err);
        res.setHeader('Content-Type', 'text/plain');
        res.statusCode = 500;
        res.end('Error: ' + err.stack);
      }
    }, {
      key: 'notFound',
      value: function notFound(res) {
        res.setHeader('Content-Type', 'application/json');
        res.writeHead(404);
        res.write(JSON.stringify({
          error: 'not_found',
          reason: 'no such route'
        }));
        res.end();
      }
    }, {
      key: 'websocketify',
      value: function websocketify(req, socket, head) {
        var _this4 = this;
  
        var client = new _client2.default(req, socket, head, this.options);
        this.clients[client.id] = client;
  
        // handle socket error to prevent possible app crash, such as ECONNRESET
        socket.on('error', function (e) {
          // ignore frequent ECONNRESET error (seems inevitable when refresh)
          if (e.code === 'ECONNRESET') return;
          _this4.error.apply(_this4, e);
        });
  
        client.once('info', function (data) {
          debug('Create client %s (url: %s)', data.id, data.url);
          _this4.emit('MSG /create', data.id, data.url);
        });
  
        client.once('end', function () {
          debug('Destroy client %s (url: %s)', client.id, client.url);
          _this4.emit('MSG /destroy', client.id, client.url);
          delete _this4.clients[client.id];
        });
      }
    }, {
      key: 'listen',
      value: function listen(port, host, fn) {
        port = port || this.options.port;
  
        // Last used port for error display
        this.port = port;
  
        if (typeof host === 'function') {
          fn = host;
          host = undefined;
        }
  
        this.server.listen(port, host, fn);
      }
    }, {
      key: 'close',
      value: function close(req, res) {
        Object.keys(this.clients).forEach(function (id) {
          this.clients[id].close();
        }, this);
  
        if (this.server._handle) this.server.close(this.emit.bind(this, 'close'));
  
        if (res) res.end();
      }
    }, {
      key: 'error',
      value: function error(e) {
        if (this.errorListener) {
          this.errorListener(e);
          return;
        }
  
        console.error();
        console.error('... Uhoh. Got error %s ...', e.message);
        console.error(e.stack);
  
        if (e.code !== 'EADDRINUSE') return;
        console.error();
        console.error('You already have a server listening on %s', this.port);
        console.error('You should stop it and try again.');
        console.error();
      }
  
      // Routes
  
    }, {
      key: 'livereload',
      value: function livereload(req, res) {
        res.setHeader('Content-Type', 'application/javascript');
        _fs2.default.createReadStream(this.options.livereload).pipe(res);
      }
    }, {
      key: 'changed',
      value: function changed(req, res) {
        var files = this.param('files', req);
  
        debug('Changed event (Files: %s)', files.join(' '));
        var clients = this.notifyClients(files);
  
        if (!res) return;
  
        res.setHeader('Content-Type', 'application/json');
        res.write(JSON.stringify({
          clients: clients,
          files: files
        }));
  
        res.end();
      }
    }, {
      key: 'alert',
      value: function alert(req, res) {
        var message = this.param('message', req);
  
        debug('Alert event (Message: %s)', message);
        var clients = this.alertClients(message);
  
        if (!res) return;
  
        res.setHeader('Content-Type', 'application/json');
        res.write(JSON.stringify({
          clients: clients,
          message: message
        }));
  
        res.end();
      }
    }, {
      key: 'notifyClients',
      value: function notifyClients(files) {
        var clients = Object.keys(this.clients).map(function (id) {
          var client = this.clients[id];
          debug('Reloading client %s (url: %s)', client.id, client.url);
          client.reload(files);
          return {
            id: client.id,
            url: client.url
          };
        }, this);
  
        return clients;
      }
    }, {
      key: 'alertClients',
      value: function alertClients(message) {
        var clients = Object.keys(this.clients).map(function (id) {
          var client = this.clients[id];
          debug('Alert client %s (url: %s)', client.id, client.url);
          client.alert(message);
          return {
            id: client.id,
            url: client.url
          };
        }, this);
  
        return clients;
      }
  
      // Lookup param from body / params / query.
  
    }, {
      key: 'param',
      value: function param(name, req) {
        var param = void 0;
        if (req.body && req.body[name]) param = req.body[name];else if (req.params && req.params[name]) param = req.params[name];else if (req.query && req.query[name]) param = req.query[name];
  
        // normalize files array
        if (name === 'files') {
          param = Array.isArray(param) ? param : typeof param === 'string' ? param.split(/[\s,]/) : [];
        }
  
        return param;
      }
    }]);
  
    return Server;
  }(_events2.default.EventEmitter);
  
  exports.default = Server;
  module.exports = exports['default'];