docs: function Sender(senderID, serverKey, type) {
      if (util.isNullOrUndefined(senderID || util.isNullOrUndefined(serverKey))) {
          throw new IllegalArgumentException();
      }
      this.senderId = senderID;
      this.serverKey = serverKey;
      this.connectionType = Constants.FCM_PRODUCTION_IDX;  // Default to Production
  
      this.events = new Events();
      this.draining = true;
      this.queued = [];
      this.acks = [];
  
  	// Set default port to Production
  	var fcmPort = Constants.FCM_SEND_PRODUCTION_PORT;
  
      if (type && type === Constants.FCM_DEVELOPMENT_IDX ) {
  		fcmPort = Constants.FCM_SEND_DEVELOPMENT_PORT;
          this.connectionType = Constants.FCM_DEVELOPMENT_IDX;
  	}
  
      var self = this;
  
      this.client = new xmpp.Client({
          jid: this.senderId + '@gcm.googleapis.com',
          password: this.serverKey,
          port: fcmPort,
          host: Constants.FCM_SEND_ENDPOINT,
          reconnect: true,
          legacySSL: true,
          preferredSaslMechanism: Constants.FCM_PREFERRED_SASL
      });
  
      this.client.connection.socket.setTimeout(0);
      this.client.connection.socket.setKeepAlive(true, 10000);
  
      this.client.on('online', function () {
          self.events.emit('connected', self.connectionType);
  
          if (self.draining) {
              self.draining = false;
              var i = self.queued.length;
              while (i--) {
                  self._send(self.queued[i]);
              }
              self.queued = [];
          }
      });
  
      this.client.on('close', function () {
          if (self.draining) {
              self.client.connect();
          } else {
              self.events.emit('disconnected', self.connectionType);
          }
      });
  
      this.client.on('error', function (e) {
          self.events.emit('error', e);
      });
  
      this.client.on('stanza', function (stanza) {
          if (stanza.is('message') && stanza.attrs.type !== 'error') {
              var data = JSON.parse(stanza.getChildText('gcm'));
  
              if (!data || !data.message_id) {
                  return;
              }
  
              switch (data.message_type) {
                  case 'control':
                      if (data.control_type === 'CONNECTION_DRAINING') {
                          self.draining = true;
                      }
                      break;
  
                  case 'nack':
                  case 'ack':
                      if (data.message_id in self.acks) {
                          var result = new Result().from(data.from).messageId(data.message_id)
                              .messageType(data.message_type).registrationId(data.registration_id).error(data.error)
                              .errorDescription(data.error_description).build();
                          self.acks[data.message_id](result);
                          delete self.acks[data.message_id];
                      }
                      break;
  
                  case 'receipt':
                      if (data.from) {
                          self._send({
                              to: data.from,
                              message_id: data.message_id,
                              message_type: 'ack'
                          });
                      }
                      self.events.emit('receipt', data.message_id, data.from, data.data, data.category);
                      break;
  
                  default:
                      // Send ack, as per spec
                      if (data.from) {
                          self._send({
                              to: data.from,
                              message_id: data.message_id,
                              message_type: 'ack'
                          });
  
                          if (data.data) {
                              self.events.emit('message', data.message_id, data.from, data.data, data.category);
                          }
                      }
  
                      break;
              }
          } else {
              var message = stanza.getChildText('error').getChildText('text');
              self.events.emit('message-error', message);
          }
      });
  }