Index: frontend/node_modules/faye-websocket/lib/faye/eventsource.js
===================================================================
--- frontend/node_modules/faye-websocket/lib/faye/eventsource.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/faye-websocket/lib/faye/eventsource.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,133 @@
+'use strict';
+
+var Stream      = require('stream').Stream,
+    util        = require('util'),
+    driver      = require('websocket-driver'),
+    Headers     = require('websocket-driver/lib/websocket/driver/headers'),
+    API         = require('./websocket/api'),
+    EventTarget = require('./websocket/api/event_target'),
+    Event       = require('./websocket/api/event');
+
+var EventSource = function(request, response, options) {
+  this.writable = true;
+  options = options || {};
+
+  this._stream = response.socket;
+  this._ping   = options.ping  || this.DEFAULT_PING;
+  this._retry  = options.retry || this.DEFAULT_RETRY;
+
+  var scheme       = driver.isSecureRequest(request) ? 'https:' : 'http:';
+  this.url         = scheme + '//' + request.headers.host + request.url;
+  this.lastEventId = request.headers['last-event-id'] || '';
+  this.readyState  = API.CONNECTING;
+
+  var headers = new Headers(),
+      self    = this;
+
+  if (options.headers) {
+    for (var key in options.headers) headers.set(key, options.headers[key]);
+  }
+
+  if (!this._stream || !this._stream.writable) return;
+  process.nextTick(function() { self._open() });
+
+  this._stream.setTimeout(0);
+  this._stream.setNoDelay(true);
+
+  var handshake = 'HTTP/1.1 200 OK\r\n' +
+                  'Content-Type: text/event-stream\r\n' +
+                  'Cache-Control: no-cache, no-store\r\n' +
+                  'Connection: close\r\n' +
+                  headers.toString() +
+                  '\r\n' +
+                  'retry: ' + Math.floor(this._retry * 1000) + '\r\n\r\n';
+
+  this._write(handshake);
+
+  this._stream.on('drain', function() { self.emit('drain') });
+
+  if (this._ping)
+    this._pingTimer = setInterval(function() { self.ping() }, this._ping * 1000);
+
+  ['error', 'end'].forEach(function(event) {
+    self._stream.on(event, function() { self.close() });
+  });
+};
+util.inherits(EventSource, Stream);
+
+EventSource.isEventSource = function(request) {
+  if (request.method !== 'GET') return false;
+  var accept = (request.headers.accept || '').split(/\s*,\s*/);
+  return accept.indexOf('text/event-stream') >= 0;
+};
+
+var instance = {
+  DEFAULT_PING:   10,
+  DEFAULT_RETRY:  5,
+
+  _write: function(chunk) {
+    if (!this.writable) return false;
+    try {
+      return this._stream.write(chunk, 'utf8');
+    } catch (e) {
+      return false;
+    }
+  },
+
+  _open: function() {
+    if (this.readyState !== API.CONNECTING) return;
+
+    this.readyState = API.OPEN;
+
+    var event = new Event('open');
+    event.initEvent('open', false, false);
+    this.dispatchEvent(event);
+  },
+
+  write: function(message) {
+    return this.send(message);
+  },
+
+  end: function(message) {
+    if (message !== undefined) this.write(message);
+    this.close();
+  },
+
+  send: function(message, options) {
+    if (this.readyState > API.OPEN) return false;
+
+    message = String(message).replace(/(\r\n|\r|\n)/g, '$1data: ');
+    options = options || {};
+
+    var frame = '';
+    if (options.event) frame += 'event: ' + options.event + '\r\n';
+    if (options.id)    frame += 'id: '    + options.id    + '\r\n';
+    frame += 'data: ' + message + '\r\n\r\n';
+
+    return this._write(frame);
+  },
+
+  ping: function() {
+    return this._write(':\r\n\r\n');
+  },
+
+  close: function() {
+    if (this.readyState > API.OPEN) return false;
+
+    this.readyState = API.CLOSED;
+    this.writable = false;
+    if (this._pingTimer) clearInterval(this._pingTimer);
+    if (this._stream) this._stream.end();
+
+    var event = new Event('close');
+    event.initEvent('close', false, false);
+    this.dispatchEvent(event);
+
+    return true;
+  }
+};
+
+for (var method in instance) EventSource.prototype[method] = instance[method];
+for (var key in EventTarget) EventSource.prototype[key] = EventTarget[key];
+
+module.exports = EventSource;
Index: frontend/node_modules/faye-websocket/lib/faye/websocket.js
===================================================================
--- frontend/node_modules/faye-websocket/lib/faye/websocket.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/faye-websocket/lib/faye/websocket.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,47 @@
+// API references:
+//
+// * https://html.spec.whatwg.org/multipage/comms.html#network
+// * https://dom.spec.whatwg.org/#interface-eventtarget
+// * https://dom.spec.whatwg.org/#interface-event
+
+'use strict';
+
+var util   = require('util'),
+    driver = require('websocket-driver'),
+    API    = require('./websocket/api');
+
+var WebSocket = function(request, socket, body, protocols, options) {
+  options = options || {};
+
+  this._stream = socket;
+  this._driver = driver.http(request, { maxLength: options.maxLength, protocols: protocols });
+
+  var self = this;
+  if (!this._stream || !this._stream.writable) return;
+  if (!this._stream.readable) return this._stream.end();
+
+  var catchup = function() { self._stream.removeListener('data', catchup) };
+  this._stream.on('data', catchup);
+
+  API.call(this, options);
+
+  process.nextTick(function() {
+    self._driver.start();
+    self._driver.io.write(body);
+  });
+};
+util.inherits(WebSocket, API);
+
+WebSocket.isWebSocket = function(request) {
+  return driver.isWebSocket(request);
+};
+
+WebSocket.validateOptions = function(options, validKeys) {
+  driver.validateOptions(options, validKeys);
+};
+
+WebSocket.WebSocket   = WebSocket;
+WebSocket.Client      = require('./websocket/client');
+WebSocket.EventSource = require('./eventsource');
+
+module.exports        = WebSocket;
Index: frontend/node_modules/faye-websocket/lib/faye/websocket/api.js
===================================================================
--- frontend/node_modules/faye-websocket/lib/faye/websocket/api.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/faye-websocket/lib/faye/websocket/api.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,199 @@
+'use strict';
+
+var Stream      = require('stream').Stream,
+    util        = require('util'),
+    driver      = require('websocket-driver'),
+    EventTarget = require('./api/event_target'),
+    Event       = require('./api/event');
+
+var API = function(options) {
+  options = options || {};
+  driver.validateOptions(options, ['headers', 'extensions', 'maxLength', 'ping', 'proxy', 'tls', 'ca']);
+
+  this.readable = this.writable = true;
+
+  var headers = options.headers;
+  if (headers) {
+    for (var name in headers) this._driver.setHeader(name, headers[name]);
+  }
+
+  var extensions = options.extensions;
+  if (extensions) {
+    [].concat(extensions).forEach(this._driver.addExtension, this._driver);
+  }
+
+  this._ping          = options.ping;
+  this._pingId        = 0;
+  this.readyState     = API.CONNECTING;
+  this.bufferedAmount = 0;
+  this.protocol       = '';
+  this.url            = this._driver.url;
+  this.version        = this._driver.version;
+
+  var self = this;
+
+  this._driver.on('open',    function(e) { self._open() });
+  this._driver.on('message', function(e) { self._receiveMessage(e.data) });
+  this._driver.on('close',   function(e) { self._beginClose(e.reason, e.code) });
+
+  this._driver.on('error', function(error) {
+    self._emitError(error.message);
+  });
+  this.on('error', function() {});
+
+  this._driver.messages.on('drain', function() {
+    self.emit('drain');
+  });
+
+  if (this._ping)
+    this._pingTimer = setInterval(function() {
+      self._pingId += 1;
+      self.ping(self._pingId.toString());
+    }, this._ping * 1000);
+
+  this._configureStream();
+
+  if (!this._proxy) {
+    this._stream.pipe(this._driver.io);
+    this._driver.io.pipe(this._stream);
+  }
+};
+util.inherits(API, Stream);
+
+API.CONNECTING = 0;
+API.OPEN       = 1;
+API.CLOSING    = 2;
+API.CLOSED     = 3;
+
+API.CLOSE_TIMEOUT = 30000;
+
+var instance = {
+  write: function(data) {
+    return this.send(data);
+  },
+
+  end: function(data) {
+    if (data !== undefined) this.send(data);
+    this.close();
+  },
+
+  pause: function() {
+    return this._driver.messages.pause();
+  },
+
+  resume: function() {
+    return this._driver.messages.resume();
+  },
+
+  send: function(data) {
+    if (this.readyState > API.OPEN) return false;
+    if (!(data instanceof Buffer)) data = String(data);
+    return this._driver.messages.write(data);
+  },
+
+  ping: function(message, callback) {
+    if (this.readyState > API.OPEN) return false;
+    return this._driver.ping(message, callback);
+  },
+
+  close: function(code, reason) {
+    if (code === undefined) code = 1000;
+    if (reason === undefined) reason = '';
+
+    if (code !== 1000 && (code < 3000 || code > 4999))
+      throw new Error("Failed to execute 'close' on WebSocket: " +
+                      "The code must be either 1000, or between 3000 and 4999. " +
+                      code + " is neither.");
+
+    if (this.readyState < API.CLOSING) {
+      var self = this;
+      this._closeTimer = setTimeout(function() {
+        self._beginClose('', 1006);
+      }, API.CLOSE_TIMEOUT);
+    }
+
+    if (this.readyState !== API.CLOSED) this.readyState = API.CLOSING;
+
+    this._driver.close(reason, code);
+  },
+
+  _configureStream: function() {
+    var self = this;
+
+    this._stream.setTimeout(0);
+    this._stream.setNoDelay(true);
+
+    ['close', 'end'].forEach(function(event) {
+      this._stream.on(event, function() { self._finalizeClose() });
+    }, this);
+
+    this._stream.on('error', function(error) {
+      self._emitError('Network error: ' + self.url + ': ' + error.message);
+      self._finalizeClose();
+    });
+  },
+
+  _open: function() {
+    if (this.readyState !== API.CONNECTING) return;
+
+    this.readyState = API.OPEN;
+    this.protocol = this._driver.protocol || '';
+
+    var event = new Event('open');
+    event.initEvent('open', false, false);
+    this.dispatchEvent(event);
+  },
+
+  _receiveMessage: function(data) {
+    if (this.readyState > API.OPEN) return false;
+
+    if (this.readable) this.emit('data', data);
+
+    var event = new Event('message', { data: data });
+    event.initEvent('message', false, false);
+    this.dispatchEvent(event);
+  },
+
+  _emitError: function(message) {
+    if (this.readyState >= API.CLOSING) return;
+
+    var event = new Event('error', { message: message });
+    event.initEvent('error', false, false);
+    this.dispatchEvent(event);
+  },
+
+  _beginClose: function(reason, code) {
+    if (this.readyState === API.CLOSED) return;
+    this.readyState = API.CLOSING;
+    this._closeParams = [reason, code];
+
+    if (this._stream) {
+      this._stream.destroy();
+      if (!this._stream.readable) this._finalizeClose();
+    }
+  },
+
+  _finalizeClose: function() {
+    if (this.readyState === API.CLOSED) return;
+    this.readyState = API.CLOSED;
+
+    if (this._closeTimer) clearTimeout(this._closeTimer);
+    if (this._pingTimer) clearInterval(this._pingTimer);
+    if (this._stream) this._stream.end();
+
+    if (this.readable) this.emit('end');
+    this.readable = this.writable = false;
+
+    var reason = this._closeParams ? this._closeParams[0] : '',
+        code   = this._closeParams ? this._closeParams[1] : 1006;
+
+    var event = new Event('close', { code: code, reason: reason });
+    event.initEvent('close', false, false);
+    this.dispatchEvent(event);
+  }
+};
+
+for (var method in instance) API.prototype[method] = instance[method];
+for (var key in EventTarget) API.prototype[key] = EventTarget[key];
+
+module.exports = API;
Index: frontend/node_modules/faye-websocket/lib/faye/websocket/api/event.js
===================================================================
--- frontend/node_modules/faye-websocket/lib/faye/websocket/api/event.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/faye-websocket/lib/faye/websocket/api/event.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,22 @@
+'use strict';
+
+var Event = function(eventType, options) {
+  this.type = eventType;
+  for (var key in options)
+    this[key] = options[key];
+};
+
+Event.prototype.initEvent = function(eventType, canBubble, cancelable) {
+  this.type       = eventType;
+  this.bubbles    = canBubble;
+  this.cancelable = cancelable;
+};
+
+Event.prototype.stopPropagation = function() {};
+Event.prototype.preventDefault  = function() {};
+
+Event.CAPTURING_PHASE = 1;
+Event.AT_TARGET       = 2;
+Event.BUBBLING_PHASE  = 3;
+
+module.exports = Event;
Index: frontend/node_modules/faye-websocket/lib/faye/websocket/api/event_target.js
===================================================================
--- frontend/node_modules/faye-websocket/lib/faye/websocket/api/event_target.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/faye-websocket/lib/faye/websocket/api/event_target.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,30 @@
+'use strict';
+
+var Event = require('./event');
+
+var EventTarget = {
+  onopen:     null,
+  onmessage:  null,
+  onerror:    null,
+  onclose:    null,
+
+  addEventListener: function(eventType, listener, useCapture) {
+    this.on(eventType, listener);
+  },
+
+  removeEventListener: function(eventType, listener, useCapture) {
+    this.removeListener(eventType, listener);
+  },
+
+  dispatchEvent: function(event) {
+    event.target = event.currentTarget = this;
+    event.eventPhase = Event.AT_TARGET;
+
+    if (this['on' + event.type])
+      this['on' + event.type](event);
+
+    this.emit(event.type, event);
+  }
+};
+
+module.exports = EventTarget;
Index: frontend/node_modules/faye-websocket/lib/faye/websocket/client.js
===================================================================
--- frontend/node_modules/faye-websocket/lib/faye/websocket/client.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/faye-websocket/lib/faye/websocket/client.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,90 @@
+'use strict';
+
+var util   = require('util'),
+    net    = require('net'),
+    tls    = require('tls'),
+    url    = require('url'),
+    driver = require('websocket-driver'),
+    API    = require('./api'),
+    Event  = require('./api/event');
+
+var DEFAULT_PORTS    = { 'http:': 80, 'https:': 443, 'ws:':80, 'wss:': 443 },
+    SECURE_PROTOCOLS = ['https:', 'wss:'];
+
+var Client = function(_url, protocols, options) {
+  options = options || {};
+
+  this.url     = _url;
+  this._driver = driver.client(this.url, { maxLength: options.maxLength, protocols: protocols });
+
+  ['open', 'error'].forEach(function(event) {
+    this._driver.on(event, function() {
+      self.headers    = self._driver.headers;
+      self.statusCode = self._driver.statusCode;
+    });
+  }, this);
+
+  var proxy      = options.proxy || {},
+      endpoint   = url.parse(proxy.origin || this.url),
+      port       = endpoint.port || DEFAULT_PORTS[endpoint.protocol],
+      secure     = SECURE_PROTOCOLS.indexOf(endpoint.protocol) >= 0,
+      onConnect  = function() { self._onConnect() },
+      netOptions = options.net || {},
+      originTLS  = options.tls || {},
+      socketTLS  = proxy.origin ? (proxy.tls || {}) : originTLS,
+      self       = this;
+
+  netOptions.host = socketTLS.host = endpoint.hostname;
+  netOptions.port = socketTLS.port = port;
+
+  originTLS.ca = originTLS.ca || options.ca;
+  socketTLS.servername = socketTLS.servername || endpoint.hostname;
+
+  this._stream = secure
+               ? tls.connect(socketTLS, onConnect)
+               : net.connect(netOptions, onConnect);
+
+  if (proxy.origin) this._configureProxy(proxy, originTLS);
+
+  API.call(this, options);
+};
+util.inherits(Client, API);
+
+Client.prototype._onConnect = function() {
+  var worker = this._proxy || this._driver;
+  worker.start();
+};
+
+Client.prototype._configureProxy = function(proxy, originTLS) {
+  var uri    = url.parse(this.url),
+      secure = SECURE_PROTOCOLS.indexOf(uri.protocol) >= 0,
+      self   = this,
+      name;
+
+  this._proxy = this._driver.proxy(proxy.origin);
+
+  if (proxy.headers) {
+    for (name in proxy.headers) this._proxy.setHeader(name, proxy.headers[name]);
+  }
+
+  this._proxy.pipe(this._stream, { end: false });
+  this._stream.pipe(this._proxy);
+
+  this._proxy.on('connect', function() {
+    if (secure) {
+      var options = { socket: self._stream, servername: uri.hostname };
+      for (name in originTLS) options[name] = originTLS[name];
+      self._stream = tls.connect(options);
+      self._configureStream();
+    }
+    self._driver.io.pipe(self._stream);
+    self._stream.pipe(self._driver.io);
+    self._driver.start();
+  });
+
+  this._proxy.on('error', function(error) {
+    self._driver.emit('error', error);
+  });
+};
+
+module.exports = Client;
