Index: frontend/node_modules/faye-websocket/CHANGELOG.md
===================================================================
--- frontend/node_modules/faye-websocket/CHANGELOG.md	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/faye-websocket/CHANGELOG.md	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,139 @@
+### 0.11.4 / 2021-05-24
+
+- Prevent the client hanging if `close()` is called when already closing
+
+### 0.11.3 / 2019-06-10
+
+- Fix a race condition that caused a timeout not to be cancelled immediately
+  when the WebSocket is closed
+- Change license from MIT to Apache 2.0
+
+### 0.11.2 / 2019-06-10
+
+(This version was pulled due to an error when publishing)
+
+### 0.11.1 / 2017-01-22
+
+- Forcibly close the I/O stream after a timeout if the peer does not respond
+  after calling `close()`
+
+### 0.11.0 / 2016-02-24
+
+- Introduce a `net` option to the `Client` class for setting things like, say,
+  `servername`
+
+### 0.10.0 / 2015-07-08
+
+- Add the standard `code` and `reason` parameters to the `close` method
+
+### 0.9.4 / 2015-03-08
+
+- Don't send input to the driver before `start()` is called
+
+### 0.9.3 / 2015-02-19
+
+- Make sure the TCP socket is not left open when closing the connection
+
+### 0.9.2 / 2014-12-21
+
+- Only emit `error` once, and don't emit it after `close`
+
+### 0.9.1 / 2014-12-18
+
+- Check that all options to the WebSocket constructor are recognized
+
+### 0.9.0 / 2014-12-13
+
+- Allow protocol extensions to be passed into websocket-extensions
+
+### 0.8.1 / 2014-11-12
+
+- Send the correct hostname when upgrading a connection to TLS
+
+### 0.8.0 / 2014-11-08
+
+- Support connections via HTTP proxies
+- Close the connection cleanly if we're still waiting for a handshake response
+
+### 0.7.3 / 2014-10-04
+
+- Allow sockets to be closed when they are in any state other than `CLOSED`
+
+### 0.7.2 / 2013-12-29
+
+- Make sure the `close` event is emitted by clients on Node v0.10
+
+### 0.7.1 / 2013-12-03
+
+- Support the `maxLength` websocket-driver option
+- Make the client emit `error` events on network errors
+
+### 0.7.0 / 2013-09-09
+
+- Allow the server to send custom headers with EventSource responses
+
+### 0.6.1 / 2013-07-05
+
+- Add `ca` option to the client for specifying certificate authorities
+- Start the server driver asynchronously so that `onopen` handlers can be added
+
+### 0.6.0 / 2013-05-12
+
+- Add support for custom headers
+
+### 0.5.0 / 2013-05-05
+
+- Extract the protocol handlers into the `websocket-driver` library
+- Support the Node streaming API
+
+### 0.4.4 / 2013-02-14
+
+- Emit the `close` event if TCP is closed before CLOSE frame is acked
+
+### 0.4.3 / 2012-07-09
+
+- Add `Connection: close` to EventSource response
+- Handle situations where `request.socket` is undefined
+
+### 0.4.2 / 2012-04-06
+
+- Add WebSocket error code `1011`.
+- Handle URLs with no path correctly by sending `GET /`
+
+### 0.4.1 / 2012-02-26
+
+- Treat anything other than a `Buffer` as a string when calling `send()`
+
+### 0.4.0 / 2012-02-13
+
+- Add `ping()` method to server-side `WebSocket` and `EventSource`
+- Buffer `send()` calls until the draft-76 handshake is complete
+- Fix HTTPS problems on Node 0.7
+
+### 0.3.1 / 2012-01-16
+
+- Call `setNoDelay(true)` on `net.Socket` objects to reduce latency
+
+### 0.3.0 / 2012-01-13
+
+- Add support for `EventSource` connections
+
+### 0.2.0 / 2011-12-21
+
+- Add support for `Sec-WebSocket-Protocol` negotiation
+- Support `hixie-76` close frames and 75/76 ignored segments
+- Improve performance of HyBi parsing/framing functions
+- Decouple parsers from TCP and reduce write volume
+
+### 0.1.2 / 2011-12-05
+
+- Detect closed sockets on the server side when TCP connection breaks
+- Make `hixie-76` sockets work through HAProxy
+
+### 0.1.1 / 2011-11-30
+
+- Fix `addEventListener()` interface methods
+
+### 0.1.0 / 2011-11-27
+
+- Initial release, based on WebSocket components from Faye
Index: frontend/node_modules/faye-websocket/LICENSE.md
===================================================================
--- frontend/node_modules/faye-websocket/LICENSE.md	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/faye-websocket/LICENSE.md	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,12 @@
+Copyright 2010-2021 James Coglan
+
+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.
Index: frontend/node_modules/faye-websocket/README.md
===================================================================
--- frontend/node_modules/faye-websocket/README.md	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/faye-websocket/README.md	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,311 @@
+# faye-websocket
+
+This is a general-purpose WebSocket implementation extracted from the
+[Faye](http://faye.jcoglan.com) project. It provides classes for easily building
+WebSocket servers and clients in Node. It does not provide a server itself, but
+rather makes it easy to handle WebSocket connections within an existing
+[Node](https://nodejs.org/) application. It does not provide any abstraction
+other than the standard [WebSocket
+API](https://html.spec.whatwg.org/multipage/comms.html#network).
+
+It also provides an abstraction for handling
+[EventSource](https://html.spec.whatwg.org/multipage/comms.html#server-sent-events)
+connections, which are one-way connections that allow the server to push data to
+the client. They are based on streaming HTTP responses and can be easier to access
+via proxies than WebSockets.
+
+
+## Installation
+
+```
+$ npm install faye-websocket
+```
+
+
+## Handling WebSocket connections in Node
+
+You can handle WebSockets on the server side by listening for HTTP Upgrade
+requests, and creating a new socket for the request. This socket object exposes
+the usual WebSocket methods for receiving and sending messages. For example this
+is how you'd implement an echo server:
+
+```js
+var WebSocket = require('faye-websocket'),
+    http      = require('http');
+
+var server = http.createServer();
+
+server.on('upgrade', function(request, socket, body) {
+  if (WebSocket.isWebSocket(request)) {
+    var ws = new WebSocket(request, socket, body);
+
+    ws.on('message', function(event) {
+      ws.send(event.data);
+    });
+
+    ws.on('close', function(event) {
+      console.log('close', event.code, event.reason);
+      ws = null;
+    });
+  }
+});
+
+server.listen(8000);
+```
+
+`WebSocket` objects are also duplex streams, so you could replace the
+`ws.on('message', ...)` line with:
+
+```js
+    ws.pipe(ws);
+```
+
+Note that under certain circumstances (notably a draft-76 client connecting
+through an HTTP proxy), the WebSocket handshake will not be complete after you
+call `new WebSocket()` because the server will not have received the entire
+handshake from the client yet. In this case, calls to `ws.send()` will buffer
+the message in memory until the handshake is complete, at which point any
+buffered messages will be sent to the client.
+
+If you need to detect when the WebSocket handshake is complete, you can use the
+`onopen` event.
+
+If the connection's protocol version supports it, you can call `ws.ping()` to
+send a ping message and wait for the client's response. This method takes a
+message string, and an optional callback that fires when a matching pong message
+is received. It returns `true` if and only if a ping message was sent. If the
+client does not support ping/pong, this method sends no data and returns
+`false`.
+
+```js
+ws.ping('Mic check, one, two', function() {
+  // fires when pong is received
+});
+```
+
+
+## Using the WebSocket client
+
+The client supports both the plain-text `ws` protocol and the encrypted `wss`
+protocol, and has exactly the same interface as a socket you would use in a web
+browser. On the wire it identifies itself as `hybi-13`.
+
+```js
+var WebSocket = require('faye-websocket'),
+    ws        = new WebSocket.Client('ws://www.example.com/');
+
+ws.on('open', function(event) {
+  console.log('open');
+  ws.send('Hello, world!');
+});
+
+ws.on('message', function(event) {
+  console.log('message', event.data);
+});
+
+ws.on('close', function(event) {
+  console.log('close', event.code, event.reason);
+  ws = null;
+});
+```
+
+The WebSocket client also lets you inspect the status and headers of the
+handshake response via its `statusCode` and `headers` properties.
+
+To connect via a proxy, set the `proxy` option to the HTTP origin of the proxy,
+including any authorization information, custom headers and TLS config you
+require. Only the `origin` setting is required.
+
+```js
+var ws = new WebSocket.Client('ws://www.example.com/', [], {
+  proxy: {
+    origin:  'http://username:password@proxy.example.com',
+    headers: { 'User-Agent': 'node' },
+    tls:     { cert: fs.readFileSync('client.crt') }
+  }
+});
+```
+
+The `tls` value is an object that will be passed to
+[`tls.connect()`](https://nodejs.org/api/tls.html#tls_tls_connect_options_callback).
+
+
+## Subprotocol negotiation
+
+The WebSocket protocol allows peers to select and identify the application
+protocol to use over the connection. On the client side, you can set which
+protocols the client accepts by passing a list of protocol names when you
+construct the socket:
+
+```js
+var ws = new WebSocket.Client('ws://www.example.com/', ['irc', 'amqp']);
+```
+
+On the server side, you can likewise pass in the list of protocols the server
+supports after the other constructor arguments:
+
+```js
+var ws = new WebSocket(request, socket, body, ['irc', 'amqp']);
+```
+
+If the client and server agree on a protocol, both the client- and server-side
+socket objects expose the selected protocol through the `ws.protocol` property.
+
+
+## Protocol extensions
+
+faye-websocket is based on the
+[websocket-extensions](https://github.com/faye/websocket-extensions-node)
+framework that allows extensions to be negotiated via the
+`Sec-WebSocket-Extensions` header. To add extensions to a connection, pass an
+array of extensions to the `:extensions` option. For example, to add
+[permessage-deflate](https://github.com/faye/permessage-deflate-node):
+
+```js
+var deflate = require('permessage-deflate');
+
+var ws = new WebSocket(request, socket, body, [], { extensions: [deflate] });
+```
+
+
+## Initialization options
+
+Both the server- and client-side classes allow an options object to be passed in
+at initialization time, for example:
+
+```js
+var ws = new WebSocket(request, socket, body, protocols, options);
+var ws = new WebSocket.Client(url, protocols, options);
+```
+
+`protocols` is an array of subprotocols as described above, or `null`.
+`options` is an optional object containing any of these fields:
+
+- `extensions` - an array of
+  [websocket-extensions](https://github.com/faye/websocket-extensions-node)
+  compatible extensions, as described above
+- `headers` - an object containing key-value pairs representing HTTP headers to
+  be sent during the handshake process
+- `maxLength` - the maximum allowed size of incoming message frames, in bytes.
+  The default value is `2^26 - 1`, or 1 byte short of 64 MiB.
+- `ping` - an integer that sets how often the WebSocket should send ping frames,
+  measured in seconds
+
+The client accepts some additional options:
+
+- `proxy` - settings for a proxy as described above
+- `net` - an object containing settings for the origin server that will be
+  passed to
+  [`net.connect()`](https://nodejs.org/api/net.html#net_socket_connect_options_connectlistener)
+- `tls` - an object containing TLS settings for the origin server, this will be
+  passed to
+  [`tls.connect()`](https://nodejs.org/api/tls.html#tls_tls_connect_options_callback)
+- `ca` - (legacy) a shorthand for passing `{ tls: { ca: value } }`
+
+
+## WebSocket API
+
+Both server- and client-side `WebSocket` objects support the following API.
+
+- **`on('open', function(event) {})`** fires when the socket connection is
+  established. Event has no attributes.
+- **`on('message', function(event) {})`** fires when the socket receives a
+  message. Event has one attribute, **`data`**, which is either a `String` (for
+  text frames) or a `Buffer` (for binary frames).
+- **`on('error', function(event) {})`** fires when there is a protocol error due
+  to bad data sent by the other peer. This event is purely informational, you do
+  not need to implement error recover.
+- **`on('close', function(event) {})`** fires when either the client or the
+  server closes the connection. Event has two optional attributes, **`code`**
+  and **`reason`**, that expose the status code and message sent by the peer
+  that closed the connection.
+- **`send(message)`** accepts either a `String` or a `Buffer` and sends a text
+  or binary message over the connection to the other peer.
+- **`ping(message, function() {})`** sends a ping frame with an optional message
+  and fires the callback when a matching pong is received.
+- **`close(code, reason)`** closes the connection, sending the given status code
+  and reason text, both of which are optional.
+- **`version`** is a string containing the version of the `WebSocket` protocol
+  the connection is using.
+- **`protocol`** is a string (which may be empty) identifying the subprotocol
+  the socket is using.
+
+
+## Handling EventSource connections in Node
+
+EventSource connections provide a very similar interface, although because they
+only allow the server to send data to the client, there is no `onmessage` API.
+EventSource allows the server to push text messages to the client, where each
+message has an optional event-type and ID.
+
+```js
+var WebSocket   = require('faye-websocket'),
+    EventSource = WebSocket.EventSource,
+    http        = require('http');
+
+var server = http.createServer();
+
+server.on('request', function(request, response) {
+  if (EventSource.isEventSource(request)) {
+    var es = new EventSource(request, response);
+    console.log('open', es.url, es.lastEventId);
+
+    // Periodically send messages
+    var loop = setInterval(function() { es.send('Hello') }, 1000);
+
+    es.on('close', function() {
+      clearInterval(loop);
+      es = null;
+    });
+
+  } else {
+    // Normal HTTP request
+    response.writeHead(200, { 'Content-Type': 'text/plain' });
+    response.end('Hello');
+  }
+});
+
+server.listen(8000);
+```
+
+The `send` method takes two optional parameters, `event` and `id`. The default
+event-type is `'message'` with no ID. For example, to send a `notification`
+event with ID `99`:
+
+```js
+es.send('Breaking News!', { event: 'notification', id: '99' });
+```
+
+The `EventSource` object exposes the following properties:
+
+- **`url`** is a string containing the URL the client used to create the
+  EventSource.
+- **`lastEventId`** is a string containing the last event ID received by the
+  client. You can use this when the client reconnects after a dropped connection
+  to determine which messages need resending.
+
+When you initialize an EventSource with ` new EventSource()`, you can pass
+configuration options after the `response` parameter. Available options are:
+
+- **`headers`** is an object containing custom headers to be set on the
+  EventSource response.
+- **`retry`** is a number that tells the client how long (in seconds) it should
+  wait after a dropped connection before attempting to reconnect.
+- **`ping`** is a number that tells the server how often (in seconds) to send
+  'ping' packets to the client to keep the connection open, to defeat timeouts
+  set by proxies. The client will ignore these messages.
+
+For example, this creates a connection that allows access from any origin, pings
+every 15 seconds and is retryable every 10 seconds if the connection is broken:
+
+```js
+var es = new EventSource(request, response, {
+  headers: { 'Access-Control-Allow-Origin': '*' },
+  ping:    15,
+  retry:   10
+});
+```
+
+You can send a ping message at any time by calling `es.ping()`. Unlike
+WebSocket, the client does not send a response to this; it is merely to send
+some data over the wire to keep the connection alive.
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;
Index: frontend/node_modules/faye-websocket/package.json
===================================================================
--- frontend/node_modules/faye-websocket/package.json	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/faye-websocket/package.json	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,35 @@
+{
+          "name": "faye-websocket",
+          "description": "Standards-compliant WebSocket server and client",
+          "homepage": "https://github.com/faye/faye-websocket-node",
+          "author": "James Coglan <jcoglan@gmail.com> (http://jcoglan.com/)",
+          "keywords": [
+                    "websocket",
+                    "eventsource"
+          ],
+          "license": "Apache-2.0",
+          "version": "0.11.4",
+          "engines": {
+                    "node": ">=0.8.0"
+          },
+          "files": [
+                    "lib"
+          ],
+          "main": "./lib/faye/websocket",
+          "dependencies": {
+                    "websocket-driver": ">=0.5.1"
+          },
+          "devDependencies": {
+                    "jstest": "*",
+                    "pace": "*",
+                    "permessage-deflate": "*"
+          },
+          "scripts": {
+                    "test": "jstest spec/runner.js"
+          },
+          "repository": {
+                    "type": "git",
+                    "url": "git://github.com/faye/faye-websocket-node.git"
+          },
+          "bugs": "https://github.com/faye/faye-websocket-node/issues"
+}
