docs: function InterchangeConnection(connectionManager, interchangeUrl) {
    var self = {};
  
    var socket;
  
    self.interchangeUrl = interchangeUrl;
    self.connected = false;
    self.sessionId = undefined;
    self.serverVersion = 'unknown';
    self.serverAgent = 'unknown';
  
    var pendingCommands = [];
  
    /**
     * Opens a socket connection to the interchange server.
     */
    self._open = function() {
      debug('Connecting to %s', interchangeUrl);
  
      socket = io.connect(interchangeUrl, {
          forceNew: true,
          transports: ['websocket']
      });
  
      ///////////////////
      // Socket Events //
      ///////////////////
  
      socket.on('connect', function() {
        debug('Connected to %s', interchangeUrl);
        self.connected = true;
        connectionManager.emit('connect', {
            interchangeUrl: interchangeUrl
        });
        _nextCommand();
      });
  
      socket.on('error', function(err) {
        console.error('Connection error %s', interchangeUrl);
        connectionManager.emit('error', {
            interchangeUrl: interchangeUrl,
            error: err
        });
      });
  
      socket.on('reconnect', function() {
        debug('Reconnecting %s', interchangeUrl);
        // If our client has previously set the session, then reissue it on
        // reconnection.
        if (self.sessionId) {
          self.SESSION(self.sessionId);
        }
        connectionManager.emit('reconnect', {
            interchangeUrl: interchangeUrl
        });
      });
  
      socket.on('disconnect', function() {
        debug('Disconnected %s', interchangeUrl);
        self.connected = false;
        connectionManager.emit('disconnect', {
            interchangeUrl: interchangeUrl
        });
      });
  
      socket.on('reconnect_failed', function() {
        debug('Reconnection failed %s', interchangeUrl);
        connectionManager.emit('reconnect_failed', {
            interchangeUrl: interchangeUrl
        });
      });
  
      ///////////////////////////////
      // Interchange Push Messages //
      ///////////////////////////////
  
      socket.on('STANZA', function(stanza) {
        debug('STANZA', stanza);
        connectionManager.emit('STANZA', stanza);
      });
  
      return self;
    }
  
    //////////////////////////
    // Interchange Commands //
    //////////////////////////
  
    self.SESSION = function(sessionId) {
      self.sessionId = sessionId;
      var url = 'vox:__session__' + (sessionId ? ('/session/' + sessionId) : '/session');
      return sendCommand('POST', url, {
            version: connectionManager.version,
            agent: connectionManager.agent,
         }, true)
        .then(function(reply) {
          var newSessionId = reply ? reply.newSessionId : 0;
          // If the server replies with a new session ID, then it has created a
          // new session for us.  Update our internal record.
          if (newSessionId) {
            self.sessionId = newSessionId;
          }
          reply = reply ? reply : {};
          reply.version = (reply.version ? reply.version : 'unknown').substr(0, 64);
          reply.agent = (reply.agent ? reply.agent : 'unknown').substr(0, 64);
          self.serverVersion = reply.version;
          self.serverAgent = reply.agent;
          reply.interchangeUrl = interchangeUrl;
          connectionManager.emit('SESSION', reply);
          _nextCommand();
          return reply;
        });
    }
  
    self.GET = function(url, payload) {
      return sendCommand('GET', url, payload);
    }
  
    self.POST = function(url, payload) {
      return sendCommand('POST', url, payload);
    }
  
    self.SUBSCRIBE = function(url, payload) {
      return sendCommand('SUBSCRIBE', url, payload);
    }
  
    self.UNSUBSCRIBE = function(url, payload) {
      return sendCommand('UNSUBSCRIBE', url, payload);
    }
  
    self.close = function() {
      socket.close();
      connectionManager._removeConnection(self);
    }
  
    /**
     * Helper function to send a command to the server.
     *
     * Returns a Promise for the server's response.  Rejects the Promise if the
     * reply's status is not 200.
     *
     * If this connection is not ready to receive commands, the command is queued
     * for later execution.
     */
    function sendCommand(method, url, payload, skipQueue) {
      if (!self.connected) {
        debug('Queueing %s %s', method, url);
        return new P(function(resolve, reject) {
          var p = P.method(function() {
            debug('Dequeueing %s %s', method, url);
            return _sendCommand(method, url, payload)
              .then(resolve)
              .catch(reject);
          });
          if (skipQueue) {
            pendingCommands.splice(0, 0, p);
          } else {
            pendingCommands.push(p);
          }
        });
      }
      return _sendCommand(method, url, payload);
    }
  
    function _sendCommand(method, url, payload) {
      return new P(function(resolve, reject) {
        var data = {
            url: url,
            payload: payload
        };
        debug('REQUEST %s %s %s\n', interchangeUrl, method, url, data);
        socket.emit(method, data, function(reply) {
          debug('REPLY %s %s %s\n', interchangeUrl, method, url, reply);
          if (reply && reply.status && reply.status != 200) {
            reject(new errors.HttpError(reply.status, reply.message));
          } else {
            resolve(reply);
          }
        });
      });
    }
  
    /**
     * Executes the next command on the queue.
     */
    function _nextCommand() {
      var fn = pendingCommands.shift();
      if (!fn) {
        return;
      }
      fn().then(_nextCommand);
    }
  
    return self;
  }