Index: frontend/node_modules/spdy-transport/.travis.yml
===================================================================
--- frontend/node_modules/spdy-transport/.travis.yml	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/spdy-transport/.travis.yml	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,14 @@
+sudo: false
+
+language: node_js
+
+node_js:
+  - "6"
+  - "8"
+  - "10"
+  - "stable"
+
+script:
+  - npm run lint
+  - npm test
+  - npm run coverage
Index: frontend/node_modules/spdy-transport/README.md
===================================================================
--- frontend/node_modules/spdy-transport/README.md	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/spdy-transport/README.md	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,76 @@
+# spdy-transport
+
+[![Build Status](https://travis-ci.org/spdy-http2/spdy-transport.svg?branch=master)](http://travis-ci.org/spdy-http2/spdy-transport)
+[![NPM version](https://badge.fury.io/js/spdy-transport.svg)](http://badge.fury.io/js/spdy-transport)
+[![dependencies Status](https://david-dm.org/spdy-http2/spdy-transport/status.svg?style=flat-square)](https://david-dm.org/spdy-http2/spdy-transport)
+[![Standard - JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg?style=flat-square)](http://standardjs.com/)
+[![Waffle](https://img.shields.io/badge/track-waffle-blue.svg?style=flat-square)](https://waffle.io/spdy-http2/node-spdy)
+
+> SPDY/HTTP2 generic transport implementation.
+
+## Usage
+
+```javascript
+var transport = require('spdy-transport');
+
+// NOTE: socket is some stream or net.Socket instance, may be an argument
+// of `net.createServer`'s connection handler.
+
+var server = transport.connection.create(socket, {
+  protocol: 'http2',
+  isServer: true
+});
+
+server.on('stream', function(stream) {
+  console.log(stream.method, stream.path, stream.headers);
+  stream.respond(200, {
+    header: 'value'
+  });
+
+  stream.on('readable', function() {
+    var chunk = stream.read();
+    if (!chunk)
+      return;
+
+    console.log(chunk);
+  });
+
+  stream.on('end', function() {
+    console.log('end');
+  });
+
+  // And other node.js Stream APIs
+  // ...
+});
+```
+
+## LICENSE
+
+This software is licensed under the MIT License.
+
+Copyright Fedor Indutny, 2015.
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to permit
+persons to whom the Software is furnished to do so, subject to the
+following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+[0]: http://json.org/
+[1]: http://github.com/indutny/bud-backend
+[2]: https://github.com/nodejs/io.js
+[3]: https://github.com/libuv/libuv
+[4]: http://openssl.org/
Index: frontend/node_modules/spdy-transport/lib/spdy-transport.js
===================================================================
--- frontend/node_modules/spdy-transport/lib/spdy-transport.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/spdy-transport/lib/spdy-transport.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,25 @@
+'use strict'
+
+var transport = exports
+
+// Exports utils
+transport.utils = require('./spdy-transport/utils')
+
+// Export parser&framer
+transport.protocol = {}
+transport.protocol.base = require('./spdy-transport/protocol/base')
+transport.protocol.spdy = require('./spdy-transport/protocol/spdy')
+transport.protocol.http2 = require('./spdy-transport/protocol/http2')
+
+// Window
+transport.Window = require('./spdy-transport/window')
+
+// Priority Tree
+transport.Priority = require('./spdy-transport/priority')
+
+// Export Connection and Stream
+transport.Stream = require('./spdy-transport/stream').Stream
+transport.Connection = require('./spdy-transport/connection').Connection
+
+// Just for `transport.connection.create()`
+transport.connection = transport.Connection
Index: frontend/node_modules/spdy-transport/lib/spdy-transport/connection.js
===================================================================
--- frontend/node_modules/spdy-transport/lib/spdy-transport/connection.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/spdy-transport/lib/spdy-transport/connection.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,845 @@
+'use strict'
+
+var util = require('util')
+var transport = require('../spdy-transport')
+
+var debug = {
+  server: require('debug')('spdy:connection:server'),
+  client: require('debug')('spdy:connection:client')
+}
+var EventEmitter = require('events').EventEmitter
+
+var Stream = transport.Stream
+
+function Connection (socket, options) {
+  EventEmitter.call(this)
+
+  var state = {}
+  this._spdyState = state
+
+  // NOTE: There's a big trick here. Connection is used as a `this` argument
+  // to the wrapped `connection` event listener.
+  // socket end doesn't necessarly mean connection drop
+  this.httpAllowHalfOpen = true
+
+  state.timeout = new transport.utils.Timeout(this)
+
+  // Protocol info
+  state.protocol = transport.protocol[options.protocol]
+  state.version = null
+  state.constants = state.protocol.constants
+  state.pair = null
+  state.isServer = options.isServer
+
+  // Root of priority tree (i.e. stream id = 0)
+  state.priorityRoot = new transport.Priority({
+    defaultWeight: state.constants.DEFAULT_WEIGHT,
+    maxCount: transport.protocol.base.constants.MAX_PRIORITY_STREAMS
+  })
+
+  // Defaults
+  state.maxStreams = options.maxStreams ||
+                     state.constants.MAX_CONCURRENT_STREAMS
+
+  state.autoSpdy31 = options.protocol.name !== 'h2' && options.autoSpdy31
+  state.acceptPush = options.acceptPush === undefined
+    ? !state.isServer
+    : options.acceptPush
+
+  if (options.maxChunk === false) { state.maxChunk = Infinity } else if (options.maxChunk === undefined) { state.maxChunk = transport.protocol.base.constants.DEFAULT_MAX_CHUNK } else {
+    state.maxChunk = options.maxChunk
+  }
+
+  // Connection-level flow control
+  var windowSize = options.windowSize || 1 << 20
+  state.window = new transport.Window({
+    id: 0,
+    isServer: state.isServer,
+    recv: {
+      size: state.constants.DEFAULT_WINDOW,
+      max: state.constants.MAX_INITIAL_WINDOW_SIZE
+    },
+    send: {
+      size: state.constants.DEFAULT_WINDOW,
+      max: state.constants.MAX_INITIAL_WINDOW_SIZE
+    }
+  })
+
+  // It starts with DEFAULT_WINDOW, update must be sent to change it on client
+  state.window.recv.setMax(windowSize)
+
+  // Boilerplate for Stream constructor
+  state.streamWindow = new transport.Window({
+    id: -1,
+    isServer: state.isServer,
+    recv: {
+      size: windowSize,
+      max: state.constants.MAX_INITIAL_WINDOW_SIZE
+    },
+    send: {
+      size: state.constants.DEFAULT_WINDOW,
+      max: state.constants.MAX_INITIAL_WINDOW_SIZE
+    }
+  })
+
+  // Various state info
+  state.pool = state.protocol.compressionPool.create(options.headerCompression)
+  state.counters = {
+    push: 0,
+    stream: 0
+  }
+
+  // Init streams list
+  state.stream = {
+    map: {},
+    count: 0,
+    nextId: state.isServer ? 2 : 1,
+    lastId: {
+      both: 0,
+      received: 0
+    }
+  }
+  state.ping = {
+    nextId: state.isServer ? 2 : 1,
+    map: {}
+  }
+  state.goaway = false
+
+  // Debug
+  state.debug = state.isServer ? debug.server : debug.client
+
+  // X-Forwarded feature
+  state.xForward = null
+
+  // Create parser and hole for framer
+  state.parser = state.protocol.parser.create({
+    // NOTE: needed to distinguish ping from ping ACK in SPDY
+    isServer: state.isServer,
+    window: state.window
+  })
+  state.framer = state.protocol.framer.create({
+    window: state.window,
+    timeout: state.timeout
+  })
+
+  // SPDY has PUSH enabled on servers
+  if (state.protocol.name === 'spdy') {
+    state.framer.enablePush(state.isServer)
+  }
+
+  if (!state.isServer) { state.parser.skipPreface() }
+
+  this.socket = socket
+
+  this._init()
+}
+util.inherits(Connection, EventEmitter)
+exports.Connection = Connection
+
+Connection.create = function create (socket, options) {
+  return new Connection(socket, options)
+}
+
+Connection.prototype._init = function init () {
+  var self = this
+  var state = this._spdyState
+  var pool = state.pool
+
+  // Initialize session window
+  state.window.recv.on('drain', function () {
+    self._onSessionWindowDrain()
+  })
+
+  // Initialize parser
+  state.parser.on('data', function (frame) {
+    self._handleFrame(frame)
+  })
+  state.parser.once('version', function (version) {
+    self._onVersion(version)
+  })
+
+  // Propagate parser errors
+  state.parser.on('error', function (err) {
+    self._onParserError(err)
+  })
+
+  // Propagate framer errors
+  state.framer.on('error', function (err) {
+    self.emit('error', err)
+  })
+
+  this.socket.pipe(state.parser)
+  state.framer.pipe(this.socket)
+
+  // Allow high-level api to catch socket errors
+  this.socket.on('error', function onSocketError (e) {
+    self.emit('error', e)
+  })
+
+  this.socket.once('close', function onclose (hadError) {
+    var err
+    if (hadError) {
+      err = new Error('socket hang up')
+      err.code = 'ECONNRESET'
+    }
+
+    self.destroyStreams(err)
+    self.emit('close')
+
+    if (state.pair) {
+      pool.put(state.pair)
+    }
+
+    state.framer.resume()
+  })
+
+  // Reset timeout on close
+  this.once('close', function () {
+    self.setTimeout(0)
+  })
+
+  function _onWindowOverflow () {
+    self._onWindowOverflow()
+  }
+
+  state.window.recv.on('overflow', _onWindowOverflow)
+  state.window.send.on('overflow', _onWindowOverflow)
+
+  // Do not allow half-open connections
+  this.socket.allowHalfOpen = false
+}
+
+Connection.prototype._onVersion = function _onVersion (version) {
+  var state = this._spdyState
+  var prev = state.version
+  var parser = state.parser
+  var framer = state.framer
+  var pool = state.pool
+
+  state.version = version
+  state.debug('id=0 version=%d', version)
+
+  // Ignore transition to 3.1
+  if (!prev) {
+    state.pair = pool.get(version)
+    parser.setCompression(state.pair)
+    framer.setCompression(state.pair)
+  }
+  framer.setVersion(version)
+
+  if (!state.isServer) {
+    framer.prefaceFrame()
+    if (state.xForward !== null) {
+      framer.xForwardedFor({ host: state.xForward })
+    }
+  }
+
+  // Send preface+settings frame (once)
+  framer.settingsFrame({
+    max_header_list_size: state.constants.DEFAULT_MAX_HEADER_LIST_SIZE,
+    max_concurrent_streams: state.maxStreams,
+    enable_push: state.acceptPush ? 1 : 0,
+    initial_window_size: state.window.recv.max
+  })
+
+  // Update session window
+  if (state.version >= 3.1 || (state.isServer && state.autoSpdy31)) { this._onSessionWindowDrain() }
+
+  this.emit('version', version)
+}
+
+Connection.prototype._onParserError = function _onParserError (err) {
+  var state = this._spdyState
+
+  // Prevent further errors
+  state.parser.kill()
+
+  // Send GOAWAY
+  if (err instanceof transport.protocol.base.utils.ProtocolError) {
+    this._goaway({
+      lastId: state.stream.lastId.both,
+      code: err.code,
+      extra: err.message,
+      send: true
+    })
+  }
+
+  this.emit('error', err)
+}
+
+Connection.prototype._handleFrame = function _handleFrame (frame) {
+  var state = this._spdyState
+
+  state.debug('id=0 frame', frame)
+  state.timeout.reset()
+
+  // For testing purposes
+  this.emit('frame', frame)
+
+  var stream
+
+  // Session window update
+  if (frame.type === 'WINDOW_UPDATE' && frame.id === 0) {
+    if (state.version < 3.1 && state.autoSpdy31) {
+      state.debug('id=0 switch version to 3.1')
+      state.version = 3.1
+      this.emit('version', 3.1)
+    }
+    state.window.send.update(frame.delta)
+    return
+  }
+
+  if (state.isServer && frame.type === 'PUSH_PROMISE') {
+    state.debug('id=0 server PUSH_PROMISE')
+    this._goaway({
+      lastId: state.stream.lastId.both,
+      code: 'PROTOCOL_ERROR',
+      send: true
+    })
+    return
+  }
+
+  if (!stream && frame.id !== undefined) {
+    // Load created one
+    stream = state.stream.map[frame.id]
+
+    // Fail if not found
+    if (!stream &&
+        frame.type !== 'HEADERS' &&
+        frame.type !== 'PRIORITY' &&
+        frame.type !== 'RST') {
+      // Other side should destroy the stream upon receiving GOAWAY
+      if (this._isGoaway(frame.id)) { return }
+
+      state.debug('id=0 stream=%d not found', frame.id)
+      state.framer.rstFrame({ id: frame.id, code: 'INVALID_STREAM' })
+      return
+    }
+  }
+
+  // Create new stream
+  if (!stream && frame.type === 'HEADERS') {
+    this._handleHeaders(frame)
+    return
+  }
+
+  if (stream) {
+    stream._handleFrame(frame)
+  } else if (frame.type === 'SETTINGS') {
+    this._handleSettings(frame.settings)
+  } else if (frame.type === 'ACK_SETTINGS') {
+    // TODO(indutny): handle it one day
+  } else if (frame.type === 'PING') {
+    this._handlePing(frame)
+  } else if (frame.type === 'GOAWAY') {
+    this._handleGoaway(frame)
+  } else if (frame.type === 'X_FORWARDED_FOR') {
+    // Set X-Forwarded-For only once
+    if (state.xForward === null) {
+      state.xForward = frame.host
+    }
+  } else if (frame.type === 'PRIORITY') {
+    // TODO(indutny): handle this
+  } else {
+    state.debug('id=0 unknown frame type: %s', frame.type)
+  }
+}
+
+Connection.prototype._onWindowOverflow = function _onWindowOverflow () {
+  var state = this._spdyState
+  state.debug('id=0 window overflow')
+  this._goaway({
+    lastId: state.stream.lastId.both,
+    code: 'FLOW_CONTROL_ERROR',
+    send: true
+  })
+}
+
+Connection.prototype._isGoaway = function _isGoaway (id) {
+  var state = this._spdyState
+  if (state.goaway !== false && state.goaway < id) { return true }
+  return false
+}
+
+Connection.prototype._getId = function _getId () {
+  var state = this._spdyState
+
+  var id = state.stream.nextId
+  state.stream.nextId += 2
+  return id
+}
+
+Connection.prototype._createStream = function _createStream (uri) {
+  var state = this._spdyState
+  var id = uri.id
+  if (id === undefined) { id = this._getId() }
+
+  var isGoaway = this._isGoaway(id)
+
+  if (uri.push && !state.acceptPush) {
+    state.debug('id=0 push disabled promisedId=%d', id)
+
+    // Fatal error
+    this._goaway({
+      lastId: state.stream.lastId.both,
+      code: 'PROTOCOL_ERROR',
+      send: true
+    })
+    isGoaway = true
+  }
+
+  var stream = new Stream(this, {
+    id: id,
+    request: uri.request !== false,
+    method: uri.method,
+    path: uri.path,
+    host: uri.host,
+    priority: uri.priority,
+    headers: uri.headers,
+    parent: uri.parent,
+    readable: !isGoaway && uri.readable,
+    writable: !isGoaway && uri.writable
+  })
+  var self = this
+
+  // Just an empty stream for API consistency
+  if (isGoaway) {
+    return stream
+  }
+
+  state.stream.lastId.both = Math.max(state.stream.lastId.both, id)
+
+  state.debug('id=0 add stream=%d', stream.id)
+  state.stream.map[stream.id] = stream
+  state.stream.count++
+  state.counters.stream++
+  if (stream.parent !== null) {
+    state.counters.push++
+  }
+
+  stream.once('close', function () {
+    self._removeStream(stream)
+  })
+
+  return stream
+}
+
+Connection.prototype._handleHeaders = function _handleHeaders (frame) {
+  var state = this._spdyState
+
+  // Must be HEADERS frame after stream close
+  if (frame.id <= state.stream.lastId.received) { return }
+
+  // Someone is using our ids!
+  if ((frame.id + state.stream.nextId) % 2 === 0) {
+    state.framer.rstFrame({ id: frame.id, code: 'PROTOCOL_ERROR' })
+    return
+  }
+
+  var stream = this._createStream({
+    id: frame.id,
+    request: false,
+    method: frame.headers[':method'],
+    path: frame.headers[':path'],
+    host: frame.headers[':authority'],
+    priority: frame.priority,
+    headers: frame.headers,
+    writable: frame.writable
+  })
+
+  // GOAWAY
+  if (this._isGoaway(stream.id)) {
+    return
+  }
+
+  state.stream.lastId.received = Math.max(
+    state.stream.lastId.received,
+    stream.id
+  )
+
+  // TODO(indutny) handle stream limit
+  if (!this.emit('stream', stream)) {
+    // No listeners was set - abort the stream
+    stream.abort()
+    return
+  }
+
+  // Create fake frame to simulate end of the data
+  if (frame.fin) {
+    stream._handleFrame({ type: 'FIN', fin: true })
+  }
+
+  return stream
+}
+
+Connection.prototype._onSessionWindowDrain = function _onSessionWindowDrain () {
+  var state = this._spdyState
+  if (state.version < 3.1 && !(state.isServer && state.autoSpdy31)) {
+    return
+  }
+
+  var delta = state.window.recv.getDelta()
+  if (delta === 0) {
+    return
+  }
+
+  state.debug('id=0 session window drain, update by %d', delta)
+
+  state.framer.windowUpdateFrame({
+    id: 0,
+    delta: delta
+  })
+  state.window.recv.update(delta)
+}
+
+Connection.prototype.start = function start (version) {
+  this._spdyState.parser.setVersion(version)
+}
+
+// Mostly for testing
+Connection.prototype.getVersion = function getVersion () {
+  return this._spdyState.version
+}
+
+Connection.prototype._handleSettings = function _handleSettings (settings) {
+  var state = this._spdyState
+
+  state.framer.ackSettingsFrame()
+
+  this._setDefaultWindow(settings)
+  if (settings.max_frame_size) { state.framer.setMaxFrameSize(settings.max_frame_size) }
+
+  // TODO(indutny): handle max_header_list_size
+  if (settings.header_table_size) {
+    try {
+      state.pair.compress.updateTableSize(settings.header_table_size)
+    } catch (e) {
+      this._goaway({
+        lastId: 0,
+        code: 'PROTOCOL_ERROR',
+        send: true
+      })
+      return
+    }
+  }
+
+  // HTTP2 clients needs to enable PUSH streams explicitly
+  if (state.protocol.name !== 'spdy') {
+    if (settings.enable_push === undefined) {
+      state.framer.enablePush(state.isServer)
+    } else {
+      state.framer.enablePush(settings.enable_push === 1)
+    }
+  }
+
+  // TODO(indutny): handle max_concurrent_streams
+}
+
+Connection.prototype._setDefaultWindow = function _setDefaultWindow (settings) {
+  if (settings.initial_window_size === undefined) {
+    return
+  }
+
+  var state = this._spdyState
+
+  // Update defaults
+  var window = state.streamWindow
+  window.send.setMax(settings.initial_window_size)
+
+  // Update existing streams
+  Object.keys(state.stream.map).forEach(function (id) {
+    var stream = state.stream.map[id]
+    var window = stream._spdyState.window
+
+    window.send.updateMax(settings.initial_window_size)
+  })
+}
+
+Connection.prototype._handlePing = function handlePing (frame) {
+  var self = this
+  var state = this._spdyState
+
+  // Handle incoming PING
+  if (!frame.ack) {
+    state.framer.pingFrame({
+      opaque: frame.opaque,
+      ack: true
+    })
+
+    self.emit('ping', frame.opaque)
+    return
+  }
+
+  // Handle reply PING
+  var hex = frame.opaque.toString('hex')
+  if (!state.ping.map[hex]) {
+    return
+  }
+  var ping = state.ping.map[hex]
+  delete state.ping.map[hex]
+
+  if (ping.cb) {
+    ping.cb(null)
+  }
+}
+
+Connection.prototype._handleGoaway = function handleGoaway (frame) {
+  this._goaway({
+    lastId: frame.lastId,
+    code: frame.code,
+    send: false
+  })
+}
+
+Connection.prototype.ping = function ping (callback) {
+  var state = this._spdyState
+
+  // HTTP2 is using 8-byte opaque
+  var opaque = Buffer.alloc(state.constants.PING_OPAQUE_SIZE)
+  opaque.fill(0)
+  opaque.writeUInt32BE(state.ping.nextId, opaque.length - 4)
+  state.ping.nextId += 2
+
+  state.ping.map[opaque.toString('hex')] = { cb: callback }
+  state.framer.pingFrame({
+    opaque: opaque,
+    ack: false
+  })
+}
+
+Connection.prototype.getCounter = function getCounter (name) {
+  return this._spdyState.counters[name]
+}
+
+Connection.prototype.reserveStream = function reserveStream (uri, callback) {
+  var stream = this._createStream(uri)
+
+  // GOAWAY
+  if (this._isGoaway(stream.id)) {
+    var err = new Error('Can\'t send request after GOAWAY')
+    process.nextTick(function () {
+      if (callback) { callback(err) } else {
+        stream.emit('error', err)
+      }
+    })
+    return stream
+  }
+
+  if (callback) {
+    process.nextTick(function () {
+      callback(null, stream)
+    })
+  }
+
+  return stream
+}
+
+Connection.prototype.request = function request (uri, callback) {
+  var stream = this.reserveStream(uri, function (err) {
+    if (err) {
+      if (callback) {
+        callback(err)
+      } else {
+        stream.emit('error', err)
+      }
+      return
+    }
+
+    if (stream._wasSent()) {
+      if (callback) {
+        callback(null, stream)
+      }
+      return
+    }
+
+    stream.send(function (err) {
+      if (err) {
+        if (callback) { return callback(err) } else { return stream.emit('error', err) }
+      }
+
+      if (callback) {
+        callback(null, stream)
+      }
+    })
+  })
+
+  return stream
+}
+
+Connection.prototype._removeStream = function _removeStream (stream) {
+  var state = this._spdyState
+
+  state.debug('id=0 remove stream=%d', stream.id)
+  delete state.stream.map[stream.id]
+  state.stream.count--
+
+  if (state.stream.count === 0) {
+    this.emit('_streamDrain')
+  }
+}
+
+Connection.prototype._goaway = function _goaway (params) {
+  var state = this._spdyState
+  var self = this
+
+  state.goaway = params.lastId
+  state.debug('id=0 goaway from=%d', state.goaway)
+
+  Object.keys(state.stream.map).forEach(function (id) {
+    var stream = state.stream.map[id]
+
+    // Abort every stream started after GOAWAY
+    if (stream.id <= params.lastId) {
+      return
+    }
+
+    stream.abort()
+    stream.emit('error', new Error('New stream after GOAWAY'))
+  })
+
+  function finish () {
+    // Destroy socket if there are no streams
+    if (state.stream.count === 0 || params.code !== 'OK') {
+      // No further frames should be processed
+      state.parser.kill()
+
+      process.nextTick(function () {
+        var err = new Error('Fatal error: ' + params.code)
+        self._onStreamDrain(err)
+      })
+      return
+    }
+
+    self.on('_streamDrain', self._onStreamDrain)
+  }
+
+  if (params.send) {
+    // Make sure that GOAWAY frame is sent before dumping framer
+    state.framer.goawayFrame({
+      lastId: params.lastId,
+      code: params.code,
+      extra: params.extra
+    }, finish)
+  } else {
+    finish()
+  }
+}
+
+Connection.prototype._onStreamDrain = function _onStreamDrain (error) {
+  var state = this._spdyState
+
+  state.debug('id=0 _onStreamDrain')
+
+  state.framer.dump()
+  state.framer.unpipe(this.socket)
+  state.framer.resume()
+
+  if (this.socket.destroySoon) {
+    this.socket.destroySoon()
+  }
+  this.emit('close', error)
+}
+
+Connection.prototype.end = function end (callback) {
+  var state = this._spdyState
+
+  if (callback) {
+    this.once('close', callback)
+  }
+  this._goaway({
+    lastId: state.stream.lastId.both,
+    code: 'OK',
+    send: true
+  })
+}
+
+Connection.prototype.destroyStreams = function destroyStreams (err) {
+  var state = this._spdyState
+  Object.keys(state.stream.map).forEach(function (id) {
+    var stream = state.stream.map[id]
+
+    stream.destroy()
+    if (err) {
+      stream.emit('error', err)
+    }
+  })
+}
+
+Connection.prototype.isServer = function isServer () {
+  return this._spdyState.isServer
+}
+
+Connection.prototype.getXForwardedFor = function getXForwardFor () {
+  return this._spdyState.xForward
+}
+
+Connection.prototype.sendXForwardedFor = function sendXForwardedFor (host) {
+  var state = this._spdyState
+  if (state.version !== null) {
+    state.framer.xForwardedFor({ host: host })
+  } else {
+    state.xForward = host
+  }
+}
+
+Connection.prototype.pushPromise = function pushPromise (parent, uri, callback) {
+  var state = this._spdyState
+
+  var stream = this._createStream({
+    request: false,
+    parent: parent,
+    method: uri.method,
+    path: uri.path,
+    host: uri.host,
+    priority: uri.priority,
+    headers: uri.headers,
+    readable: false
+  })
+
+  var err
+
+  // TODO(indutny): deduplicate this logic somehow
+  if (this._isGoaway(stream.id)) {
+    err = new Error('Can\'t send PUSH_PROMISE after GOAWAY')
+
+    process.nextTick(function () {
+      if (callback) {
+        callback(err)
+      } else {
+        stream.emit('error', err)
+      }
+    })
+    return stream
+  }
+
+  if (uri.push && !state.acceptPush) {
+    err = new Error(
+      'Can\'t send PUSH_PROMISE, other side won\'t accept it')
+    process.nextTick(function () {
+      if (callback) { callback(err) } else {
+        stream.emit('error', err)
+      }
+    })
+    return stream
+  }
+
+  stream._sendPush(uri.status, uri.response, function (err) {
+    if (!callback) {
+      if (err) {
+        stream.emit('error', err)
+      }
+      return
+    }
+
+    if (err) { return callback(err) }
+    callback(null, stream)
+  })
+
+  return stream
+}
+
+Connection.prototype.setTimeout = function setTimeout (delay, callback) {
+  var state = this._spdyState
+
+  state.timeout.set(delay, callback)
+}
Index: frontend/node_modules/spdy-transport/lib/spdy-transport/priority.js
===================================================================
--- frontend/node_modules/spdy-transport/lib/spdy-transport/priority.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/spdy-transport/lib/spdy-transport/priority.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,188 @@
+'use strict'
+
+var transport = require('../spdy-transport')
+var utils = transport.utils
+
+var assert = require('assert')
+var debug = require('debug')('spdy:priority')
+
+function PriorityNode (tree, options) {
+  this.tree = tree
+
+  this.id = options.id
+  this.parent = options.parent
+  this.weight = options.weight
+
+  // To be calculated in `addChild`
+  this.priorityFrom = 0
+  this.priorityTo = 1
+  this.priority = 1
+
+  this.children = {
+    list: [],
+    weight: 0
+  }
+
+  if (this.parent !== null) {
+    this.parent.addChild(this)
+  }
+}
+
+function compareChildren (a, b) {
+  return a.weight === b.weight ? a.id - b.id : a.weight - b.weight
+}
+
+PriorityNode.prototype.toJSON = function toJSON () {
+  return {
+    parent: this.parent,
+    weight: this.weight,
+    exclusive: this.exclusive
+  }
+}
+
+PriorityNode.prototype.getPriority = function getPriority () {
+  return this.priority
+}
+
+PriorityNode.prototype.getPriorityRange = function getPriorityRange () {
+  return { from: this.priorityFrom, to: this.priorityTo }
+}
+
+PriorityNode.prototype.addChild = function addChild (child) {
+  child.parent = this
+  utils.binaryInsert(this.children.list, child, compareChildren)
+  this.children.weight += child.weight
+
+  this._updatePriority(this.priorityFrom, this.priorityTo)
+}
+
+PriorityNode.prototype.remove = function remove () {
+  assert(this.parent, 'Can\'t remove root node')
+
+  this.parent.removeChild(this)
+  this.tree._removeNode(this)
+
+  // Move all children to the parent
+  for (var i = 0; i < this.children.list.length; i++) {
+    this.parent.addChild(this.children.list[i])
+  }
+}
+
+PriorityNode.prototype.removeChild = function removeChild (child) {
+  this.children.weight -= child.weight
+  var index = utils.binarySearch(this.children.list, child, compareChildren)
+  if (index !== -1 && this.children.list.length >= index) {
+    this.children.list.splice(index, 1)
+  }
+}
+
+PriorityNode.prototype.removeChildren = function removeChildren () {
+  var children = this.children.list
+  this.children.list = []
+  this.children.weight = 0
+  return children
+}
+
+PriorityNode.prototype._updatePriority = function _updatePriority (from, to) {
+  this.priority = to - from
+  this.priorityFrom = from
+  this.priorityTo = to
+
+  var weight = 0
+  for (var i = 0; i < this.children.list.length; i++) {
+    var node = this.children.list[i]
+    var nextWeight = weight + node.weight
+
+    node._updatePriority(
+      from + this.priority * (weight / this.children.weight),
+      from + this.priority * (nextWeight / this.children.weight)
+    )
+    weight = nextWeight
+  }
+}
+
+function PriorityTree (options) {
+  this.map = {}
+  this.list = []
+  this.defaultWeight = options.defaultWeight || 16
+
+  this.count = 0
+  this.maxCount = options.maxCount
+
+  // Root
+  this.root = this.add({
+    id: 0,
+    parent: null,
+    weight: 1
+  })
+}
+module.exports = PriorityTree
+
+PriorityTree.create = function create (options) {
+  return new PriorityTree(options)
+}
+
+PriorityTree.prototype.add = function add (options) {
+  if (options.id === options.parent) {
+    return this.addDefault(options.id)
+  }
+
+  var parent = options.parent === null ? null : this.map[options.parent]
+  if (parent === undefined) {
+    return this.addDefault(options.id)
+  }
+
+  debug('add node=%d parent=%d weight=%d exclusive=%d',
+    options.id,
+    options.parent === null ? -1 : options.parent,
+    options.weight || this.defaultWeight,
+    options.exclusive ? 1 : 0)
+
+  var children
+  if (options.exclusive) {
+    children = parent.removeChildren()
+  }
+
+  var node = new PriorityNode(this, {
+    id: options.id,
+    parent: parent,
+    weight: options.weight || this.defaultWeight
+  })
+  this.map[options.id] = node
+
+  if (options.exclusive) {
+    for (var i = 0; i < children.length; i++) {
+      node.addChild(children[i])
+    }
+  }
+
+  this.count++
+  if (this.count > this.maxCount) {
+    debug('hit maximum remove id=%d', this.list[0].id)
+    this.list.shift().remove()
+  }
+
+  // Root node is not subject to removal
+  if (node.parent !== null) {
+    this.list.push(node)
+  }
+
+  return node
+}
+
+// Only for testing, should use `node`'s methods
+PriorityTree.prototype.get = function get (id) {
+  return this.map[id]
+}
+
+PriorityTree.prototype.addDefault = function addDefault (id) {
+  debug('creating default node')
+  return this.add({ id: id, parent: 0, weight: this.defaultWeight })
+}
+
+PriorityTree.prototype._removeNode = function _removeNode (node) {
+  delete this.map[node.id]
+  var index = utils.binarySearch(this.list, node, compareChildren)
+  this.list.splice(index, 1)
+  this.count--
+}
Index: frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/base/constants.js
===================================================================
--- frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/base/constants.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/base/constants.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,4 @@
+exports.DEFAULT_METHOD = 'GET'
+exports.DEFAULT_HOST = 'localhost'
+exports.MAX_PRIORITY_STREAMS = 100
+exports.DEFAULT_MAX_CHUNK = 8 * 1024
Index: frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/base/framer.js
===================================================================
--- frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/base/framer.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/base/framer.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,58 @@
+'use strict'
+
+var util = require('util')
+
+var transport = require('../../../spdy-transport')
+var base = require('./')
+var Scheduler = base.Scheduler
+
+function Framer (options) {
+  Scheduler.call(this)
+
+  this.version = null
+  this.compress = null
+  this.window = options.window
+  this.timeout = options.timeout
+
+  // Wait for `enablePush`
+  this.pushEnabled = null
+}
+util.inherits(Framer, Scheduler)
+module.exports = Framer
+
+Framer.prototype.setVersion = function setVersion (version) {
+  this.version = version
+  this.emit('version')
+}
+
+Framer.prototype.setCompression = function setCompresion (pair) {
+  this.compress = new transport.utils.LockStream(pair.compress)
+}
+
+Framer.prototype.enablePush = function enablePush (enable) {
+  this.pushEnabled = enable
+  this.emit('_pushEnabled')
+}
+
+Framer.prototype._checkPush = function _checkPush (callback) {
+  if (this.pushEnabled === null) {
+    this.once('_pushEnabled', function () {
+      this._checkPush(callback)
+    })
+    return
+  }
+
+  var err = null
+  if (!this.pushEnabled) {
+    err = new Error('PUSH_PROMISE disabled by other side')
+  }
+  process.nextTick(function () {
+    return callback(err)
+  })
+}
+
+Framer.prototype._resetTimeout = function _resetTimeout () {
+  if (this.timeout) {
+    this.timeout.reset()
+  }
+}
Index: frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/base/index.js
===================================================================
--- frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/base/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/base/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,7 @@
+'use strict'
+
+exports.utils = require('./utils')
+exports.constants = require('./constants')
+exports.Scheduler = require('./scheduler')
+exports.Parser = require('./parser')
+exports.Framer = require('./framer')
Index: frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/base/parser.js
===================================================================
--- frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/base/parser.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/base/parser.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,106 @@
+'use strict'
+
+var transport = require('../../../spdy-transport')
+
+var util = require('util')
+var utils = require('./').utils
+var OffsetBuffer = require('obuf')
+var Transform = require('readable-stream').Transform
+
+function Parser (options) {
+  Transform.call(this, {
+    readableObjectMode: true
+  })
+
+  this.buffer = new OffsetBuffer()
+  this.partial = false
+  this.waiting = 0
+
+  this.window = options.window
+
+  this.version = null
+  this.decompress = null
+  this.dead = false
+}
+module.exports = Parser
+util.inherits(Parser, Transform)
+
+Parser.prototype.error = utils.error
+
+Parser.prototype.kill = function kill () {
+  this.dead = true
+}
+
+Parser.prototype._transform = function transform (data, encoding, cb) {
+  if (!this.dead) { this.buffer.push(data) }
+
+  this._consume(cb)
+}
+
+Parser.prototype._consume = function _consume (cb) {
+  var self = this
+
+  function next (err, frame) {
+    if (err) {
+      return cb(err)
+    }
+
+    if (Array.isArray(frame)) {
+      for (var i = 0; i < frame.length; i++) {
+        self.push(frame[i])
+      }
+    } else if (frame) {
+      self.push(frame)
+    }
+
+    // Consume more packets
+    if (!sync) {
+      return self._consume(cb)
+    }
+
+    process.nextTick(function () {
+      self._consume(cb)
+    })
+  }
+
+  if (this.dead) {
+    return cb()
+  }
+
+  if (this.buffer.size < this.waiting) {
+    // No data at all
+    if (this.buffer.size === 0) {
+      return cb()
+    }
+
+    // Partial DATA frame or something that we can process partially
+    if (this.partial) {
+      var partial = this.buffer.clone(this.buffer.size)
+      this.buffer.skip(partial.size)
+      this.waiting -= partial.size
+
+      this.executePartial(partial, next)
+      return
+    }
+
+    // We shall not do anything until we get all expected data
+    return cb()
+  }
+
+  var sync = true
+
+  var content = this.buffer.clone(this.waiting)
+  this.buffer.skip(this.waiting)
+
+  this.execute(content, next)
+  sync = false
+}
+
+Parser.prototype.setVersion = function setVersion (version) {
+  this.version = version
+  this.emit('version', version)
+}
+
+Parser.prototype.setCompression = function setCompresion (pair) {
+  this.decompress = new transport.utils.LockStream(pair.decompress)
+}
Index: frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/base/scheduler.js
===================================================================
--- frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/base/scheduler.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/base/scheduler.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,216 @@
+'use strict'
+
+var transport = require('../../../spdy-transport')
+var utils = transport.utils
+
+var assert = require('assert')
+var util = require('util')
+var debug = require('debug')('spdy:scheduler')
+var Readable = require('readable-stream').Readable
+
+/*
+ * We create following structure in `pending`:
+ * [ [ id = 0 ], [ id = 1 ], [ id = 2 ], [ id = 0 ] ]
+ *     chunks      chunks      chunks      chunks
+ *     chunks                  chunks
+ *     chunks
+ *
+ * Then on the `.tick()` pass we pick one chunks from each item and remove the
+ * item if it is empty:
+ *
+ * [ [ id = 0 ], [ id = 2 ] ]
+ *     chunks      chunks
+ *     chunks
+ *
+ * Writing out: chunks for 0, chunks for 1, chunks for 2, chunks for 0
+ *
+ * This way data is interleaved between the different streams.
+ */
+
+function Scheduler (options) {
+  Readable.call(this)
+
+  // Pretty big window by default
+  this.window = 0.25
+
+  if (options && options.window) { this.window = options.window }
+
+  this.sync = []
+  this.list = []
+  this.count = 0
+  this.pendingTick = false
+}
+util.inherits(Scheduler, Readable)
+module.exports = Scheduler
+
+// Just for testing, really
+Scheduler.create = function create (options) {
+  return new Scheduler(options)
+}
+
+function insertCompare (a, b) {
+  return a.priority === b.priority
+    ? a.stream - b.stream
+    : b.priority - a.priority
+}
+
+Scheduler.prototype.schedule = function schedule (data) {
+  var priority = data.priority
+  var stream = data.stream
+  var chunks = data.chunks
+
+  // Synchronous frames should not be interleaved
+  if (priority === false) {
+    debug('queue sync', chunks)
+    this.sync.push(data)
+    this.count += chunks.length
+
+    this._read()
+    return
+  }
+
+  debug('queue async priority=%d stream=%d', priority, stream, chunks)
+  var item = new SchedulerItem(stream, priority)
+  var index = utils.binaryLookup(this.list, item, insertCompare)
+
+  // Push new item
+  if (index >= this.list.length || insertCompare(this.list[index], item) !== 0) {
+    this.list.splice(index, 0, item)
+  } else { // Coalesce
+    item = this.list[index]
+  }
+
+  item.push(data)
+
+  this.count += chunks.length
+
+  this._read()
+}
+
+Scheduler.prototype._read = function _read () {
+  if (this.count === 0) {
+    return
+  }
+
+  if (this.pendingTick) {
+    return
+  }
+  this.pendingTick = true
+
+  var self = this
+  process.nextTick(function () {
+    self.pendingTick = false
+    self.tick()
+  })
+}
+
+Scheduler.prototype.tick = function tick () {
+  // No luck for async frames
+  if (!this.tickSync()) { return false }
+
+  return this.tickAsync()
+}
+
+Scheduler.prototype.tickSync = function tickSync () {
+  // Empty sync queue first
+  var sync = this.sync
+  var res = true
+  this.sync = []
+  for (var i = 0; i < sync.length; i++) {
+    var item = sync[i]
+    debug('tick sync pending=%d', this.count, item.chunks)
+    for (var j = 0; j < item.chunks.length; j++) {
+      this.count--
+      // TODO: handle stream backoff properly
+      try {
+        res = this.push(item.chunks[j])
+      } catch (err) {
+        this.emit('error', err)
+        return false
+      }
+    }
+    debug('after tick sync pending=%d', this.count)
+
+    // TODO(indutny): figure out the way to invoke callback on actual write
+    if (item.callback) {
+      item.callback(null)
+    }
+  }
+  return res
+}
+
+Scheduler.prototype.tickAsync = function tickAsync () {
+  var res = true
+  var list = this.list
+  if (list.length === 0) {
+    return res
+  }
+
+  var startPriority = list[0].priority
+  for (var index = 0; list.length > 0; index++) {
+    // Loop index
+    index %= list.length
+    if (startPriority - list[index].priority > this.window) { index = 0 }
+    debug('tick async index=%d start=%d', index, startPriority)
+
+    var current = list[index]
+    var item = current.shift()
+
+    if (current.isEmpty()) {
+      list.splice(index, 1)
+      if (index === 0 && list.length > 0) {
+        startPriority = list[0].priority
+      }
+      index--
+    }
+
+    debug('tick async pending=%d', this.count, item.chunks)
+    for (var i = 0; i < item.chunks.length; i++) {
+      this.count--
+      // TODO: handle stream backoff properly
+      try {
+        res = this.push(item.chunks[i])
+      } catch (err) {
+        this.emit('error', err)
+        return false
+      }
+    }
+    debug('after tick pending=%d', this.count)
+
+    // TODO(indutny): figure out the way to invoke callback on actual write
+    if (item.callback) {
+      item.callback(null)
+    }
+    if (!res) { break }
+  }
+
+  return res
+}
+
+Scheduler.prototype.dump = function dump () {
+  this.tickSync()
+
+  // Write everything out
+  while (!this.tickAsync()) {
+    // Intentional no-op
+  }
+  assert.strictEqual(this.count, 0)
+}
+
+function SchedulerItem (stream, priority) {
+  this.stream = stream
+  this.priority = priority
+  this.queue = []
+}
+
+SchedulerItem.prototype.push = function push (chunks) {
+  this.queue.push(chunks)
+}
+
+SchedulerItem.prototype.shift = function shift () {
+  return this.queue.shift()
+}
+
+SchedulerItem.prototype.isEmpty = function isEmpty () {
+  return this.queue.length === 0
+}
Index: frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/base/utils.js
===================================================================
--- frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/base/utils.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/base/utils.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,94 @@
+'use strict'
+
+var utils = exports
+
+var util = require('util')
+
+function ProtocolError (code, message) {
+  this.code = code
+  this.message = message
+}
+util.inherits(ProtocolError, Error)
+utils.ProtocolError = ProtocolError
+
+utils.error = function error (code, message) {
+  return new ProtocolError(code, message)
+}
+
+utils.reverse = function reverse (object) {
+  var result = []
+
+  Object.keys(object).forEach(function (key) {
+    result[object[key] | 0] = key
+  })
+
+  return result
+}
+
+// weight [1, 36] <=> priority [0, 7]
+// This way weight=16 is preserved and has priority=3
+utils.weightToPriority = function weightToPriority (weight) {
+  return ((Math.min(35, (weight - 1)) / 35) * 7) | 0
+}
+
+utils.priorityToWeight = function priorityToWeight (priority) {
+  return (((priority / 7) * 35) | 0) + 1
+}
+
+// Copy-Paste from node
+exports.addHeaderLine = function addHeaderLine (field, value, dest) {
+  field = field.toLowerCase()
+  if (/^:/.test(field)) {
+    dest[field] = value
+    return
+  }
+
+  switch (field) {
+    // Array headers:
+    case 'set-cookie':
+      if (dest[field] !== undefined) {
+        dest[field].push(value)
+      } else {
+        dest[field] = [ value ]
+      }
+      break
+
+    /* eslint-disable max-len */
+    // list is taken from:
+    /* eslint-enable max-len */
+    case 'content-type':
+    case 'content-length':
+    case 'user-agent':
+    case 'referer':
+    case 'host':
+    case 'authorization':
+    case 'proxy-authorization':
+    case 'if-modified-since':
+    case 'if-unmodified-since':
+    case 'from':
+    case 'location':
+    case 'max-forwards':
+      // drop duplicates
+      if (dest[field] === undefined) {
+        dest[field] = value
+      }
+      break
+
+    case 'cookie':
+      // make semicolon-separated list
+      if (dest[field] !== undefined) {
+        dest[field] += '; ' + value
+      } else {
+        dest[field] = value
+      }
+      break
+
+    default:
+      // make comma-separated list
+      if (dest[field] !== undefined) {
+        dest[field] += ', ' + value
+      } else {
+        dest[field] = value
+      }
+  }
+}
Index: frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/http2/constants.js
===================================================================
--- frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/http2/constants.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/http2/constants.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,93 @@
+'use strict'
+
+var transport = require('../../../spdy-transport')
+var base = transport.protocol.base
+
+exports.PREFACE_SIZE = 24
+exports.PREFACE = 'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n'
+exports.PREFACE_BUFFER = Buffer.from(exports.PREFACE)
+
+exports.PING_OPAQUE_SIZE = 8
+
+exports.FRAME_HEADER_SIZE = 9
+exports.INITIAL_MAX_FRAME_SIZE = 16384
+exports.ABSOLUTE_MAX_FRAME_SIZE = 16777215
+exports.HEADER_TABLE_SIZE = 4096
+exports.DEFAULT_MAX_HEADER_LIST_SIZE = 80 * 1024 // as in http_parser
+exports.MAX_INITIAL_WINDOW_SIZE = 2147483647
+
+exports.DEFAULT_WEIGHT = 16
+
+exports.MAX_CONCURRENT_STREAMS = Infinity
+
+exports.frameType = {
+  DATA: 0,
+  HEADERS: 1,
+  PRIORITY: 2,
+  RST_STREAM: 3,
+  SETTINGS: 4,
+  PUSH_PROMISE: 5,
+  PING: 6,
+  GOAWAY: 7,
+  WINDOW_UPDATE: 8,
+  CONTINUATION: 9,
+
+  // Custom
+  X_FORWARDED_FOR: 0xde
+}
+
+exports.flags = {
+  ACK: 0x01, // SETTINGS-only
+  END_STREAM: 0x01,
+  END_HEADERS: 0x04,
+  PADDED: 0x08,
+  PRIORITY: 0x20
+}
+
+exports.settings = {
+  SETTINGS_HEADER_TABLE_SIZE: 0x01,
+  SETTINGS_ENABLE_PUSH: 0x02,
+  SETTINGS_MAX_CONCURRENT_STREAMS: 0x03,
+  SETTINGS_INITIAL_WINDOW_SIZE: 0x04,
+  SETTINGS_MAX_FRAME_SIZE: 0x05,
+  SETTINGS_MAX_HEADER_LIST_SIZE: 0x06
+}
+
+exports.settingsIndex = [
+  null,
+  'header_table_size',
+  'enable_push',
+  'max_concurrent_streams',
+  'initial_window_size',
+  'max_frame_size',
+  'max_header_list_size'
+]
+
+exports.error = {
+  OK: 0,
+  NO_ERROR: 0,
+
+  PROTOCOL_ERROR: 1,
+  INTERNAL_ERROR: 2,
+  FLOW_CONTROL_ERROR: 3,
+  SETTINGS_TIMEOUT: 4,
+
+  STREAM_CLOSED: 5,
+  INVALID_STREAM: 5,
+
+  FRAME_SIZE_ERROR: 6,
+  REFUSED_STREAM: 7,
+  CANCEL: 8,
+  COMPRESSION_ERROR: 9,
+  CONNECT_ERROR: 10,
+  ENHANCE_YOUR_CALM: 11,
+  INADEQUATE_SECURITY: 12,
+  HTTP_1_1_REQUIRED: 13
+}
+exports.errorByCode = base.utils.reverse(exports.error)
+
+exports.DEFAULT_WINDOW = 64 * 1024 - 1
+
+exports.goaway = exports.error
+exports.goawayByCode = Object.assign({}, exports.errorByCode)
+exports.goawayByCode[0] = 'OK'
Index: frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/http2/framer.js
===================================================================
--- frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/http2/framer.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/http2/framer.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,542 @@
+'use strict'
+
+var transport = require('../../../spdy-transport')
+var base = transport.protocol.base
+var constants = require('./').constants
+
+var assert = require('assert')
+var util = require('util')
+var WriteBuffer = require('wbuf')
+var OffsetBuffer = require('obuf')
+var debug = require('debug')('spdy:framer')
+var debugExtra = require('debug')('spdy:framer:extra')
+
+function Framer (options) {
+  base.Framer.call(this, options)
+
+  this.maxFrameSize = constants.INITIAL_MAX_FRAME_SIZE
+}
+util.inherits(Framer, base.Framer)
+module.exports = Framer
+
+Framer.create = function create (options) {
+  return new Framer(options)
+}
+
+Framer.prototype.setMaxFrameSize = function setMaxFrameSize (size) {
+  this.maxFrameSize = size
+}
+
+Framer.prototype._frame = function _frame (frame, body, callback) {
+  debug('id=%d type=%s', frame.id, frame.type)
+
+  var buffer = new WriteBuffer()
+
+  buffer.reserve(constants.FRAME_HEADER_SIZE)
+  var len = buffer.skip(3)
+  buffer.writeUInt8(constants.frameType[frame.type])
+  buffer.writeUInt8(frame.flags)
+  buffer.writeUInt32BE(frame.id & 0x7fffffff)
+
+  body(buffer)
+
+  var frameSize = buffer.size - constants.FRAME_HEADER_SIZE
+  len.writeUInt24BE(frameSize)
+
+  var chunks = buffer.render()
+  var toWrite = {
+    stream: frame.id,
+    priority: frame.priority === undefined ? false : frame.priority,
+    chunks: chunks,
+    callback: callback
+  }
+
+  if (this.window && frame.type === 'DATA') {
+    var self = this
+    this._resetTimeout()
+    this.window.send.update(-frameSize, function () {
+      self._resetTimeout()
+      self.schedule(toWrite)
+    })
+  } else {
+    this._resetTimeout()
+    this.schedule(toWrite)
+  }
+
+  return chunks
+}
+
+Framer.prototype._split = function _split (frame) {
+  var buf = new OffsetBuffer()
+  for (var i = 0; i < frame.chunks.length; i++) { buf.push(frame.chunks[i]) }
+
+  var frames = []
+  while (!buf.isEmpty()) {
+    // First frame may have reserved bytes in it
+    var size = this.maxFrameSize
+    if (frames.length === 0) {
+      size -= frame.reserve
+    }
+    size = Math.min(size, buf.size)
+
+    var frameBuf = buf.clone(size)
+    buf.skip(size)
+
+    frames.push({
+      size: frameBuf.size,
+      chunks: frameBuf.toChunks()
+    })
+  }
+
+  return frames
+}
+
+Framer.prototype._continuationFrame = function _continuationFrame (frame,
+  body,
+  callback) {
+  var frames = this._split(frame)
+
+  frames.forEach(function (subFrame, i) {
+    var isFirst = i === 0
+    var isLast = i === frames.length - 1
+
+    var flags = isLast ? constants.flags.END_HEADERS : 0
+
+    // PRIORITY and friends
+    if (isFirst) {
+      flags |= frame.flags
+    }
+
+    this._frame({
+      id: frame.id,
+      priority: false,
+      type: isFirst ? frame.type : 'CONTINUATION',
+      flags: flags
+    }, function (buf) {
+      // Fill those reserved bytes
+      if (isFirst && body) { body(buf) }
+
+      buf.reserve(subFrame.size)
+      for (var i = 0; i < subFrame.chunks.length; i++) { buf.copyFrom(subFrame.chunks[i]) }
+    }, isLast ? callback : null)
+  }, this)
+
+  if (frames.length === 0) {
+    this._frame({
+      id: frame.id,
+      priority: false,
+      type: frame.type,
+      flags: frame.flags | constants.flags.END_HEADERS
+    }, function (buf) {
+      if (body) { body(buf) }
+    }, callback)
+  }
+}
+
+Framer.prototype._compressHeaders = function _compressHeaders (headers,
+  pairs,
+  callback) {
+  Object.keys(headers || {}).forEach(function (name) {
+    var lowName = name.toLowerCase()
+
+    // Not allowed in HTTP2
+    switch (lowName) {
+      case 'host':
+      case 'connection':
+      case 'keep-alive':
+      case 'proxy-connection':
+      case 'transfer-encoding':
+      case 'upgrade':
+        return
+    }
+
+    // Should be in `pairs`
+    if (/^:/.test(lowName)) {
+      return
+    }
+
+    // Do not compress, or index Cookie field (for security reasons)
+    var neverIndex = lowName === 'cookie' || lowName === 'set-cookie'
+
+    var value = headers[name]
+    if (Array.isArray(value)) {
+      for (var i = 0; i < value.length; i++) {
+        pairs.push({
+          name: lowName,
+          value: value[i] + '',
+          neverIndex: neverIndex,
+          huffman: !neverIndex
+        })
+      }
+    } else {
+      pairs.push({
+        name: lowName,
+        value: value + '',
+        neverIndex: neverIndex,
+        huffman: !neverIndex
+      })
+    }
+  })
+
+  assert(this.compress !== null, 'Framer version not initialized')
+  debugExtra('compressing headers=%j', pairs)
+  this.compress.write([ pairs ], callback)
+}
+
+Framer.prototype._isDefaultPriority = function _isDefaultPriority (priority) {
+  if (!priority) { return true }
+
+  return !priority.parent &&
+         priority.weight === constants.DEFAULT &&
+         !priority.exclusive
+}
+
+Framer.prototype._defaultHeaders = function _defaultHeaders (frame, pairs) {
+  if (!frame.path) {
+    throw new Error('`path` is required frame argument')
+  }
+
+  pairs.push({
+    name: ':method',
+    value: frame.method || base.constants.DEFAULT_METHOD
+  })
+  pairs.push({ name: ':path', value: frame.path })
+  pairs.push({ name: ':scheme', value: frame.scheme || 'https' })
+  pairs.push({
+    name: ':authority',
+    value: frame.host ||
+           (frame.headers && frame.headers.host) ||
+           base.constants.DEFAULT_HOST
+  })
+}
+
+Framer.prototype._headersFrame = function _headersFrame (kind, frame, callback) {
+  var pairs = []
+
+  if (kind === 'request') {
+    this._defaultHeaders(frame, pairs)
+  } else if (kind === 'response') {
+    pairs.push({ name: ':status', value: (frame.status || 200) + '' })
+  }
+
+  var self = this
+  this._compressHeaders(frame.headers, pairs, function (err, chunks) {
+    if (err) {
+      if (callback) {
+        return callback(err)
+      } else {
+        return self.emit('error', err)
+      }
+    }
+
+    var reserve = 0
+
+    // If priority info is present, and the values are not default ones
+    // reserve space for the priority info and add PRIORITY flag
+    var priority = frame.priority
+    if (!self._isDefaultPriority(priority)) { reserve = 5 }
+
+    var flags = reserve === 0 ? 0 : constants.flags.PRIORITY
+
+    // Mostly for testing
+    if (frame.fin) {
+      flags |= constants.flags.END_STREAM
+    }
+
+    self._continuationFrame({
+      id: frame.id,
+      type: 'HEADERS',
+      flags: flags,
+      reserve: reserve,
+      chunks: chunks
+    }, function (buf) {
+      if (reserve === 0) {
+        return
+      }
+
+      buf.writeUInt32BE(((priority.exclusive ? 0x80000000 : 0) |
+                         priority.parent) >>> 0)
+      buf.writeUInt8((priority.weight | 0) - 1)
+    }, callback)
+  })
+}
+
+Framer.prototype.requestFrame = function requestFrame (frame, callback) {
+  return this._headersFrame('request', frame, callback)
+}
+
+Framer.prototype.responseFrame = function responseFrame (frame, callback) {
+  return this._headersFrame('response', frame, callback)
+}
+
+Framer.prototype.headersFrame = function headersFrame (frame, callback) {
+  return this._headersFrame('headers', frame, callback)
+}
+
+Framer.prototype.pushFrame = function pushFrame (frame, callback) {
+  var self = this
+
+  function compress (headers, pairs, callback) {
+    self._compressHeaders(headers, pairs, function (err, chunks) {
+      if (err) {
+        if (callback) {
+          return callback(err)
+        } else {
+          return self.emit('error', err)
+        }
+      }
+
+      callback(chunks)
+    })
+  }
+
+  function sendPromise (chunks) {
+    self._continuationFrame({
+      id: frame.id,
+      type: 'PUSH_PROMISE',
+      reserve: 4,
+      chunks: chunks
+    }, function (buf) {
+      buf.writeUInt32BE(frame.promisedId)
+    })
+  }
+
+  function sendResponse (chunks, callback) {
+    var priority = frame.priority
+    var isDefaultPriority = self._isDefaultPriority(priority)
+    var flags = isDefaultPriority ? 0 : constants.flags.PRIORITY
+
+    // Mostly for testing
+    if (frame.fin) {
+      flags |= constants.flags.END_STREAM
+    }
+
+    self._continuationFrame({
+      id: frame.promisedId,
+      type: 'HEADERS',
+      flags: flags,
+      reserve: isDefaultPriority ? 0 : 5,
+      chunks: chunks
+    }, function (buf) {
+      if (isDefaultPriority) {
+        return
+      }
+
+      buf.writeUInt32BE((priority.exclusive ? 0x80000000 : 0) |
+                        priority.parent)
+      buf.writeUInt8((priority.weight | 0) - 1)
+    }, callback)
+  }
+
+  this._checkPush(function (err) {
+    if (err) {
+      return callback(err)
+    }
+
+    var pairs = {
+      promise: [],
+      response: []
+    }
+
+    self._defaultHeaders(frame, pairs.promise)
+    pairs.response.push({ name: ':status', value: (frame.status || 200) + '' })
+
+    compress(frame.headers, pairs.promise, function (promiseChunks) {
+      sendPromise(promiseChunks)
+      if (frame.response === false) {
+        return callback(null)
+      }
+      compress(frame.response, pairs.response, function (responseChunks) {
+        sendResponse(responseChunks, callback)
+      })
+    })
+  })
+}
+
+Framer.prototype.priorityFrame = function priorityFrame (frame, callback) {
+  this._frame({
+    id: frame.id,
+    priority: false,
+    type: 'PRIORITY',
+    flags: 0
+  }, function (buf) {
+    var priority = frame.priority
+    buf.writeUInt32BE((priority.exclusive ? 0x80000000 : 0) |
+                      priority.parent)
+    buf.writeUInt8((priority.weight | 0) - 1)
+  }, callback)
+}
+
+Framer.prototype.dataFrame = function dataFrame (frame, callback) {
+  var frames = this._split({
+    reserve: 0,
+    chunks: [ frame.data ]
+  })
+
+  var fin = frame.fin ? constants.flags.END_STREAM : 0
+
+  var self = this
+  frames.forEach(function (subFrame, i) {
+    var isLast = i === frames.length - 1
+    var flags = 0
+    if (isLast) {
+      flags |= fin
+    }
+
+    self._frame({
+      id: frame.id,
+      priority: frame.priority,
+      type: 'DATA',
+      flags: flags
+    }, function (buf) {
+      buf.reserve(subFrame.size)
+      for (var i = 0; i < subFrame.chunks.length; i++) { buf.copyFrom(subFrame.chunks[i]) }
+    }, isLast ? callback : null)
+  })
+
+  // Empty DATA
+  if (frames.length === 0) {
+    this._frame({
+      id: frame.id,
+      priority: frame.priority,
+      type: 'DATA',
+      flags: fin
+    }, function (buf) {
+      // No-op
+    }, callback)
+  }
+}
+
+Framer.prototype.pingFrame = function pingFrame (frame, callback) {
+  this._frame({
+    id: 0,
+    type: 'PING',
+    flags: frame.ack ? constants.flags.ACK : 0
+  }, function (buf) {
+    buf.copyFrom(frame.opaque)
+  }, callback)
+}
+
+Framer.prototype.rstFrame = function rstFrame (frame, callback) {
+  this._frame({
+    id: frame.id,
+    type: 'RST_STREAM',
+    flags: 0
+  }, function (buf) {
+    buf.writeUInt32BE(constants.error[frame.code])
+  }, callback)
+}
+
+Framer.prototype.prefaceFrame = function prefaceFrame (callback) {
+  debug('preface')
+  this._resetTimeout()
+  this.schedule({
+    stream: 0,
+    priority: false,
+    chunks: [ constants.PREFACE_BUFFER ],
+    callback: callback
+  })
+}
+
+Framer.prototype.settingsFrame = function settingsFrame (options, callback) {
+  var key = JSON.stringify(options)
+
+  var settings = Framer.settingsCache[key]
+  if (settings) {
+    debug('cached settings')
+    this._resetTimeout()
+    this.schedule({
+      id: 0,
+      priority: false,
+      chunks: settings,
+      callback: callback
+    })
+    return
+  }
+
+  var params = []
+  for (var i = 0; i < constants.settingsIndex.length; i++) {
+    var name = constants.settingsIndex[i]
+    if (!name) {
+      continue
+    }
+
+    // value: Infinity
+    if (!isFinite(options[name])) {
+      continue
+    }
+
+    if (options[name] !== undefined) {
+      params.push({ key: i, value: options[name] })
+    }
+  }
+
+  var bodySize = params.length * 6
+
+  var chunks = this._frame({
+    id: 0,
+    type: 'SETTINGS',
+    flags: 0
+  }, function (buffer) {
+    buffer.reserve(bodySize)
+    for (var i = 0; i < params.length; i++) {
+      var param = params[i]
+
+      buffer.writeUInt16BE(param.key)
+      buffer.writeUInt32BE(param.value)
+    }
+  }, callback)
+
+  Framer.settingsCache[key] = chunks
+}
+Framer.settingsCache = {}
+
+Framer.prototype.ackSettingsFrame = function ackSettingsFrame (callback) {
+  /* var chunks = */ this._frame({
+    id: 0,
+    type: 'SETTINGS',
+    flags: constants.flags.ACK
+  }, function (buffer) {
+    // No-op
+  }, callback)
+}
+
+Framer.prototype.windowUpdateFrame = function windowUpdateFrame (frame,
+  callback) {
+  this._frame({
+    id: frame.id,
+    type: 'WINDOW_UPDATE',
+    flags: 0
+  }, function (buffer) {
+    buffer.reserve(4)
+    buffer.writeInt32BE(frame.delta)
+  }, callback)
+}
+
+Framer.prototype.goawayFrame = function goawayFrame (frame, callback) {
+  this._frame({
+    type: 'GOAWAY',
+    id: 0,
+    flags: 0
+  }, function (buf) {
+    buf.reserve(8)
+
+    // Last-good-stream-ID
+    buf.writeUInt32BE(frame.lastId & 0x7fffffff)
+    // Code
+    buf.writeUInt32BE(constants.goaway[frame.code])
+
+    // Extra debugging information
+    if (frame.extra) { buf.write(frame.extra) }
+  }, callback)
+}
+
+Framer.prototype.xForwardedFor = function xForwardedFor (frame, callback) {
+  this._frame({
+    type: 'X_FORWARDED_FOR',
+    id: 0,
+    flags: 0
+  }, function (buf) {
+    buf.write(frame.host)
+  }, callback)
+}
Index: frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/http2/hpack-pool.js
===================================================================
--- frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/http2/hpack-pool.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/http2/hpack-pool.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,34 @@
+'use strict'
+
+var constants = require('./').constants
+
+var hpack = require('hpack.js')
+
+function Pool () {
+}
+module.exports = Pool
+
+Pool.create = function create () {
+  return new Pool()
+}
+
+Pool.prototype.get = function get (version) {
+  var options = {
+    table: {
+      maxSize: constants.HEADER_TABLE_SIZE
+    }
+  }
+
+  var compress = hpack.compressor.create(options)
+  var decompress = hpack.decompressor.create(options)
+
+  return {
+    version: version,
+
+    compress: compress,
+    decompress: decompress
+  }
+}
+
+Pool.prototype.put = function put () {
+}
Index: frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/http2/index.js
===================================================================
--- frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/http2/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/http2/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,8 @@
+'use strict'
+
+exports.name = 'h2'
+
+exports.constants = require('./constants')
+exports.parser = require('./parser')
+exports.framer = require('./framer')
+exports.compressionPool = require('./hpack-pool')
Index: frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/http2/parser.js
===================================================================
--- frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/http2/parser.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/http2/parser.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,578 @@
+'use strict'
+
+var parser = exports
+
+var transport = require('../../../spdy-transport')
+var base = transport.protocol.base
+var utils = base.utils
+var constants = require('./').constants
+
+var assert = require('assert')
+var util = require('util')
+
+function Parser (options) {
+  base.Parser.call(this, options)
+
+  this.isServer = options.isServer
+
+  this.waiting = constants.PREFACE_SIZE
+  this.state = 'preface'
+  this.pendingHeader = null
+
+  // Header Block queue
+  this._lastHeaderBlock = null
+  this.maxFrameSize = constants.INITIAL_MAX_FRAME_SIZE
+  this.maxHeaderListSize = constants.DEFAULT_MAX_HEADER_LIST_SIZE
+}
+util.inherits(Parser, base.Parser)
+
+parser.create = function create (options) {
+  return new Parser(options)
+}
+
+Parser.prototype.setMaxFrameSize = function setMaxFrameSize (size) {
+  this.maxFrameSize = size
+}
+
+Parser.prototype.setMaxHeaderListSize = function setMaxHeaderListSize (size) {
+  this.maxHeaderListSize = size
+}
+
+// Only for testing
+Parser.prototype.skipPreface = function skipPreface () {
+  // Just some number bigger than 3.1, doesn't really matter for HTTP2
+  this.setVersion(4)
+
+  // Parse frame header!
+  this.state = 'frame-head'
+  this.waiting = constants.FRAME_HEADER_SIZE
+}
+
+Parser.prototype.execute = function execute (buffer, callback) {
+  if (this.state === 'preface') { return this.onPreface(buffer, callback) }
+
+  if (this.state === 'frame-head') {
+    return this.onFrameHead(buffer, callback)
+  }
+
+  assert(this.state === 'frame-body' && this.pendingHeader !== null)
+
+  var self = this
+  var header = this.pendingHeader
+  this.pendingHeader = null
+
+  this.onFrameBody(header, buffer, function (err, frame) {
+    if (err) {
+      return callback(err)
+    }
+
+    self.state = 'frame-head'
+    self.partial = false
+    self.waiting = constants.FRAME_HEADER_SIZE
+    callback(null, frame)
+  })
+}
+
+Parser.prototype.executePartial = function executePartial (buffer, callback) {
+  var header = this.pendingHeader
+
+  assert.strictEqual(header.flags & constants.flags.PADDED, 0)
+
+  if (this.window) { this.window.recv.update(-buffer.size) }
+
+  callback(null, {
+    type: 'DATA',
+    id: header.id,
+
+    // Partial DATA can't be FIN
+    fin: false,
+    data: buffer.take(buffer.size)
+  })
+}
+
+Parser.prototype.onPreface = function onPreface (buffer, callback) {
+  if (buffer.take(buffer.size).toString() !== constants.PREFACE) {
+    return callback(this.error(constants.error.PROTOCOL_ERROR,
+      'Invalid preface'))
+  }
+
+  this.skipPreface()
+  callback(null, null)
+}
+
+Parser.prototype.onFrameHead = function onFrameHead (buffer, callback) {
+  var header = {
+    length: buffer.readUInt24BE(),
+    control: true,
+    type: buffer.readUInt8(),
+    flags: buffer.readUInt8(),
+    id: buffer.readUInt32BE() & 0x7fffffff
+  }
+
+  if (header.length > this.maxFrameSize) {
+    return callback(this.error(constants.error.FRAME_SIZE_ERROR,
+      'Frame length OOB'))
+  }
+
+  header.control = header.type !== constants.frameType.DATA
+
+  this.state = 'frame-body'
+  this.pendingHeader = header
+  this.waiting = header.length
+  this.partial = !header.control
+
+  // TODO(indutny): eventually support partial padded DATA
+  if (this.partial) {
+    this.partial = (header.flags & constants.flags.PADDED) === 0
+  }
+
+  callback(null, null)
+}
+
+Parser.prototype.onFrameBody = function onFrameBody (header, buffer, callback) {
+  var frameType = constants.frameType
+
+  if (header.type === frameType.DATA) {
+    this.onDataFrame(header, buffer, callback)
+  } else if (header.type === frameType.HEADERS) {
+    this.onHeadersFrame(header, buffer, callback)
+  } else if (header.type === frameType.CONTINUATION) {
+    this.onContinuationFrame(header, buffer, callback)
+  } else if (header.type === frameType.WINDOW_UPDATE) {
+    this.onWindowUpdateFrame(header, buffer, callback)
+  } else if (header.type === frameType.RST_STREAM) {
+    this.onRSTFrame(header, buffer, callback)
+  } else if (header.type === frameType.SETTINGS) {
+    this.onSettingsFrame(header, buffer, callback)
+  } else if (header.type === frameType.PUSH_PROMISE) {
+    this.onPushPromiseFrame(header, buffer, callback)
+  } else if (header.type === frameType.PING) {
+    this.onPingFrame(header, buffer, callback)
+  } else if (header.type === frameType.GOAWAY) {
+    this.onGoawayFrame(header, buffer, callback)
+  } else if (header.type === frameType.PRIORITY) {
+    this.onPriorityFrame(header, buffer, callback)
+  } else if (header.type === frameType.X_FORWARDED_FOR) {
+    this.onXForwardedFrame(header, buffer, callback)
+  } else {
+    this.onUnknownFrame(header, buffer, callback)
+  }
+}
+
+Parser.prototype.onUnknownFrame = function onUnknownFrame (header, buffer, callback) {
+  if (this._lastHeaderBlock !== null) {
+    callback(this.error(constants.error.PROTOCOL_ERROR,
+      'Received unknown frame in the middle of a header block'))
+    return
+  }
+  callback(null, { type: 'unknown: ' + header.type })
+}
+
+Parser.prototype.unpadData = function unpadData (header, body, callback) {
+  var isPadded = (header.flags & constants.flags.PADDED) !== 0
+
+  if (!isPadded) { return callback(null, body) }
+
+  if (!body.has(1)) {
+    return callback(this.error(constants.error.FRAME_SIZE_ERROR,
+      'Not enough space for padding'))
+  }
+
+  var pad = body.readUInt8()
+  if (!body.has(pad)) {
+    return callback(this.error(constants.error.PROTOCOL_ERROR,
+      'Invalid padding size'))
+  }
+
+  var contents = body.clone(body.size - pad)
+  body.skip(body.size)
+  callback(null, contents)
+}
+
+Parser.prototype.onDataFrame = function onDataFrame (header, body, callback) {
+  var isEndStream = (header.flags & constants.flags.END_STREAM) !== 0
+
+  if (header.id === 0) {
+    return callback(this.error(constants.error.PROTOCOL_ERROR,
+      'Received DATA frame with stream=0'))
+  }
+
+  // Count received bytes
+  if (this.window) {
+    this.window.recv.update(-body.size)
+  }
+
+  this.unpadData(header, body, function (err, data) {
+    if (err) {
+      return callback(err)
+    }
+
+    callback(null, {
+      type: 'DATA',
+      id: header.id,
+      fin: isEndStream,
+      data: data.take(data.size)
+    })
+  })
+}
+
+Parser.prototype.initHeaderBlock = function initHeaderBlock (header,
+  frame,
+  block,
+  callback) {
+  if (this._lastHeaderBlock) {
+    return callback(this.error(constants.error.PROTOCOL_ERROR,
+      'Duplicate Stream ID'))
+  }
+
+  this._lastHeaderBlock = {
+    id: header.id,
+    frame: frame,
+    queue: [],
+    size: 0
+  }
+
+  this.queueHeaderBlock(header, block, callback)
+}
+
+Parser.prototype.queueHeaderBlock = function queueHeaderBlock (header,
+  block,
+  callback) {
+  var self = this
+  var item = this._lastHeaderBlock
+  if (!this._lastHeaderBlock || item.id !== header.id) {
+    return callback(this.error(constants.error.PROTOCOL_ERROR,
+      'No matching stream for continuation'))
+  }
+
+  var fin = (header.flags & constants.flags.END_HEADERS) !== 0
+
+  var chunks = block.toChunks()
+  for (var i = 0; i < chunks.length; i++) {
+    var chunk = chunks[i]
+    item.queue.push(chunk)
+    item.size += chunk.length
+  }
+
+  if (item.size >= self.maxHeaderListSize) {
+    return callback(this.error(constants.error.PROTOCOL_ERROR,
+      'Compressed header list is too large'))
+  }
+
+  if (!fin) { return callback(null, null) }
+  this._lastHeaderBlock = null
+
+  this.decompress.write(item.queue, function (err, chunks) {
+    if (err) {
+      return callback(self.error(constants.error.COMPRESSION_ERROR,
+        err.message))
+    }
+
+    var headers = {}
+    var size = 0
+    for (var i = 0; i < chunks.length; i++) {
+      var header = chunks[i]
+
+      size += header.name.length + header.value.length + 32
+      if (size >= self.maxHeaderListSize) {
+        return callback(self.error(constants.error.PROTOCOL_ERROR,
+          'Header list is too large'))
+      }
+
+      if (/[A-Z]/.test(header.name)) {
+        return callback(self.error(constants.error.PROTOCOL_ERROR,
+          'Header name must be lowercase'))
+      }
+
+      utils.addHeaderLine(header.name, header.value, headers)
+    }
+
+    item.frame.headers = headers
+    item.frame.path = headers[':path']
+
+    callback(null, item.frame)
+  })
+}
+
+Parser.prototype.onHeadersFrame = function onHeadersFrame (header,
+  body,
+  callback) {
+  var self = this
+
+  if (header.id === 0) {
+    return callback(this.error(constants.error.PROTOCOL_ERROR,
+      'Invalid stream id for HEADERS'))
+  }
+
+  this.unpadData(header, body, function (err, data) {
+    if (err) { return callback(err) }
+
+    var isPriority = (header.flags & constants.flags.PRIORITY) !== 0
+    if (!data.has(isPriority ? 5 : 0)) {
+      return callback(self.error(constants.error.FRAME_SIZE_ERROR,
+        'Not enough data for HEADERS'))
+    }
+
+    var exclusive = false
+    var dependency = 0
+    var weight = constants.DEFAULT_WEIGHT
+    if (isPriority) {
+      dependency = data.readUInt32BE()
+      exclusive = (dependency & 0x80000000) !== 0
+      dependency &= 0x7fffffff
+
+      // Weight's range is [1, 256]
+      weight = data.readUInt8() + 1
+    }
+
+    if (dependency === header.id) {
+      return callback(self.error(constants.error.PROTOCOL_ERROR,
+        'Stream can\'t dependend on itself'))
+    }
+
+    var streamInfo = {
+      type: 'HEADERS',
+      id: header.id,
+      priority: {
+        parent: dependency,
+        exclusive: exclusive,
+        weight: weight
+      },
+      fin: (header.flags & constants.flags.END_STREAM) !== 0,
+      writable: true,
+      headers: null,
+      path: null
+    }
+
+    self.initHeaderBlock(header, streamInfo, data, callback)
+  })
+}
+
+Parser.prototype.onContinuationFrame = function onContinuationFrame (header,
+  body,
+  callback) {
+  this.queueHeaderBlock(header, body, callback)
+}
+
+Parser.prototype.onRSTFrame = function onRSTFrame (header, body, callback) {
+  if (body.size !== 4) {
+    return callback(this.error(constants.error.FRAME_SIZE_ERROR,
+      'RST_STREAM length not 4'))
+  }
+
+  if (header.id === 0) {
+    return callback(this.error(constants.error.PROTOCOL_ERROR,
+      'Invalid stream id for RST_STREAM'))
+  }
+
+  callback(null, {
+    type: 'RST',
+    id: header.id,
+    code: constants.errorByCode[body.readUInt32BE()]
+  })
+}
+
+Parser.prototype._validateSettings = function _validateSettings (settings) {
+  if (settings['enable_push'] !== undefined &&
+      settings['enable_push'] !== 0 &&
+      settings['enable_push'] !== 1) {
+    return this.error(constants.error.PROTOCOL_ERROR,
+      'SETTINGS_ENABLE_PUSH must be 0 or 1')
+  }
+
+  if (settings['initial_window_size'] !== undefined &&
+      (settings['initial_window_size'] > constants.MAX_INITIAL_WINDOW_SIZE ||
+       settings['initial_window_size'] < 0)) {
+    return this.error(constants.error.FLOW_CONTROL_ERROR,
+      'SETTINGS_INITIAL_WINDOW_SIZE is OOB')
+  }
+
+  if (settings['max_frame_size'] !== undefined &&
+      (settings['max_frame_size'] > constants.ABSOLUTE_MAX_FRAME_SIZE ||
+       settings['max_frame_size'] < constants.INITIAL_MAX_FRAME_SIZE)) {
+    return this.error(constants.error.PROTOCOL_ERROR,
+      'SETTINGS_MAX_FRAME_SIZE is OOB')
+  }
+
+  return undefined
+}
+
+Parser.prototype.onSettingsFrame = function onSettingsFrame (header,
+  body,
+  callback) {
+  if (header.id !== 0) {
+    return callback(this.error(constants.error.PROTOCOL_ERROR,
+      'Invalid stream id for SETTINGS'))
+  }
+
+  var isAck = (header.flags & constants.flags.ACK) !== 0
+  if (isAck && body.size !== 0) {
+    return callback(this.error(constants.error.FRAME_SIZE_ERROR,
+      'SETTINGS with ACK and non-zero length'))
+  }
+
+  if (isAck) {
+    return callback(null, { type: 'ACK_SETTINGS' })
+  }
+
+  if (body.size % 6 !== 0) {
+    return callback(this.error(constants.error.FRAME_SIZE_ERROR,
+      'SETTINGS length not multiple of 6'))
+  }
+
+  var settings = {}
+  while (!body.isEmpty()) {
+    var id = body.readUInt16BE()
+    var value = body.readUInt32BE()
+    var name = constants.settingsIndex[id]
+
+    if (name) {
+      settings[name] = value
+    }
+  }
+
+  var err = this._validateSettings(settings)
+  if (err !== undefined) {
+    return callback(err)
+  }
+
+  callback(null, {
+    type: 'SETTINGS',
+    settings: settings
+  })
+}
+
+Parser.prototype.onPushPromiseFrame = function onPushPromiseFrame (header,
+  body,
+  callback) {
+  if (header.id === 0) {
+    return callback(this.error(constants.error.PROTOCOL_ERROR,
+      'Invalid stream id for PUSH_PROMISE'))
+  }
+
+  var self = this
+  this.unpadData(header, body, function (err, data) {
+    if (err) {
+      return callback(err)
+    }
+
+    if (!data.has(4)) {
+      return callback(self.error(constants.error.FRAME_SIZE_ERROR,
+        'PUSH_PROMISE length less than 4'))
+    }
+
+    var streamInfo = {
+      type: 'PUSH_PROMISE',
+      id: header.id,
+      fin: false,
+      promisedId: data.readUInt32BE() & 0x7fffffff,
+      headers: null,
+      path: null
+    }
+
+    self.initHeaderBlock(header, streamInfo, data, callback)
+  })
+}
+
+Parser.prototype.onPingFrame = function onPingFrame (header, body, callback) {
+  if (body.size !== 8) {
+    return callback(this.error(constants.error.FRAME_SIZE_ERROR,
+      'PING length != 8'))
+  }
+
+  if (header.id !== 0) {
+    return callback(this.error(constants.error.PROTOCOL_ERROR,
+      'Invalid stream id for PING'))
+  }
+
+  var ack = (header.flags & constants.flags.ACK) !== 0
+  callback(null, { type: 'PING', opaque: body.take(body.size), ack: ack })
+}
+
+Parser.prototype.onGoawayFrame = function onGoawayFrame (header,
+  body,
+  callback) {
+  if (!body.has(8)) {
+    return callback(this.error(constants.error.FRAME_SIZE_ERROR,
+      'GOAWAY length < 8'))
+  }
+
+  if (header.id !== 0) {
+    return callback(this.error(constants.error.PROTOCOL_ERROR,
+      'Invalid stream id for GOAWAY'))
+  }
+
+  var frame = {
+    type: 'GOAWAY',
+    lastId: body.readUInt32BE(),
+    code: constants.goawayByCode[body.readUInt32BE()]
+  }
+
+  if (body.size !== 0) { frame.debug = body.take(body.size) }
+
+  callback(null, frame)
+}
+
+Parser.prototype.onPriorityFrame = function onPriorityFrame (header,
+  body,
+  callback) {
+  if (body.size !== 5) {
+    return callback(this.error(constants.error.FRAME_SIZE_ERROR,
+      'PRIORITY length != 5'))
+  }
+
+  if (header.id === 0) {
+    return callback(this.error(constants.error.PROTOCOL_ERROR,
+      'Invalid stream id for PRIORITY'))
+  }
+
+  var dependency = body.readUInt32BE()
+
+  // Again the range is from 1 to 256
+  var weight = body.readUInt8() + 1
+
+  if (dependency === header.id) {
+    return callback(this.error(constants.error.PROTOCOL_ERROR,
+      'Stream can\'t dependend on itself'))
+  }
+
+  callback(null, {
+    type: 'PRIORITY',
+    id: header.id,
+    priority: {
+      exclusive: (dependency & 0x80000000) !== 0,
+      parent: dependency & 0x7fffffff,
+      weight: weight
+    }
+  })
+}
+
+Parser.prototype.onWindowUpdateFrame = function onWindowUpdateFrame (header,
+  body,
+  callback) {
+  if (body.size !== 4) {
+    return callback(this.error(constants.error.FRAME_SIZE_ERROR,
+      'WINDOW_UPDATE length != 4'))
+  }
+
+  var delta = body.readInt32BE()
+  if (delta === 0) {
+    return callback(this.error(constants.error.PROTOCOL_ERROR,
+      'WINDOW_UPDATE delta == 0'))
+  }
+
+  callback(null, {
+    type: 'WINDOW_UPDATE',
+    id: header.id,
+    delta: delta
+  })
+}
+
+Parser.prototype.onXForwardedFrame = function onXForwardedFrame (header,
+  body,
+  callback) {
+  callback(null, {
+    type: 'X_FORWARDED_FOR',
+    host: body.take(body.size).toString()
+  })
+}
Index: frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/spdy/constants.js
===================================================================
--- frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/spdy/constants.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/spdy/constants.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,146 @@
+'use strict'
+
+var transport = require('../../../spdy-transport')
+var base = transport.protocol.base
+
+exports.FRAME_HEADER_SIZE = 8
+
+exports.PING_OPAQUE_SIZE = 4
+
+exports.MAX_CONCURRENT_STREAMS = Infinity
+exports.DEFAULT_MAX_HEADER_LIST_SIZE = Infinity
+
+exports.DEFAULT_WEIGHT = 16
+
+exports.frameType = {
+  SYN_STREAM: 1,
+  SYN_REPLY: 2,
+  RST_STREAM: 3,
+  SETTINGS: 4,
+  PING: 6,
+  GOAWAY: 7,
+  HEADERS: 8,
+  WINDOW_UPDATE: 9,
+
+  // Custom
+  X_FORWARDED_FOR: 0xf000
+}
+
+exports.flags = {
+  FLAG_FIN: 0x01,
+  FLAG_COMPRESSED: 0x02,
+  FLAG_UNIDIRECTIONAL: 0x02
+}
+
+exports.error = {
+  PROTOCOL_ERROR: 1,
+  INVALID_STREAM: 2,
+  REFUSED_STREAM: 3,
+  UNSUPPORTED_VERSION: 4,
+  CANCEL: 5,
+  INTERNAL_ERROR: 6,
+  FLOW_CONTROL_ERROR: 7,
+  STREAM_IN_USE: 8,
+  // STREAM_ALREADY_CLOSED: 9
+  STREAM_CLOSED: 9,
+  INVALID_CREDENTIALS: 10,
+  FRAME_TOO_LARGE: 11
+}
+exports.errorByCode = base.utils.reverse(exports.error)
+
+exports.settings = {
+  FLAG_SETTINGS_PERSIST_VALUE: 1,
+  FLAG_SETTINGS_PERSISTED: 2,
+
+  SETTINGS_UPLOAD_BANDWIDTH: 1,
+  SETTINGS_DOWNLOAD_BANDWIDTH: 2,
+  SETTINGS_ROUND_TRIP_TIME: 3,
+  SETTINGS_MAX_CONCURRENT_STREAMS: 4,
+  SETTINGS_CURRENT_CWND: 5,
+  SETTINGS_DOWNLOAD_RETRANS_RATE: 6,
+  SETTINGS_INITIAL_WINDOW_SIZE: 7,
+  SETTINGS_CLIENT_CERTIFICATE_VECTOR_SIZE: 8
+}
+
+exports.settingsIndex = [
+  null,
+
+  'upload_bandwidth',
+  'download_bandwidth',
+  'round_trip_time',
+  'max_concurrent_streams',
+  'current_cwnd',
+  'download_retrans_rate',
+  'initial_window_size',
+  'client_certificate_vector_size'
+]
+
+exports.DEFAULT_WINDOW = 64 * 1024
+exports.MAX_INITIAL_WINDOW_SIZE = 2147483647
+
+exports.goaway = {
+  OK: 0,
+  PROTOCOL_ERROR: 1,
+  INTERNAL_ERROR: 2
+}
+exports.goawayByCode = base.utils.reverse(exports.goaway)
+
+exports.statusReason = {
+  100: 'Continue',
+  101: 'Switching Protocols',
+  102: 'Processing', // RFC 2518, obsoleted by RFC 4918
+  200: 'OK',
+  201: 'Created',
+  202: 'Accepted',
+  203: 'Non-Authoritative Information',
+  204: 'No Content',
+  205: 'Reset Content',
+  206: 'Partial Content',
+  207: 'Multi-Status', // RFC 4918
+  300: 'Multiple Choices',
+  301: 'Moved Permanently',
+  302: 'Moved Temporarily',
+  303: 'See Other',
+  304: 'Not Modified',
+  305: 'Use Proxy',
+  307: 'Temporary Redirect',
+  308: 'Permanent Redirect', // RFC 7238
+  400: 'Bad Request',
+  401: 'Unauthorized',
+  402: 'Payment Required',
+  403: 'Forbidden',
+  404: 'Not Found',
+  405: 'Method Not Allowed',
+  406: 'Not Acceptable',
+  407: 'Proxy Authentication Required',
+  408: 'Request Time-out',
+  409: 'Conflict',
+  410: 'Gone',
+  411: 'Length Required',
+  412: 'Precondition Failed',
+  413: 'Request Entity Too Large',
+  414: 'Request-URI Too Large',
+  415: 'Unsupported Media Type',
+  416: 'Requested Range Not Satisfiable',
+  417: 'Expectation Failed',
+  418: 'I\'m a teapot', // RFC 2324
+  422: 'Unprocessable Entity', // RFC 4918
+  423: 'Locked', // RFC 4918
+  424: 'Failed Dependency', // RFC 4918
+  425: 'Unordered Collection', // RFC 4918
+  426: 'Upgrade Required', // RFC 2817
+  428: 'Precondition Required', // RFC 6585
+  429: 'Too Many Requests', // RFC 6585
+  431: 'Request Header Fields Too Large', // RFC 6585
+  500: 'Internal Server Error',
+  501: 'Not Implemented',
+  502: 'Bad Gateway',
+  503: 'Service Unavailable',
+  504: 'Gateway Time-out',
+  505: 'HTTP Version Not Supported',
+  506: 'Variant Also Negotiates', // RFC 2295
+  507: 'Insufficient Storage', // RFC 4918
+  509: 'Bandwidth Limit Exceeded',
+  510: 'Not Extended', // RFC 2774
+  511: 'Network Authentication Required' // RFC 6585
+}
Index: frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/spdy/dictionary.js
===================================================================
--- frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/spdy/dictionary.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/spdy/dictionary.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,203 @@
+'use strict'
+
+var dictionary = {}
+module.exports = dictionary
+
+dictionary[2] = Buffer.from([
+  'optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-',
+  'languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi',
+  'f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser',
+  '-agent10010120020120220320420520630030130230330430530630740040140240340440',
+  '5406407408409410411412413414415416417500501502503504505accept-rangesageeta',
+  'glocationproxy-authenticatepublicretry-afterservervarywarningwww-authentic',
+  'ateallowcontent-basecontent-encodingcache-controlconnectiondatetrailertran',
+  'sfer-encodingupgradeviawarningcontent-languagecontent-lengthcontent-locati',
+  'oncontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookieMo',
+  'ndayTuesdayWednesdayThursdayFridaySaturdaySundayJanFebMarAprMayJunJulAugSe',
+  'pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic',
+  'ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1',
+  '.1statusversionurl\x00'
+].join(''))
+
+dictionary[3] = Buffer.from([
+  0x00, 0x00, 0x00, 0x07, 0x6f, 0x70, 0x74, 0x69, // ....opti
+  0x6f, 0x6e, 0x73, 0x00, 0x00, 0x00, 0x04, 0x68, // ons....h
+  0x65, 0x61, 0x64, 0x00, 0x00, 0x00, 0x04, 0x70, // ead....p
+  0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x03, 0x70, // ost....p
+  0x75, 0x74, 0x00, 0x00, 0x00, 0x06, 0x64, 0x65, // ut....de
+  0x6c, 0x65, 0x74, 0x65, 0x00, 0x00, 0x00, 0x05, // lete....
+  0x74, 0x72, 0x61, 0x63, 0x65, 0x00, 0x00, 0x00, // trace...
+  0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x00, // .accept.
+  0x00, 0x00, 0x0e, 0x61, 0x63, 0x63, 0x65, 0x70, // ...accep
+  0x74, 0x2d, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, // t-charse
+  0x74, 0x00, 0x00, 0x00, 0x0f, 0x61, 0x63, 0x63, // t....acc
+  0x65, 0x70, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, // ept-enco
+  0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x0f, // ding....
+  0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x6c, // accept-l
+  0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x00, // anguage.
+  0x00, 0x00, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x70, // ...accep
+  0x74, 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73, // t-ranges
+  0x00, 0x00, 0x00, 0x03, 0x61, 0x67, 0x65, 0x00, // ....age.
+  0x00, 0x00, 0x05, 0x61, 0x6c, 0x6c, 0x6f, 0x77, // ...allow
+  0x00, 0x00, 0x00, 0x0d, 0x61, 0x75, 0x74, 0x68, // ....auth
+  0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, // orizatio
+  0x6e, 0x00, 0x00, 0x00, 0x0d, 0x63, 0x61, 0x63, // n....cac
+  0x68, 0x65, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72, // he-contr
+  0x6f, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x63, 0x6f, // ol....co
+  0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, // nnection
+  0x00, 0x00, 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, // ....cont
+  0x65, 0x6e, 0x74, 0x2d, 0x62, 0x61, 0x73, 0x65, // ent-base
+  0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, 0x6e, 0x74, // ....cont
+  0x65, 0x6e, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, // ent-enco
+  0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, // ding....
+  0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, // content-
+  0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, // language
+  0x00, 0x00, 0x00, 0x0e, 0x63, 0x6f, 0x6e, 0x74, // ....cont
+  0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x65, 0x6e, 0x67, // ent-leng
+  0x74, 0x68, 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, // th....co
+  0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x6f, // ntent-lo
+  0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, // cation..
+  0x00, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, // ..conten
+  0x74, 0x2d, 0x6d, 0x64, 0x35, 0x00, 0x00, 0x00, // t-md5...
+  0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, // .content
+  0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, // -range..
+  0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, // ..conten
+  0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x00, 0x00, // t-type..
+  0x00, 0x04, 0x64, 0x61, 0x74, 0x65, 0x00, 0x00, // ..date..
+  0x00, 0x04, 0x65, 0x74, 0x61, 0x67, 0x00, 0x00, // ..etag..
+  0x00, 0x06, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, // ..expect
+  0x00, 0x00, 0x00, 0x07, 0x65, 0x78, 0x70, 0x69, // ....expi
+  0x72, 0x65, 0x73, 0x00, 0x00, 0x00, 0x04, 0x66, // res....f
+  0x72, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x04, 0x68, // rom....h
+  0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x08, 0x69, // ost....i
+  0x66, 0x2d, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, // f-match.
+  0x00, 0x00, 0x11, 0x69, 0x66, 0x2d, 0x6d, 0x6f, // ...if-mo
+  0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2d, 0x73, // dified-s
+  0x69, 0x6e, 0x63, 0x65, 0x00, 0x00, 0x00, 0x0d, // ince....
+  0x69, 0x66, 0x2d, 0x6e, 0x6f, 0x6e, 0x65, 0x2d, // if-none-
+  0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, 0x00, 0x00, // match...
+  0x08, 0x69, 0x66, 0x2d, 0x72, 0x61, 0x6e, 0x67, // .if-rang
+  0x65, 0x00, 0x00, 0x00, 0x13, 0x69, 0x66, 0x2d, // e....if-
+  0x75, 0x6e, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, // unmodifi
+  0x65, 0x64, 0x2d, 0x73, 0x69, 0x6e, 0x63, 0x65, // ed-since
+  0x00, 0x00, 0x00, 0x0d, 0x6c, 0x61, 0x73, 0x74, // ....last
+  0x2d, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, // -modifie
+  0x64, 0x00, 0x00, 0x00, 0x08, 0x6c, 0x6f, 0x63, // d....loc
+  0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, // ation...
+  0x0c, 0x6d, 0x61, 0x78, 0x2d, 0x66, 0x6f, 0x72, // .max-for
+  0x77, 0x61, 0x72, 0x64, 0x73, 0x00, 0x00, 0x00, // wards...
+  0x06, 0x70, 0x72, 0x61, 0x67, 0x6d, 0x61, 0x00, // .pragma.
+  0x00, 0x00, 0x12, 0x70, 0x72, 0x6f, 0x78, 0x79, // ...proxy
+  0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, // -authent
+  0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, 0x00, // icate...
+  0x13, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2d, 0x61, // .proxy-a
+  0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, // uthoriza
+  0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x05, // tion....
+  0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, 0x00, // range...
+  0x07, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x72, // .referer
+  0x00, 0x00, 0x00, 0x0b, 0x72, 0x65, 0x74, 0x72, // ....retr
+  0x79, 0x2d, 0x61, 0x66, 0x74, 0x65, 0x72, 0x00, // y-after.
+  0x00, 0x00, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, // ...serve
+  0x72, 0x00, 0x00, 0x00, 0x02, 0x74, 0x65, 0x00, // r....te.
+  0x00, 0x00, 0x07, 0x74, 0x72, 0x61, 0x69, 0x6c, // ...trail
+  0x65, 0x72, 0x00, 0x00, 0x00, 0x11, 0x74, 0x72, // er....tr
+  0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x2d, 0x65, // ansfer-e
+  0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x00, // ncoding.
+  0x00, 0x00, 0x07, 0x75, 0x70, 0x67, 0x72, 0x61, // ...upgra
+  0x64, 0x65, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x73, // de....us
+  0x65, 0x72, 0x2d, 0x61, 0x67, 0x65, 0x6e, 0x74, // er-agent
+  0x00, 0x00, 0x00, 0x04, 0x76, 0x61, 0x72, 0x79, // ....vary
+  0x00, 0x00, 0x00, 0x03, 0x76, 0x69, 0x61, 0x00, // ....via.
+  0x00, 0x00, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, // ...warni
+  0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, 0x77, 0x77, // ng....ww
+  0x77, 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, // w-authen
+  0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, // ticate..
+  0x00, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, // ..method
+  0x00, 0x00, 0x00, 0x03, 0x67, 0x65, 0x74, 0x00, // ....get.
+  0x00, 0x00, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, // ...statu
+  0x73, 0x00, 0x00, 0x00, 0x06, 0x32, 0x30, 0x30, // s....200
+  0x20, 0x4f, 0x4b, 0x00, 0x00, 0x00, 0x07, 0x76, // .OK....v
+  0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x00, // ersion..
+  0x00, 0x08, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, // ..HTTP.1
+  0x2e, 0x31, 0x00, 0x00, 0x00, 0x03, 0x75, 0x72, // .1....ur
+  0x6c, 0x00, 0x00, 0x00, 0x06, 0x70, 0x75, 0x62, // l....pub
+  0x6c, 0x69, 0x63, 0x00, 0x00, 0x00, 0x0a, 0x73, // lic....s
+  0x65, 0x74, 0x2d, 0x63, 0x6f, 0x6f, 0x6b, 0x69, // et-cooki
+  0x65, 0x00, 0x00, 0x00, 0x0a, 0x6b, 0x65, 0x65, // e....kee
+  0x70, 0x2d, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x00, // p-alive.
+  0x00, 0x00, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, // ...origi
+  0x6e, 0x31, 0x30, 0x30, 0x31, 0x30, 0x31, 0x32, // n1001012
+  0x30, 0x31, 0x32, 0x30, 0x32, 0x32, 0x30, 0x35, // 01202205
+  0x32, 0x30, 0x36, 0x33, 0x30, 0x30, 0x33, 0x30, // 20630030
+  0x32, 0x33, 0x30, 0x33, 0x33, 0x30, 0x34, 0x33, // 23033043
+  0x30, 0x35, 0x33, 0x30, 0x36, 0x33, 0x30, 0x37, // 05306307
+  0x34, 0x30, 0x32, 0x34, 0x30, 0x35, 0x34, 0x30, // 40240540
+  0x36, 0x34, 0x30, 0x37, 0x34, 0x30, 0x38, 0x34, // 64074084
+  0x30, 0x39, 0x34, 0x31, 0x30, 0x34, 0x31, 0x31, // 09410411
+  0x34, 0x31, 0x32, 0x34, 0x31, 0x33, 0x34, 0x31, // 41241341
+  0x34, 0x34, 0x31, 0x35, 0x34, 0x31, 0x36, 0x34, // 44154164
+  0x31, 0x37, 0x35, 0x30, 0x32, 0x35, 0x30, 0x34, // 17502504
+  0x35, 0x30, 0x35, 0x32, 0x30, 0x33, 0x20, 0x4e, // 505203.N
+  0x6f, 0x6e, 0x2d, 0x41, 0x75, 0x74, 0x68, 0x6f, // on-Autho
+  0x72, 0x69, 0x74, 0x61, 0x74, 0x69, 0x76, 0x65, // ritative
+  0x20, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, // .Informa
+  0x74, 0x69, 0x6f, 0x6e, 0x32, 0x30, 0x34, 0x20, // tion204.
+  0x4e, 0x6f, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x65, // No.Conte
+  0x6e, 0x74, 0x33, 0x30, 0x31, 0x20, 0x4d, 0x6f, // nt301.Mo
+  0x76, 0x65, 0x64, 0x20, 0x50, 0x65, 0x72, 0x6d, // ved.Perm
+  0x61, 0x6e, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x34, // anently4
+  0x30, 0x30, 0x20, 0x42, 0x61, 0x64, 0x20, 0x52, // 00.Bad.R
+  0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x34, 0x30, // equest40
+  0x31, 0x20, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, // 1.Unauth
+  0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x34, 0x30, // orized40
+  0x33, 0x20, 0x46, 0x6f, 0x72, 0x62, 0x69, 0x64, // 3.Forbid
+  0x64, 0x65, 0x6e, 0x34, 0x30, 0x34, 0x20, 0x4e, // den404.N
+  0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, // ot.Found
+  0x35, 0x30, 0x30, 0x20, 0x49, 0x6e, 0x74, 0x65, // 500.Inte
+  0x72, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, // rnal.Ser
+  0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f, // ver.Erro
+  0x72, 0x35, 0x30, 0x31, 0x20, 0x4e, 0x6f, 0x74, // r501.Not
+  0x20, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, // .Impleme
+  0x6e, 0x74, 0x65, 0x64, 0x35, 0x30, 0x33, 0x20, // nted503.
+  0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20, // Service.
+  0x55, 0x6e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, // Unavaila
+  0x62, 0x6c, 0x65, 0x4a, 0x61, 0x6e, 0x20, 0x46, // bleJan.F
+  0x65, 0x62, 0x20, 0x4d, 0x61, 0x72, 0x20, 0x41, // eb.Mar.A
+  0x70, 0x72, 0x20, 0x4d, 0x61, 0x79, 0x20, 0x4a, // pr.May.J
+  0x75, 0x6e, 0x20, 0x4a, 0x75, 0x6c, 0x20, 0x41, // un.Jul.A
+  0x75, 0x67, 0x20, 0x53, 0x65, 0x70, 0x74, 0x20, // ug.Sept.
+  0x4f, 0x63, 0x74, 0x20, 0x4e, 0x6f, 0x76, 0x20, // Oct.Nov.
+  0x44, 0x65, 0x63, 0x20, 0x30, 0x30, 0x3a, 0x30, // Dec.00.0
+  0x30, 0x3a, 0x30, 0x30, 0x20, 0x4d, 0x6f, 0x6e, // 0.00.Mon
+  0x2c, 0x20, 0x54, 0x75, 0x65, 0x2c, 0x20, 0x57, // ..Tue..W
+  0x65, 0x64, 0x2c, 0x20, 0x54, 0x68, 0x75, 0x2c, // ed..Thu.
+  0x20, 0x46, 0x72, 0x69, 0x2c, 0x20, 0x53, 0x61, // .Fri..Sa
+  0x74, 0x2c, 0x20, 0x53, 0x75, 0x6e, 0x2c, 0x20, // t..Sun..
+  0x47, 0x4d, 0x54, 0x63, 0x68, 0x75, 0x6e, 0x6b, // GMTchunk
+  0x65, 0x64, 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, // ed.text.
+  0x68, 0x74, 0x6d, 0x6c, 0x2c, 0x69, 0x6d, 0x61, // html.ima
+  0x67, 0x65, 0x2f, 0x70, 0x6e, 0x67, 0x2c, 0x69, // ge.png.i
+  0x6d, 0x61, 0x67, 0x65, 0x2f, 0x6a, 0x70, 0x67, // mage.jpg
+  0x2c, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67, // .image.g
+  0x69, 0x66, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, // if.appli
+  0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, // cation.x
+  0x6d, 0x6c, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, // ml.appli
+  0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, // cation.x
+  0x68, 0x74, 0x6d, 0x6c, 0x2b, 0x78, 0x6d, 0x6c, // html.xml
+  0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x70, 0x6c, // .text.pl
+  0x61, 0x69, 0x6e, 0x2c, 0x74, 0x65, 0x78, 0x74, // ain.text
+  0x2f, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, // .javascr
+  0x69, 0x70, 0x74, 0x2c, 0x70, 0x75, 0x62, 0x6c, // ipt.publ
+  0x69, 0x63, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, // icprivat
+  0x65, 0x6d, 0x61, 0x78, 0x2d, 0x61, 0x67, 0x65, // emax-age
+  0x3d, 0x67, 0x7a, 0x69, 0x70, 0x2c, 0x64, 0x65, // .gzip.de
+  0x66, 0x6c, 0x61, 0x74, 0x65, 0x2c, 0x73, 0x64, // flate.sd
+  0x63, 0x68, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, // chcharse
+  0x74, 0x3d, 0x75, 0x74, 0x66, 0x2d, 0x38, 0x63, // t.utf-8c
+  0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x69, // harset.i
+  0x73, 0x6f, 0x2d, 0x38, 0x38, 0x35, 0x39, 0x2d, // so-8859-
+  0x31, 0x2c, 0x75, 0x74, 0x66, 0x2d, 0x2c, 0x2a, // 1.utf-..
+  0x2c, 0x65, 0x6e, 0x71, 0x3d, 0x30, 0x2e // .enq.0.
+])
+
+dictionary[3.1] = dictionary[3]
Index: frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/spdy/framer.js
===================================================================
--- frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/spdy/framer.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/spdy/framer.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,519 @@
+'use strict'
+
+var transport = require('../../../spdy-transport')
+var constants = require('./').constants
+var base = transport.protocol.base
+var utils = base.utils
+
+var assert = require('assert')
+var util = require('util')
+var Buffer = require('buffer').Buffer
+var WriteBuffer = require('wbuf')
+
+var debug = require('debug')('spdy:framer')
+
+function Framer (options) {
+  base.Framer.call(this, options)
+}
+util.inherits(Framer, base.Framer)
+module.exports = Framer
+
+Framer.create = function create (options) {
+  return new Framer(options)
+}
+
+Framer.prototype.setMaxFrameSize = function setMaxFrameSize (size) {
+  // http2-only
+}
+
+Framer.prototype.headersToDict = function headersToDict (headers,
+  preprocess,
+  callback) {
+  function stringify (value) {
+    if (value !== undefined) {
+      if (Array.isArray(value)) {
+        return value.join('\x00')
+      } else if (typeof value === 'string') {
+        return value
+      } else {
+        return value.toString()
+      }
+    } else {
+      return ''
+    }
+  }
+
+  // Lower case of all headers keys
+  var loweredHeaders = {}
+  Object.keys(headers || {}).map(function (key) {
+    loweredHeaders[key.toLowerCase()] = headers[key]
+  })
+
+  // Allow outer code to add custom headers or remove something
+  if (preprocess) { preprocess(loweredHeaders) }
+
+  // Transform object into kv pairs
+  var size = this.version === 2 ? 2 : 4
+  var len = size
+  var pairs = Object.keys(loweredHeaders).filter(function (key) {
+    var lkey = key.toLowerCase()
+
+    // Will be in `:host`
+    if (lkey === 'host' && this.version >= 3) {
+      return false
+    }
+
+    return lkey !== 'connection' && lkey !== 'keep-alive' &&
+           lkey !== 'proxy-connection' && lkey !== 'transfer-encoding'
+  }, this).map(function (key) {
+    var klen = Buffer.byteLength(key)
+    var value = stringify(loweredHeaders[key])
+    var vlen = Buffer.byteLength(value)
+
+    len += size * 2 + klen + vlen
+    return [klen, key, vlen, value]
+  })
+
+  var block = new WriteBuffer()
+  block.reserve(len)
+
+  if (this.version === 2) {
+    block.writeUInt16BE(pairs.length)
+  } else {
+    block.writeUInt32BE(pairs.length)
+  }
+
+  pairs.forEach(function (pair) {
+    // Write key length
+    if (this.version === 2) {
+      block.writeUInt16BE(pair[0])
+    } else {
+      block.writeUInt32BE(pair[0])
+    }
+
+    // Write key
+    block.write(pair[1])
+
+    // Write value length
+    if (this.version === 2) {
+      block.writeUInt16BE(pair[2])
+    } else {
+      block.writeUInt32BE(pair[2])
+    }
+    // Write value
+    block.write(pair[3])
+  }, this)
+
+  assert(this.compress !== null, 'Framer version not initialized')
+  this.compress.write(block.render(), callback)
+}
+
+Framer.prototype._frame = function _frame (frame, body, callback) {
+  if (!this.version) {
+    this.on('version', function () {
+      this._frame(frame, body, callback)
+    })
+    return
+  }
+
+  debug('id=%d type=%s', frame.id, frame.type)
+
+  var buffer = new WriteBuffer()
+
+  buffer.writeUInt16BE(0x8000 | this.version)
+  buffer.writeUInt16BE(constants.frameType[frame.type])
+  buffer.writeUInt8(frame.flags)
+  var len = buffer.skip(3)
+
+  body(buffer)
+
+  var frameSize = buffer.size - constants.FRAME_HEADER_SIZE
+  len.writeUInt24BE(frameSize)
+
+  var chunks = buffer.render()
+  var toWrite = {
+    stream: frame.id,
+    priority: false,
+    chunks: chunks,
+    callback: callback
+  }
+
+  this._resetTimeout()
+  this.schedule(toWrite)
+
+  return chunks
+}
+
+Framer.prototype._synFrame = function _synFrame (frame, callback) {
+  var self = this
+
+  if (!frame.path) {
+    throw new Error('`path` is required frame argument')
+  }
+
+  function preprocess (headers) {
+    var method = frame.method || base.constants.DEFAULT_METHOD
+    var version = frame.version || 'HTTP/1.1'
+    var scheme = frame.scheme || 'https'
+    var host = frame.host ||
+               (frame.headers && frame.headers.host) ||
+               base.constants.DEFAULT_HOST
+
+    if (self.version === 2) {
+      headers.method = method
+      headers.version = version
+      headers.url = frame.path
+      headers.scheme = scheme
+      headers.host = host
+      if (frame.status) {
+        headers.status = frame.status
+      }
+    } else {
+      headers[':method'] = method
+      headers[':version'] = version
+      headers[':path'] = frame.path
+      headers[':scheme'] = scheme
+      headers[':host'] = host
+      if (frame.status) { headers[':status'] = frame.status }
+    }
+  }
+
+  this.headersToDict(frame.headers, preprocess, function (err, chunks) {
+    if (err) {
+      if (callback) {
+        return callback(err)
+      } else {
+        return self.emit('error', err)
+      }
+    }
+
+    self._frame({
+      type: 'SYN_STREAM',
+      id: frame.id,
+      flags: frame.fin ? constants.flags.FLAG_FIN : 0
+    }, function (buf) {
+      buf.reserve(10)
+
+      buf.writeUInt32BE(frame.id & 0x7fffffff)
+      buf.writeUInt32BE(frame.associated & 0x7fffffff)
+
+      var weight = (frame.priority && frame.priority.weight) ||
+                   constants.DEFAULT_WEIGHT
+
+      // We only have 3 bits for priority in SPDY, try to fit it into this
+      var priority = utils.weightToPriority(weight)
+      buf.writeUInt8(priority << 5)
+
+      // CREDENTIALS slot
+      buf.writeUInt8(0)
+
+      for (var i = 0; i < chunks.length; i++) {
+        buf.copyFrom(chunks[i])
+      }
+    }, callback)
+  })
+}
+
+Framer.prototype.requestFrame = function requestFrame (frame, callback) {
+  this._synFrame({
+    id: frame.id,
+    fin: frame.fin,
+    associated: 0,
+    method: frame.method,
+    version: frame.version,
+    scheme: frame.scheme,
+    host: frame.host,
+    path: frame.path,
+    priority: frame.priority,
+    headers: frame.headers
+  }, callback)
+}
+
+Framer.prototype.responseFrame = function responseFrame (frame, callback) {
+  var self = this
+
+  var reason = frame.reason
+  if (!reason) {
+    reason = constants.statusReason[frame.status]
+  }
+
+  function preprocess (headers) {
+    if (self.version === 2) {
+      headers.status = frame.status + ' ' + reason
+      headers.version = 'HTTP/1.1'
+    } else {
+      headers[':status'] = frame.status + ' ' + reason
+      headers[':version'] = 'HTTP/1.1'
+    }
+  }
+
+  this.headersToDict(frame.headers, preprocess, function (err, chunks) {
+    if (err) {
+      if (callback) {
+        return callback(err)
+      } else {
+        return self.emit('error', err)
+      }
+    }
+
+    self._frame({
+      type: 'SYN_REPLY',
+      id: frame.id,
+      flags: 0
+    }, function (buf) {
+      buf.reserve(self.version === 2 ? 6 : 4)
+
+      buf.writeUInt32BE(frame.id & 0x7fffffff)
+
+      // Unused data
+      if (self.version === 2) {
+        buf.writeUInt16BE(0)
+      }
+
+      for (var i = 0; i < chunks.length; i++) {
+        buf.copyFrom(chunks[i])
+      }
+    }, callback)
+  })
+}
+
+Framer.prototype.pushFrame = function pushFrame (frame, callback) {
+  var self = this
+
+  this._checkPush(function (err) {
+    if (err) { return callback(err) }
+
+    self._synFrame({
+      id: frame.promisedId,
+      associated: frame.id,
+      method: frame.method,
+      status: frame.status || 200,
+      version: frame.version,
+      scheme: frame.scheme,
+      host: frame.host,
+      path: frame.path,
+      priority: frame.priority,
+
+      // Merge everything together, there is no difference in SPDY protocol
+      headers: Object.assign(Object.assign({}, frame.headers), frame.response)
+    }, callback)
+  })
+}
+
+Framer.prototype.headersFrame = function headersFrame (frame, callback) {
+  var self = this
+
+  this.headersToDict(frame.headers, null, function (err, chunks) {
+    if (err) {
+      if (callback) { return callback(err) } else {
+        return self.emit('error', err)
+      }
+    }
+
+    self._frame({
+      type: 'HEADERS',
+      id: frame.id,
+      priority: false,
+      flags: 0
+    }, function (buf) {
+      buf.reserve(4 + (self.version === 2 ? 2 : 0))
+      buf.writeUInt32BE(frame.id & 0x7fffffff)
+
+      // Unused data
+      if (self.version === 2) { buf.writeUInt16BE(0) }
+
+      for (var i = 0; i < chunks.length; i++) {
+        buf.copyFrom(chunks[i])
+      }
+    }, callback)
+  })
+}
+
+Framer.prototype.dataFrame = function dataFrame (frame, callback) {
+  if (!this.version) {
+    return this.on('version', function () {
+      this.dataFrame(frame, callback)
+    })
+  }
+
+  debug('id=%d type=DATA', frame.id)
+
+  var buffer = new WriteBuffer()
+  buffer.reserve(8 + frame.data.length)
+
+  buffer.writeUInt32BE(frame.id & 0x7fffffff)
+  buffer.writeUInt8(frame.fin ? 0x01 : 0x0)
+  buffer.writeUInt24BE(frame.data.length)
+  buffer.copyFrom(frame.data)
+
+  var chunks = buffer.render()
+  var toWrite = {
+    stream: frame.id,
+    priority: frame.priority,
+    chunks: chunks,
+    callback: callback
+  }
+
+  var self = this
+  this._resetTimeout()
+
+  var bypass = this.version < 3.1
+  this.window.send.update(-frame.data.length, bypass ? undefined : function () {
+    self._resetTimeout()
+    self.schedule(toWrite)
+  })
+
+  if (bypass) {
+    this._resetTimeout()
+    this.schedule(toWrite)
+  }
+}
+
+Framer.prototype.pingFrame = function pingFrame (frame, callback) {
+  this._frame({
+    type: 'PING',
+    id: 0,
+    flags: 0
+  }, function (buf, callback) {
+    buf.reserve(4)
+
+    var opaque = frame.opaque
+    buf.writeUInt32BE(opaque.readUInt32BE(opaque.length - 4, true))
+  }, callback)
+}
+
+Framer.prototype.rstFrame = function rstFrame (frame, callback) {
+  this._frame({
+    type: 'RST_STREAM',
+    id: frame.id,
+    flags: 0
+  }, function (buf) {
+    buf.reserve(8)
+
+    // Stream ID
+    buf.writeUInt32BE(frame.id & 0x7fffffff)
+    // Status Code
+    buf.writeUInt32BE(constants.error[frame.code])
+
+    // Extra debugging information
+    if (frame.extra) {
+      buf.write(frame.extra)
+    }
+  }, callback)
+}
+
+Framer.prototype.prefaceFrame = function prefaceFrame () {
+}
+
+Framer.prototype.settingsFrame = function settingsFrame (options, callback) {
+  var self = this
+
+  var key = this.version + '/' + JSON.stringify(options)
+
+  var settings = Framer.settingsCache[key]
+  if (settings) {
+    debug('cached settings')
+    this._resetTimeout()
+    this.schedule({
+      stream: 0,
+      priority: false,
+      chunks: settings,
+      callback: callback
+    })
+    return
+  }
+
+  var params = []
+  for (var i = 0; i < constants.settingsIndex.length; i++) {
+    var name = constants.settingsIndex[i]
+    if (!name) { continue }
+
+    // value: Infinity
+    if (!isFinite(options[name])) {
+      continue
+    }
+
+    if (options[name] !== undefined) {
+      params.push({ key: i, value: options[name] })
+    }
+  }
+
+  var frame = this._frame({
+    type: 'SETTINGS',
+    id: 0,
+    flags: 0
+  }, function (buf) {
+    buf.reserve(4 + 8 * params.length)
+
+    // Count of entries
+    buf.writeUInt32BE(params.length)
+
+    params.forEach(function (param) {
+      var flag = constants.settings.FLAG_SETTINGS_PERSIST_VALUE << 24
+
+      if (self.version === 2) {
+        buf.writeUInt32LE(flag | param.key)
+      } else { buf.writeUInt32BE(flag | param.key) }
+      buf.writeUInt32BE(param.value & 0x7fffffff)
+    })
+  }, callback)
+
+  Framer.settingsCache[key] = frame
+}
+Framer.settingsCache = {}
+
+Framer.prototype.ackSettingsFrame = function ackSettingsFrame (callback) {
+  if (callback) {
+    process.nextTick(callback)
+  }
+}
+
+Framer.prototype.windowUpdateFrame = function windowUpdateFrame (frame,
+  callback) {
+  this._frame({
+    type: 'WINDOW_UPDATE',
+    id: frame.id,
+    flags: 0
+  }, function (buf) {
+    buf.reserve(8)
+
+    // ID
+    buf.writeUInt32BE(frame.id & 0x7fffffff)
+
+    // Delta
+    buf.writeInt32BE(frame.delta)
+  }, callback)
+}
+
+Framer.prototype.goawayFrame = function goawayFrame (frame, callback) {
+  this._frame({
+    type: 'GOAWAY',
+    id: 0,
+    flags: 0
+  }, function (buf) {
+    buf.reserve(8)
+
+    // Last-good-stream-ID
+    buf.writeUInt32BE(frame.lastId & 0x7fffffff)
+    // Status
+    buf.writeUInt32BE(constants.goaway[frame.code])
+  }, callback)
+}
+
+Framer.prototype.priorityFrame = function priorityFrame (frame, callback) {
+  // No such thing in SPDY
+  if (callback) {
+    process.nextTick(callback)
+  }
+}
+
+Framer.prototype.xForwardedFor = function xForwardedFor (frame, callback) {
+  this._frame({
+    type: 'X_FORWARDED_FOR',
+    id: 0,
+    flags: 0
+  }, function (buf) {
+    buf.writeUInt32BE(Buffer.byteLength(frame.host))
+    buf.write(frame.host)
+  }, callback)
+}
Index: frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/spdy/index.js
===================================================================
--- frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/spdy/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/spdy/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,9 @@
+'use strict'
+
+exports.name = 'spdy'
+
+exports.dictionary = require('./dictionary')
+exports.constants = require('./constants')
+exports.parser = require('./parser')
+exports.framer = require('./framer')
+exports.compressionPool = require('./zlib-pool')
Index: frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/spdy/parser.js
===================================================================
--- frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/spdy/parser.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/spdy/parser.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,485 @@
+'use strict'
+
+var parser = exports
+
+var transport = require('../../../spdy-transport')
+var base = transport.protocol.base
+var utils = base.utils
+var constants = require('./constants')
+
+var assert = require('assert')
+var util = require('util')
+var OffsetBuffer = require('obuf')
+
+function Parser (options) {
+  base.Parser.call(this, options)
+
+  this.isServer = options.isServer
+  this.waiting = constants.FRAME_HEADER_SIZE
+  this.state = 'frame-head'
+  this.pendingHeader = null
+}
+util.inherits(Parser, base.Parser)
+
+parser.create = function create (options) {
+  return new Parser(options)
+}
+
+Parser.prototype.setMaxFrameSize = function setMaxFrameSize (size) {
+  // http2-only
+}
+
+Parser.prototype.setMaxHeaderListSize = function setMaxHeaderListSize (size) {
+  // http2-only
+}
+
+// Only for testing
+Parser.prototype.skipPreface = function skipPreface () {
+}
+
+Parser.prototype.execute = function execute (buffer, callback) {
+  if (this.state === 'frame-head') { return this.onFrameHead(buffer, callback) }
+
+  assert(this.state === 'frame-body' && this.pendingHeader !== null)
+
+  var self = this
+  var header = this.pendingHeader
+  this.pendingHeader = null
+
+  this.onFrameBody(header, buffer, function (err, frame) {
+    if (err) {
+      return callback(err)
+    }
+
+    self.state = 'frame-head'
+    self.waiting = constants.FRAME_HEADER_SIZE
+    self.partial = false
+    callback(null, frame)
+  })
+}
+
+Parser.prototype.executePartial = function executePartial (buffer, callback) {
+  var header = this.pendingHeader
+
+  if (this.window) {
+    this.window.recv.update(-buffer.size)
+  }
+
+  // DATA frame
+  callback(null, {
+    type: 'DATA',
+    id: header.id,
+
+    // Partial DATA can't be FIN
+    fin: false,
+    data: buffer.take(buffer.size)
+  })
+}
+
+Parser.prototype.onFrameHead = function onFrameHead (buffer, callback) {
+  var header = {
+    control: (buffer.peekUInt8() & 0x80) === 0x80,
+    version: null,
+    type: null,
+    id: null,
+    flags: null,
+    length: null
+  }
+
+  if (header.control) {
+    header.version = buffer.readUInt16BE() & 0x7fff
+    header.type = buffer.readUInt16BE()
+  } else {
+    header.id = buffer.readUInt32BE(0) & 0x7fffffff
+  }
+  header.flags = buffer.readUInt8()
+  header.length = buffer.readUInt24BE()
+
+  if (this.version === null && header.control) {
+    // TODO(indutny): do ProtocolError here and in the rest of errors
+    if (header.version !== 2 && header.version !== 3) {
+      return callback(new Error('Unsupported SPDY version: ' + header.version))
+    }
+    this.setVersion(header.version)
+  }
+
+  this.state = 'frame-body'
+  this.waiting = header.length
+  this.pendingHeader = header
+  this.partial = !header.control
+
+  callback(null, null)
+}
+
+Parser.prototype.onFrameBody = function onFrameBody (header, buffer, callback) {
+  // Data frame
+  if (!header.control) {
+    // Count received bytes
+    if (this.window) {
+      this.window.recv.update(-buffer.size)
+    }
+
+    // No support for compressed DATA
+    if ((header.flags & constants.flags.FLAG_COMPRESSED) !== 0) {
+      return callback(new Error('DATA compression not supported'))
+    }
+
+    if (header.id === 0) {
+      return callback(this.error(constants.error.PROTOCOL_ERROR,
+        'Invalid stream id for DATA'))
+    }
+
+    return callback(null, {
+      type: 'DATA',
+      id: header.id,
+      fin: (header.flags & constants.flags.FLAG_FIN) !== 0,
+      data: buffer.take(buffer.size)
+    })
+  }
+
+  if (header.type === 0x01 || header.type === 0x02) { // SYN_STREAM or SYN_REPLY
+    this.onSynHeadFrame(header.type, header.flags, buffer, callback)
+  } else if (header.type === 0x03) { // RST_STREAM
+    this.onRSTFrame(buffer, callback)
+  } else if (header.type === 0x04) { // SETTINGS
+    this.onSettingsFrame(buffer, callback)
+  } else if (header.type === 0x05) {
+    callback(null, { type: 'NOOP' })
+  } else if (header.type === 0x06) { // PING
+    this.onPingFrame(buffer, callback)
+  } else if (header.type === 0x07) { // GOAWAY
+    this.onGoawayFrame(buffer, callback)
+  } else if (header.type === 0x08) { // HEADERS
+    this.onHeaderFrames(buffer, callback)
+  } else if (header.type === 0x09) { // WINDOW_UPDATE
+    this.onWindowUpdateFrame(buffer, callback)
+  } else if (header.type === 0xf000) { // X-FORWARDED
+    this.onXForwardedFrame(buffer, callback)
+  } else {
+    callback(null, { type: 'unknown: ' + header.type })
+  }
+}
+
+Parser.prototype._filterHeader = function _filterHeader (headers, name) {
+  var res = {}
+  var keys = Object.keys(headers)
+
+  for (var i = 0; i < keys.length; i++) {
+    var key = keys[i]
+    if (key !== name) {
+      res[key] = headers[key]
+    }
+  }
+
+  return res
+}
+
+Parser.prototype.onSynHeadFrame = function onSynHeadFrame (type,
+  flags,
+  body,
+  callback) {
+  var self = this
+  var stream = type === 0x01
+  var offset = stream ? 10 : this.version === 2 ? 6 : 4
+
+  if (!body.has(offset)) {
+    return callback(new Error('SynHead OOB'))
+  }
+
+  var head = body.clone(offset)
+  body.skip(offset)
+  this.parseKVs(body, function (err, headers) {
+    if (err) {
+      return callback(err)
+    }
+
+    if (stream &&
+        (!headers[':method'] || !headers[':path'])) {
+      return callback(new Error('Missing `:method` and/or `:path` header'))
+    }
+
+    var id = head.readUInt32BE() & 0x7fffffff
+
+    if (id === 0) {
+      return callback(self.error(constants.error.PROTOCOL_ERROR,
+        'Invalid stream id for HEADERS'))
+    }
+
+    var associated = stream ? head.readUInt32BE() & 0x7fffffff : 0
+    var priority = stream
+      ? head.readUInt8() >> 5
+      : utils.weightToPriority(constants.DEFAULT_WEIGHT)
+    var fin = (flags & constants.flags.FLAG_FIN) !== 0
+    var unidir = (flags & constants.flags.FLAG_UNIDIRECTIONAL) !== 0
+    var path = headers[':path']
+
+    var isPush = stream && associated !== 0
+
+    var weight = utils.priorityToWeight(priority)
+    var priorityInfo = {
+      weight: weight,
+      exclusive: false,
+      parent: 0
+    }
+
+    if (!isPush) {
+      callback(null, {
+        type: 'HEADERS',
+        id: id,
+        priority: priorityInfo,
+        fin: fin,
+        writable: !unidir,
+        headers: headers,
+        path: path
+      })
+      return
+    }
+
+    if (stream && !headers[':status']) {
+      return callback(new Error('Missing `:status` header'))
+    }
+
+    var filteredHeaders = self._filterHeader(headers, ':status')
+
+    callback(null, [ {
+      type: 'PUSH_PROMISE',
+      id: associated,
+      fin: false,
+      promisedId: id,
+      headers: filteredHeaders,
+      path: path
+    }, {
+      type: 'HEADERS',
+      id: id,
+      fin: fin,
+      priority: priorityInfo,
+      writable: true,
+      path: undefined,
+      headers: {
+        ':status': headers[':status']
+      }
+    }])
+  })
+}
+
+Parser.prototype.onHeaderFrames = function onHeaderFrames (body, callback) {
+  var offset = this.version === 2 ? 6 : 4
+  if (!body.has(offset)) {
+    return callback(new Error('HEADERS OOB'))
+  }
+
+  var streamId = body.readUInt32BE() & 0x7fffffff
+  if (this.version === 2) { body.skip(2) }
+
+  this.parseKVs(body, function (err, headers) {
+    if (err) { return callback(err) }
+
+    callback(null, {
+      type: 'HEADERS',
+      priority: {
+        parent: 0,
+        exclusive: false,
+        weight: constants.DEFAULT_WEIGHT
+      },
+      id: streamId,
+      fin: false,
+      writable: true,
+      path: undefined,
+      headers: headers
+    })
+  })
+}
+
+Parser.prototype.parseKVs = function parseKVs (buffer, callback) {
+  var self = this
+
+  this.decompress.write(buffer.toChunks(), function (err, chunks) {
+    if (err) {
+      return callback(err)
+    }
+
+    var buffer = new OffsetBuffer()
+    for (var i = 0; i < chunks.length; i++) {
+      buffer.push(chunks[i])
+    }
+
+    var size = self.version === 2 ? 2 : 4
+    if (!buffer.has(size)) { return callback(new Error('KV OOB')) }
+
+    var count = self.version === 2
+      ? buffer.readUInt16BE()
+      : buffer.readUInt32BE()
+
+    var headers = {}
+
+    function readString () {
+      if (!buffer.has(size)) { return null }
+      var len = self.version === 2
+        ? buffer.readUInt16BE()
+        : buffer.readUInt32BE()
+
+      if (!buffer.has(len)) { return null }
+
+      var value = buffer.take(len)
+      return value.toString()
+    }
+
+    while (count > 0) {
+      var key = readString()
+      var value = readString()
+
+      if (key === null || value === null) {
+        return callback(new Error('Headers OOB'))
+      }
+
+      if (self.version < 3) {
+        var isInternal = /^(method|version|url|host|scheme|status)$/.test(key)
+        if (key === 'url') {
+          key = 'path'
+        }
+        if (isInternal) {
+          key = ':' + key
+        }
+      }
+
+      // Compatibility with HTTP2
+      if (key === ':status') {
+        value = value.split(/ /g, 2)[0]
+      }
+
+      count--
+      if (key === ':host') {
+        key = ':authority'
+      }
+
+      // Skip version, not present in HTTP2
+      if (key === ':version') {
+        continue
+      }
+
+      value = value.split(/\0/g)
+      for (var j = 0; j < value.length; j++) {
+        utils.addHeaderLine(key, value[j], headers)
+      }
+    }
+
+    callback(null, headers)
+  })
+}
+
+Parser.prototype.onRSTFrame = function onRSTFrame (body, callback) {
+  if (!body.has(8)) { return callback(new Error('RST OOB')) }
+
+  var frame = {
+    type: 'RST',
+    id: body.readUInt32BE() & 0x7fffffff,
+    code: constants.errorByCode[body.readUInt32BE()]
+  }
+
+  if (frame.id === 0) {
+    return callback(this.error(constants.error.PROTOCOL_ERROR,
+      'Invalid stream id for RST'))
+  }
+
+  if (body.size !== 0) {
+    frame.extra = body.take(body.size)
+  }
+  callback(null, frame)
+}
+
+Parser.prototype.onSettingsFrame = function onSettingsFrame (body, callback) {
+  if (!body.has(4)) {
+    return callback(new Error('SETTINGS OOB'))
+  }
+
+  var settings = {}
+  var number = body.readUInt32BE()
+  var idMap = {
+    1: 'upload_bandwidth',
+    2: 'download_bandwidth',
+    3: 'round_trip_time',
+    4: 'max_concurrent_streams',
+    5: 'current_cwnd',
+    6: 'download_retrans_rate',
+    7: 'initial_window_size',
+    8: 'client_certificate_vector_size'
+  }
+
+  if (!body.has(number * 8)) {
+    return callback(new Error('SETTINGS OOB#2'))
+  }
+
+  for (var i = 0; i < number; i++) {
+    var id = this.version === 2
+      ? body.readUInt32LE()
+      : body.readUInt32BE()
+
+    var flags = (id >> 24) & 0xff
+    id = id & 0xffffff
+
+    // Skip persisted settings
+    if (flags & 0x2) { continue }
+
+    var name = idMap[id]
+
+    settings[name] = body.readUInt32BE()
+  }
+
+  callback(null, {
+    type: 'SETTINGS',
+    settings: settings
+  })
+}
+
+Parser.prototype.onPingFrame = function onPingFrame (body, callback) {
+  if (!body.has(4)) {
+    return callback(new Error('PING OOB'))
+  }
+
+  var isServer = this.isServer
+  var opaque = body.clone(body.size).take(body.size)
+  var id = body.readUInt32BE()
+  var ack = isServer ? (id % 2 === 0) : (id % 2 === 1)
+
+  callback(null, { type: 'PING', opaque: opaque, ack: ack })
+}
+
+Parser.prototype.onGoawayFrame = function onGoawayFrame (body, callback) {
+  if (!body.has(8)) {
+    return callback(new Error('GOAWAY OOB'))
+  }
+
+  callback(null, {
+    type: 'GOAWAY',
+    lastId: body.readUInt32BE() & 0x7fffffff,
+    code: constants.goawayByCode[body.readUInt32BE()]
+  })
+}
+
+Parser.prototype.onWindowUpdateFrame = function onWindowUpdateFrame (body,
+  callback) {
+  if (!body.has(8)) {
+    return callback(new Error('WINDOW_UPDATE OOB'))
+  }
+
+  callback(null, {
+    type: 'WINDOW_UPDATE',
+    id: body.readUInt32BE() & 0x7fffffff,
+    delta: body.readInt32BE()
+  })
+}
+
+Parser.prototype.onXForwardedFrame = function onXForwardedFrame (body,
+  callback) {
+  if (!body.has(4)) {
+    return callback(new Error('X_FORWARDED OOB'))
+  }
+
+  var len = body.readUInt32BE()
+  if (!body.has(len)) { return callback(new Error('X_FORWARDED host length OOB')) }
+
+  callback(null, {
+    type: 'X_FORWARDED_FOR',
+    host: body.take(len).toString()
+  })
+}
Index: frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/spdy/zlib-pool.js
===================================================================
--- frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/spdy/zlib-pool.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/spdy-transport/lib/spdy-transport/protocol/spdy/zlib-pool.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,65 @@
+'use strict'
+
+var zlibpool = exports
+var zlib = require('zlib')
+
+var transport = require('../../../spdy-transport')
+
+// TODO(indutny): think about it, why has it always been Z_SYNC_FLUSH here.
+// It should be possible to manually flush stuff after the write instead
+function createDeflate (version, compression) {
+  var deflate = zlib.createDeflate({
+    dictionary: transport.protocol.spdy.dictionary[version],
+    flush: zlib.Z_SYNC_FLUSH,
+    windowBits: 11,
+    level: compression ? zlib.Z_DEFAULT_COMPRESSION : zlib.Z_NO_COMPRESSION
+  })
+
+  // For node.js v0.8
+  deflate._flush = zlib.Z_SYNC_FLUSH
+
+  return deflate
+}
+
+function createInflate (version) {
+  var inflate = zlib.createInflate({
+    dictionary: transport.protocol.spdy.dictionary[version],
+    flush: zlib.Z_SYNC_FLUSH
+  })
+
+  // For node.js v0.8
+  inflate._flush = zlib.Z_SYNC_FLUSH
+
+  return inflate
+}
+
+function Pool (compression) {
+  this.compression = compression
+  this.pool = {
+    2: [],
+    3: [],
+    3.1: []
+  }
+}
+
+zlibpool.create = function create (compression) {
+  return new Pool(compression)
+}
+
+Pool.prototype.get = function get (version) {
+  if (this.pool[version].length > 0) {
+    return this.pool[version].pop()
+  } else {
+    var id = version
+
+    return {
+      version: version,
+      compress: createDeflate(id, this.compression),
+      decompress: createInflate(id)
+    }
+  }
+}
+
+Pool.prototype.put = function put (pair) {
+  this.pool[pair.version].push(pair)
+}
Index: frontend/node_modules/spdy-transport/lib/spdy-transport/stream.js
===================================================================
--- frontend/node_modules/spdy-transport/lib/spdy-transport/stream.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/spdy-transport/lib/spdy-transport/stream.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,710 @@
+'use strict'
+
+var transport = require('../spdy-transport')
+
+var assert = require('assert')
+var util = require('util')
+
+var debug = {
+  client: require('debug')('spdy:stream:client'),
+  server: require('debug')('spdy:stream:server')
+}
+var Duplex = require('readable-stream').Duplex
+
+function Stream (connection, options) {
+  Duplex.call(this)
+
+  var connectionState = connection._spdyState
+
+  var state = {}
+  this._spdyState = state
+
+  this.id = options.id
+  this.method = options.method
+  this.path = options.path
+  this.host = options.host
+  this.headers = options.headers || {}
+  this.connection = connection
+  this.parent = options.parent || null
+
+  state.socket = null
+  state.protocol = connectionState.protocol
+  state.constants = state.protocol.constants
+
+  // See _initPriority()
+  state.priority = null
+
+  state.version = this.connection.getVersion()
+  state.isServer = this.connection.isServer()
+  state.debug = state.isServer ? debug.server : debug.client
+
+  state.framer = connectionState.framer
+  state.parser = connectionState.parser
+
+  state.request = options.request
+  state.needResponse = options.request
+  state.window = connectionState.streamWindow.clone(options.id)
+  state.sessionWindow = connectionState.window
+  state.maxChunk = connectionState.maxChunk
+
+  // Can't send incoming request
+  // (See `.send()` method)
+  state.sent = !state.request
+
+  state.readable = options.readable !== false
+  state.writable = options.writable !== false
+
+  state.aborted = false
+
+  state.corked = 0
+  state.corkQueue = []
+
+  state.timeout = new transport.utils.Timeout(this)
+
+  this.on('finish', this._onFinish)
+  this.on('end', this._onEnd)
+
+  var self = this
+  function _onWindowOverflow () {
+    self._onWindowOverflow()
+  }
+
+  state.window.recv.on('overflow', _onWindowOverflow)
+  state.window.send.on('overflow', _onWindowOverflow)
+
+  this._initPriority(options.priority)
+
+  if (!state.readable) { this.push(null) }
+  if (!state.writable) {
+    this._writableState.ended = true
+    this._writableState.finished = true
+  }
+}
+util.inherits(Stream, Duplex)
+exports.Stream = Stream
+
+Stream.prototype._init = function _init (socket) {
+  this.socket = socket
+}
+
+Stream.prototype._initPriority = function _initPriority (priority) {
+  var state = this._spdyState
+  var connectionState = this.connection._spdyState
+  var root = connectionState.priorityRoot
+
+  if (!priority) {
+    state.priority = root.addDefault(this.id)
+    return
+  }
+
+  state.priority = root.add({
+    id: this.id,
+    parent: priority.parent,
+    weight: priority.weight,
+    exclusive: priority.exclusive
+  })
+}
+
+Stream.prototype._handleFrame = function _handleFrame (frame) {
+  var state = this._spdyState
+
+  // Ignore any kind of data after abort
+  if (state.aborted) {
+    state.debug('id=%d ignoring frame=%s after abort', this.id, frame.type)
+    return
+  }
+
+  // Restart the timer on incoming frames
+  state.timeout.reset()
+
+  if (frame.type === 'DATA') {
+    this._handleData(frame)
+  } else if (frame.type === 'HEADERS') {
+    this._handleHeaders(frame)
+  } else if (frame.type === 'RST') {
+    this._handleRST(frame)
+  } else if (frame.type === 'WINDOW_UPDATE') { this._handleWindowUpdate(frame) } else if (frame.type === 'PRIORITY') {
+    this._handlePriority(frame)
+  } else if (frame.type === 'PUSH_PROMISE') { this._handlePushPromise(frame) }
+
+  if (frame.fin) {
+    state.debug('id=%d end', this.id)
+    this.push(null)
+  }
+}
+
+function checkAborted (stream, state, callback) {
+  if (state.aborted) {
+    state.debug('id=%d abort write', stream.id)
+    process.nextTick(function () {
+      callback(new Error('Stream write aborted'))
+    })
+    return true
+  }
+
+  return false
+}
+
+function _send (stream, state, data, callback) {
+  if (checkAborted(stream, state, callback)) {
+    return
+  }
+
+  state.debug('id=%d presend=%d', stream.id, data.length)
+
+  state.timeout.reset()
+
+  state.window.send.update(-data.length, function () {
+    if (checkAborted(stream, state, callback)) {
+      return
+    }
+
+    state.debug('id=%d send=%d', stream.id, data.length)
+
+    state.timeout.reset()
+
+    state.framer.dataFrame({
+      id: stream.id,
+      priority: state.priority.getPriority(),
+      fin: false,
+      data: data
+    }, function (err) {
+      state.debug('id=%d postsend=%d', stream.id, data.length)
+      callback(err)
+    })
+  })
+}
+
+Stream.prototype._write = function _write (data, enc, callback) {
+  var state = this._spdyState
+
+  // Send the request if it wasn't sent
+  if (!state.sent) { this.send() }
+
+  // Writes should come after pending control frames (response and headers)
+  if (state.corked !== 0) {
+    var self = this
+    state.corkQueue.push(function () {
+      self._write(data, enc, callback)
+    })
+    return
+  }
+
+  // Split DATA in chunks to prevent window from going negative
+  this._splitStart(data, _send, callback)
+}
+
+Stream.prototype._splitStart = function _splitStart (data, onChunk, callback) {
+  return this._split(data, 0, onChunk, callback)
+}
+
+Stream.prototype._split = function _split (data, offset, onChunk, callback) {
+  if (offset === data.length) {
+    return process.nextTick(callback)
+  }
+
+  var state = this._spdyState
+  var local = state.window.send
+  var session = state.sessionWindow.send
+
+  var availSession = Math.max(0, session.getCurrent())
+  if (availSession === 0) {
+    availSession = session.getMax()
+  }
+  var availLocal = Math.max(0, local.getCurrent())
+  if (availLocal === 0) {
+    availLocal = local.getMax()
+  }
+
+  var avail = Math.min(availSession, availLocal)
+  avail = Math.min(avail, state.maxChunk)
+
+  var self = this
+
+  if (avail === 0) {
+    state.window.send.update(0, function () {
+      self._split(data, offset, onChunk, callback)
+    })
+    return
+  }
+
+  // Split data in chunks in a following way:
+  var limit = avail
+  var size = Math.min(data.length - offset, limit)
+
+  var chunk = data.slice(offset, offset + size)
+
+  onChunk(this, state, chunk, function (err) {
+    if (err) { return callback(err) }
+
+    // Get the next chunk
+    self._split(data, offset + size, onChunk, callback)
+  })
+}
+
+Stream.prototype._read = function _read () {
+  var state = this._spdyState
+
+  if (!state.window.recv.isDraining()) {
+    return
+  }
+
+  var delta = state.window.recv.getDelta()
+
+  state.debug('id=%d window emptying, update by %d', this.id, delta)
+
+  state.window.recv.update(delta)
+  state.framer.windowUpdateFrame({
+    id: this.id,
+    delta: delta
+  })
+}
+
+Stream.prototype._handleData = function _handleData (frame) {
+  var state = this._spdyState
+
+  // DATA on ended or not readable stream!
+  if (!state.readable || this._readableState.ended) {
+    state.framer.rstFrame({ id: this.id, code: 'STREAM_CLOSED' })
+    return
+  }
+
+  state.debug('id=%d recv=%d', this.id, frame.data.length)
+  state.window.recv.update(-frame.data.length)
+
+  this.push(frame.data)
+}
+
+Stream.prototype._handleRST = function _handleRST (frame) {
+  if (frame.code !== 'CANCEL') {
+    this.emit('error', new Error('Got RST: ' + frame.code))
+  }
+  this.abort()
+}
+
+Stream.prototype._handleWindowUpdate = function _handleWindowUpdate (frame) {
+  var state = this._spdyState
+
+  state.window.send.update(frame.delta)
+}
+
+Stream.prototype._onWindowOverflow = function _onWindowOverflow () {
+  var state = this._spdyState
+
+  state.debug('id=%d window overflow', this.id)
+  state.framer.rstFrame({ id: this.id, code: 'FLOW_CONTROL_ERROR' })
+
+  this.aborted = true
+  this.emit('error', new Error('HTTP2 window overflow'))
+}
+
+Stream.prototype._handlePriority = function _handlePriority (frame) {
+  var state = this._spdyState
+
+  state.priority.remove()
+  state.priority = null
+  this._initPriority(frame.priority)
+
+  // Mostly for testing purposes
+  this.emit('priority', frame.priority)
+}
+
+Stream.prototype._handleHeaders = function _handleHeaders (frame) {
+  var state = this._spdyState
+
+  if (!state.readable || this._readableState.ended) {
+    state.framer.rstFrame({ id: this.id, code: 'STREAM_CLOSED' })
+    return
+  }
+
+  if (state.needResponse) {
+    return this._handleResponse(frame)
+  }
+
+  this.emit('headers', frame.headers)
+}
+
+Stream.prototype._handleResponse = function _handleResponse (frame) {
+  var state = this._spdyState
+
+  if (frame.headers[':status'] === undefined) {
+    state.framer.rstFrame({ id: this.id, code: 'PROTOCOL_ERROR' })
+    return
+  }
+
+  state.needResponse = false
+  this.emit('response', frame.headers[':status'] | 0, frame.headers)
+}
+
+Stream.prototype._onFinish = function _onFinish () {
+  var state = this._spdyState
+
+  // Send the request if it wasn't sent
+  if (!state.sent) {
+    // NOTE: will send HEADERS with FIN flag
+    this.send()
+  } else {
+    // Just an `.end()` without any writes will trigger immediate `finish` event
+    // without any calls to `_write()`.
+    if (state.corked !== 0) {
+      var self = this
+      state.corkQueue.push(function () {
+        self._onFinish()
+      })
+      return
+    }
+
+    state.framer.dataFrame({
+      id: this.id,
+      priority: state.priority.getPriority(),
+      fin: true,
+      data: Buffer.alloc(0)
+    })
+  }
+
+  this._maybeClose()
+}
+
+Stream.prototype._onEnd = function _onEnd () {
+  this._maybeClose()
+}
+
+Stream.prototype._checkEnded = function _checkEnded (callback) {
+  var state = this._spdyState
+
+  var ended = false
+  if (state.aborted) { ended = true }
+
+  if (!state.writable || this._writableState.finished) { ended = true }
+
+  if (!ended) {
+    return true
+  }
+
+  if (!callback) {
+    return false
+  }
+
+  var err = new Error('Ended stream can\'t send frames')
+  process.nextTick(function () {
+    callback(err)
+  })
+
+  return false
+}
+
+Stream.prototype._maybeClose = function _maybeClose () {
+  var state = this._spdyState
+
+  // .abort() emits `close`
+  if (state.aborted) {
+    return
+  }
+
+  if ((!state.readable || this._readableState.ended) &&
+      this._writableState.finished) {
+    // Clear timeout
+    state.timeout.set(0)
+
+    this.emit('close')
+  }
+}
+
+Stream.prototype._handlePushPromise = function _handlePushPromise (frame) {
+  var push = this.connection._createStream({
+    id: frame.promisedId,
+    parent: this,
+    push: true,
+    request: true,
+    method: frame.headers[':method'],
+    path: frame.headers[':path'],
+    host: frame.headers[':authority'],
+    priority: frame.priority,
+    headers: frame.headers,
+    writable: false
+  })
+
+  // GOAWAY
+  if (this.connection._isGoaway(push.id)) {
+    return
+  }
+
+  if (!this.emit('pushPromise', push)) {
+    push.abort()
+  }
+}
+
+Stream.prototype._hardCork = function _hardCork () {
+  var state = this._spdyState
+
+  this.cork()
+  state.corked++
+}
+
+Stream.prototype._hardUncork = function _hardUncork () {
+  var state = this._spdyState
+
+  this.uncork()
+  state.corked--
+  if (state.corked !== 0) {
+    return
+  }
+
+  // Invoke callbacks
+  var queue = state.corkQueue
+  state.corkQueue = []
+  for (var i = 0; i < queue.length; i++) {
+    queue[i]()
+  }
+}
+
+Stream.prototype._sendPush = function _sendPush (status, response, callback) {
+  var self = this
+  var state = this._spdyState
+
+  this._hardCork()
+  state.framer.pushFrame({
+    id: this.parent.id,
+    promisedId: this.id,
+    priority: state.priority.toJSON(),
+    path: this.path,
+    host: this.host,
+    method: this.method,
+    status: status,
+    headers: this.headers,
+    response: response
+  }, function (err) {
+    self._hardUncork()
+
+    callback(err)
+  })
+}
+
+Stream.prototype._wasSent = function _wasSent () {
+  var state = this._spdyState
+  return state.sent
+}
+
+// Public API
+
+Stream.prototype.send = function send (callback) {
+  var state = this._spdyState
+
+  if (state.sent) {
+    var err = new Error('Stream was already sent')
+    process.nextTick(function () {
+      if (callback) {
+        callback(err)
+      }
+    })
+    return
+  }
+
+  state.sent = true
+  state.timeout.reset()
+
+  // GET requests should always be auto-finished
+  if (this.method === 'GET') {
+    this._writableState.ended = true
+    this._writableState.finished = true
+  }
+
+  // TODO(indunty): ideally it should just take a stream object as an input
+  var self = this
+  this._hardCork()
+  state.framer.requestFrame({
+    id: this.id,
+    method: this.method,
+    path: this.path,
+    host: this.host,
+    priority: state.priority.toJSON(),
+    headers: this.headers,
+    fin: this._writableState.finished
+  }, function (err) {
+    self._hardUncork()
+
+    if (!callback) {
+      return
+    }
+
+    callback(err)
+  })
+}
+
+Stream.prototype.respond = function respond (status, headers, callback) {
+  var self = this
+  var state = this._spdyState
+  assert(!state.request, 'Can\'t respond on request')
+
+  state.timeout.reset()
+
+  if (!this._checkEnded(callback)) { return }
+
+  var frame = {
+    id: this.id,
+    status: status,
+    headers: headers
+  }
+  this._hardCork()
+  state.framer.responseFrame(frame, function (err) {
+    self._hardUncork()
+    if (callback) { callback(err) }
+  })
+}
+
+Stream.prototype.setWindow = function setWindow (size) {
+  var state = this._spdyState
+
+  state.timeout.reset()
+
+  if (!this._checkEnded()) {
+    return
+  }
+
+  state.debug('id=%d force window max=%d', this.id, size)
+  state.window.recv.setMax(size)
+
+  var delta = state.window.recv.getDelta()
+  if (delta === 0) { return }
+
+  state.framer.windowUpdateFrame({
+    id: this.id,
+    delta: delta
+  })
+  state.window.recv.update(delta)
+}
+
+Stream.prototype.sendHeaders = function sendHeaders (headers, callback) {
+  var self = this
+  var state = this._spdyState
+
+  state.timeout.reset()
+
+  if (!this._checkEnded(callback)) {
+    return
+  }
+
+  // Request wasn't yet send, coalesce headers
+  if (!state.sent) {
+    this.headers = Object.assign({}, this.headers)
+    Object.assign(this.headers, headers)
+    process.nextTick(function () {
+      if (callback) {
+        callback(null)
+      }
+    })
+    return
+  }
+
+  this._hardCork()
+  state.framer.headersFrame({
+    id: this.id,
+    headers: headers
+  }, function (err) {
+    self._hardUncork()
+    if (callback) { callback(err) }
+  })
+}
+
+Stream.prototype._destroy = function destroy () {
+  this.abort()
+}
+
+Stream.prototype.abort = function abort (code, callback) {
+  var state = this._spdyState
+
+  // .abort(callback)
+  if (typeof code === 'function') {
+    callback = code
+    code = null
+  }
+
+  if (this._readableState.ended && this._writableState.finished) {
+    state.debug('id=%d already closed', this.id)
+    if (callback) {
+      process.nextTick(callback)
+    }
+    return
+  }
+
+  if (state.aborted) {
+    state.debug('id=%d already aborted', this.id)
+    if (callback) { process.nextTick(callback) }
+    return
+  }
+
+  state.aborted = true
+  state.debug('id=%d abort', this.id)
+
+  this.setTimeout(0)
+
+  var abortCode = code || 'CANCEL'
+
+  state.framer.rstFrame({
+    id: this.id,
+    code: abortCode
+  })
+
+  var self = this
+  process.nextTick(function () {
+    if (callback) {
+      callback(null)
+    }
+    self.emit('close', new Error('Aborted, code: ' + abortCode))
+  })
+}
+
+Stream.prototype.setPriority = function setPriority (info) {
+  var state = this._spdyState
+
+  state.timeout.reset()
+
+  if (!this._checkEnded()) {
+    return
+  }
+
+  state.debug('id=%d priority change', this.id, info)
+
+  var frame = { id: this.id, priority: info }
+
+  // Change priority on this side
+  this._handlePriority(frame)
+
+  // And on the other too
+  state.framer.priorityFrame(frame)
+}
+
+Stream.prototype.pushPromise = function pushPromise (uri, callback) {
+  if (!this._checkEnded(callback)) {
+    return
+  }
+
+  var self = this
+  this._hardCork()
+  var push = this.connection.pushPromise(this, uri, function (err) {
+    self._hardUncork()
+    if (!err) {
+      push._hardUncork()
+    }
+
+    if (callback) {
+      return callback(err, push)
+    }
+
+    if (err) { push.emit('error', err) }
+  })
+  push._hardCork()
+
+  return push
+}
+
+Stream.prototype.setMaxChunk = function setMaxChunk (size) {
+  var state = this._spdyState
+  state.maxChunk = size
+}
+
+Stream.prototype.setTimeout = function setTimeout (delay, callback) {
+  var state = this._spdyState
+
+  state.timeout.set(delay, callback)
+}
Index: frontend/node_modules/spdy-transport/lib/spdy-transport/utils.js
===================================================================
--- frontend/node_modules/spdy-transport/lib/spdy-transport/utils.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/spdy-transport/lib/spdy-transport/utils.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,196 @@
+'use strict'
+
+var util = require('util')
+var isNode = require('detect-node')
+
+// Node.js 0.8, 0.10 and 0.12 support
+Object.assign = (process.versions.modules >= 46 || !isNode)
+  ? Object.assign // eslint-disable-next-line
+  : util._extend
+
+function QueueItem () {
+  this.prev = null
+  this.next = null
+}
+exports.QueueItem = QueueItem
+
+function Queue () {
+  QueueItem.call(this)
+
+  this.prev = this
+  this.next = this
+}
+util.inherits(Queue, QueueItem)
+exports.Queue = Queue
+
+Queue.prototype.insertTail = function insertTail (item) {
+  item.prev = this.prev
+  item.next = this
+  item.prev.next = item
+  item.next.prev = item
+}
+
+Queue.prototype.remove = function remove (item) {
+  var next = item.next
+  var prev = item.prev
+
+  item.next = item
+  item.prev = item
+  next.prev = prev
+  prev.next = next
+}
+
+Queue.prototype.head = function head () {
+  return this.next
+}
+
+Queue.prototype.tail = function tail () {
+  return this.prev
+}
+
+Queue.prototype.isEmpty = function isEmpty () {
+  return this.next === this
+}
+
+Queue.prototype.isRoot = function isRoot (item) {
+  return this === item
+}
+
+function LockStream (stream) {
+  this.locked = false
+  this.queue = []
+  this.stream = stream
+}
+exports.LockStream = LockStream
+
+LockStream.prototype.write = function write (chunks, callback) {
+  var self = this
+
+  // Do not let it interleave
+  if (this.locked) {
+    this.queue.push(function () {
+      return self.write(chunks, callback)
+    })
+    return
+  }
+
+  this.locked = true
+
+  function done (err, chunks) {
+    self.stream.removeListener('error', done)
+
+    self.locked = false
+    if (self.queue.length > 0) { self.queue.shift()() }
+    callback(err, chunks)
+  }
+
+  this.stream.on('error', done)
+
+  // Accumulate all output data
+  var output = []
+  function onData (chunk) {
+    output.push(chunk)
+  }
+  this.stream.on('data', onData)
+
+  function next (err) {
+    self.stream.removeListener('data', onData)
+    if (err) {
+      return done(err)
+    }
+
+    done(null, output)
+  }
+
+  for (var i = 0; i < chunks.length - 1; i++) { this.stream.write(chunks[i]) }
+
+  if (chunks.length > 0) {
+    this.stream.write(chunks[i], next)
+  } else { process.nextTick(next) }
+
+  if (this.stream.execute) {
+    this.stream.execute(function (err) {
+      if (err) { return done(err) }
+    })
+  }
+}
+
+// Just finds the place in array to insert
+function binaryLookup (list, item, compare) {
+  var start = 0
+  var end = list.length
+
+  while (start < end) {
+    var pos = (start + end) >> 1
+    var cmp = compare(item, list[pos])
+
+    if (cmp === 0) {
+      start = pos
+      end = pos
+      break
+    } else if (cmp < 0) {
+      end = pos
+    } else {
+      start = pos + 1
+    }
+  }
+
+  return start
+}
+exports.binaryLookup = binaryLookup
+
+function binaryInsert (list, item, compare) {
+  var index = binaryLookup(list, item, compare)
+
+  list.splice(index, 0, item)
+}
+exports.binaryInsert = binaryInsert
+
+function binarySearch (list, item, compare) {
+  var index = binaryLookup(list, item, compare)
+
+  if (index >= list.length) {
+    return -1
+  }
+
+  if (compare(item, list[index]) === 0) {
+    return index
+  }
+
+  return -1
+}
+exports.binarySearch = binarySearch
+
+function Timeout (object) {
+  this.delay = 0
+  this.timer = null
+  this.object = object
+}
+exports.Timeout = Timeout
+
+Timeout.prototype.set = function set (delay, callback) {
+  this.delay = delay
+  this.reset()
+  if (!callback) { return }
+
+  if (this.delay === 0) {
+    this.object.removeListener('timeout', callback)
+  } else {
+    this.object.once('timeout', callback)
+  }
+}
+
+Timeout.prototype.reset = function reset () {
+  if (this.timer !== null) {
+    clearTimeout(this.timer)
+    this.timer = null
+  }
+
+  if (this.delay === 0) { return }
+
+  var self = this
+  this.timer = setTimeout(function () {
+    self.timer = null
+    self.object.emit('timeout')
+  }, this.delay)
+}
Index: frontend/node_modules/spdy-transport/lib/spdy-transport/window.js
===================================================================
--- frontend/node_modules/spdy-transport/lib/spdy-transport/window.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/spdy-transport/lib/spdy-transport/window.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,174 @@
+'use strict'
+
+var util = require('util')
+var EventEmitter = require('events').EventEmitter
+var debug = {
+  server: require('debug')('spdy:window:server'),
+  client: require('debug')('spdy:window:client')
+}
+
+function Side (window, name, options) {
+  EventEmitter.call(this)
+
+  this.name = name
+  this.window = window
+  this.current = options.size
+  this.max = options.size
+  this.limit = options.max
+  this.lowWaterMark = options.lowWaterMark === undefined
+    ? this.max / 2
+    : options.lowWaterMark
+
+  this._refilling = false
+  this._refillQueue = []
+}
+util.inherits(Side, EventEmitter)
+
+Side.prototype.setMax = function setMax (max) {
+  this.window.debug('id=%d side=%s setMax=%d',
+    this.window.id,
+    this.name,
+    max)
+  this.max = max
+  this.lowWaterMark = this.max / 2
+}
+
+Side.prototype.updateMax = function updateMax (max) {
+  var delta = max - this.max
+  this.window.debug('id=%d side=%s updateMax=%d delta=%d',
+    this.window.id,
+    this.name,
+    max,
+    delta)
+
+  this.max = max
+  this.lowWaterMark = max / 2
+
+  this.update(delta)
+}
+
+Side.prototype.setLowWaterMark = function setLowWaterMark (lwm) {
+  this.lowWaterMark = lwm
+}
+
+Side.prototype.update = function update (size, callback) {
+  // Not enough space for the update, wait for refill
+  if (size <= 0 && callback && this.isEmpty()) {
+    this.window.debug('id=%d side=%s wait for refill=%d [%d/%d]',
+      this.window.id,
+      this.name,
+      -size,
+      this.current,
+      this.max)
+    this._refillQueue.push({
+      size: size,
+      callback: callback
+    })
+    return
+  }
+
+  this.current += size
+
+  if (this.current > this.limit) {
+    this.emit('overflow')
+    return
+  }
+
+  this.window.debug('id=%d side=%s update by=%d [%d/%d]',
+    this.window.id,
+    this.name,
+    size,
+    this.current,
+    this.max)
+
+  // Time to send WINDOW_UPDATE
+  if (size < 0 && this.isDraining()) {
+    this.window.debug('id=%d side=%s drained', this.window.id, this.name)
+    this.emit('drain')
+  }
+
+  // Time to write
+  if (size > 0 && this.current > 0 && this.current <= size) {
+    this.window.debug('id=%d side=%s full', this.window.id, this.name)
+    this.emit('full')
+  }
+
+  this._processRefillQueue()
+
+  if (callback) { process.nextTick(callback) }
+}
+
+Side.prototype.getCurrent = function getCurrent () {
+  return this.current
+}
+
+Side.prototype.getMax = function getMax () {
+  return this.max
+}
+
+Side.prototype.getDelta = function getDelta () {
+  return this.max - this.current
+}
+
+Side.prototype.isDraining = function isDraining () {
+  return this.current <= this.lowWaterMark
+}
+
+Side.prototype.isEmpty = function isEmpty () {
+  return this.current <= 0
+}
+
+// Private
+
+Side.prototype._processRefillQueue = function _processRefillQueue () {
+  // Prevent recursion
+  if (this._refilling) {
+    return
+  }
+  this._refilling = true
+
+  while (this._refillQueue.length > 0) {
+    var item = this._refillQueue[0]
+
+    if (this.isEmpty()) {
+      break
+    }
+
+    this.window.debug('id=%d side=%s refilled for size=%d',
+      this.window.id,
+      this.name,
+      -item.size)
+
+    this._refillQueue.shift()
+    this.update(item.size, item.callback)
+  }
+
+  this._refilling = false
+}
+
+function Window (options) {
+  this.id = options.id
+  this.isServer = options.isServer
+  this.debug = this.isServer ? debug.server : debug.client
+
+  this.recv = new Side(this, 'recv', options.recv)
+  this.send = new Side(this, 'send', options.send)
+}
+module.exports = Window
+
+Window.prototype.clone = function clone (id) {
+  return new Window({
+    id: id,
+    isServer: this.isServer,
+    recv: {
+      size: this.recv.max,
+      max: this.recv.limit,
+      lowWaterMark: this.recv.lowWaterMark
+    },
+    send: {
+      size: this.send.max,
+      max: this.send.limit,
+      lowWaterMark: this.send.lowWaterMark
+    }
+  })
+}
Index: frontend/node_modules/spdy-transport/package.json
===================================================================
--- frontend/node_modules/spdy-transport/package.json	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/spdy-transport/package.json	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,43 @@
+{
+  "name": "spdy-transport",
+  "version": "3.0.0",
+  "main": "lib/spdy-transport",
+  "description": "SPDY v2, v3, v3.1 and HTTP2 transport",
+  "license": "MIT",
+  "keywords": [
+    "spdy",
+    "http2",
+    "transport"
+  ],
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/spdy-http2/spdy-transport.git"
+  },
+  "homepage": "https://github.com/spdy-http2/spdy-transport",
+  "author": "Fedor Indutny <fedor@indutny.com>",
+  "dependencies": {
+    "debug": "^4.1.0",
+    "detect-node": "^2.0.4",
+    "hpack.js": "^2.1.6",
+    "obuf": "^1.1.2",
+    "readable-stream": "^3.0.6",
+    "wbuf": "^1.7.3"
+  },
+  "devDependencies": {
+    "async": "^2.6.1",
+    "istanbul": "^0.4.5",
+    "mocha": "^5.2.0",
+    "pre-commit": "^1.2.2",
+    "standard": "^12.0.1",
+    "stream-pair": "^1.0.3"
+  },
+  "scripts": {
+    "lint": "standard",
+    "test": "mocha --reporter=spec test/**/*-test.js test/**/**/*-test.js",
+    "coverage": "istanbul cover node_modules/.bin/_mocha -- --reporter=spec test/**/*-test.js test/**/**/*-test.js"
+  },
+  "pre-commit": [
+    "lint",
+    "test"
+  ]
+}
