Marionette JS Client

API Docs for: 1.7.1
Show:

File: lib/marionette/drivers/tcp-sync.js

var wire = require('json-wire-protocol');
var debug = require('debug')('marionette:tcp-sync');
var sockittome = require('sockit-to-me');
var DEFAULT_HOST = 'localhost';
var DEFAULT_PORT = 2828;
var SOCKET_TIMEOUT_EXTRA = 500;

function TcpSync(options) {
  if (!options) {
    options = {};
  }

  if ('port' in options) {
    this.port = options.port;
  }
  if ('host' in options) {
    this.host = options.host;
  }
  if ('connectionTimeout' in options) {
    this.connectionTimeout = options.connectionTimeout;
  }

  this.sockit = new sockittome.Sockit();
  this.sockit.setPollTimeout(this.connectionTimeout + SOCKET_TIMEOUT_EXTRA);
};

TcpSync.prototype.isSync = true;
TcpSync.prototype.host = DEFAULT_HOST;
TcpSync.prototype.port = DEFAULT_PORT;
TcpSync.prototype.connectionTimeout = 2000;
TcpSync.prototype.retryInterval = 300;

TcpSync.prototype.setScriptTimeout = function(timeout) {
  this.sockit.setPollTimeout(timeout + SOCKET_TIMEOUT_EXTRA);
};

/**
 * Utility to wait for the marionette socket to be ready.
 *
 * @method waitForSocket
 * @param {Object} [options] for timeout.
 * @param {Number} [options.interval] time between running test.
 * @param {Number} [options.timeout] maximum wallclock time before
 *   failing test.
 * @param {Function} [callback] callback.
 */
TcpSync.prototype.waitForSocket = function(options, callback) {
  // the logic is lifted from http://dxr.mozilla.org/mozilla-central/source/testing/marionette/client/marionette/marionette.py#562

  if (typeof(options) === 'function') {
    callback = options;
    options = null;
  }

  options = options || {};
  var interval = options.interval || 0;
  var timeout = options.timeout || 30000;
  var socketTimeout = 1000;

  var sockit = new sockittome.Sockit();
  var start = Date.now();
  var self = this;

  // Use sockittome's built in polling timeout during calls to connect
  // to avoid socket misuse.
  sockit.setPollTimeout(socketTimeout);
  var socketConfig = { host: this.host, port: this.port };

  function probeSocket() {
    try {
      debug('probing socket');
      sockit.connect(socketConfig);

      var s = sockit.read(16).toString();
      sockit.close();
      if (s.indexOf(":") != -1) {
        callback();
        return;
      }
    }
    catch(e) {
      debug('exception when probing socket', e.message);
      // Above read _may_ fail so it is important to close the socket...
      sockit.close();
    }
    // timeout. Abort.
    if ((Date.now() - start) > timeout) {
      console.error('timeout connecting to b2g.');
      return;
    }
    // interval delay for the next iteration.
    setTimeout(probeSocket.bind(self), interval);
  };

  probeSocket();
}

TcpSync.prototype.connect = function(callback) {

  this.waitForSocket(function _connect() {
    try {
      this.sockit.connect({ host: this.host, port: this.port });
    } catch(err) {
      if (Date.now() - this._beginConnect >= this.connectionTimeout) {
        callback(err);
      }
      setTimeout(_connect.bind(this, callback), this.retryInterval);
      return;
    }

    if (!this._beginConnect) {
      this._beginConnect = Date.now();
    }

    // Ensure this method's resolution is asynchronous in all cases
    process.nextTick(function() {
      debug('socket connected');
      delete this._beginConnect;

      this._readResponse();

      callback();
    }.bind(this));
  }.bind(this));
};

TcpSync.prototype.defaultCallback = function(err, result) {
  if (err) {
    throw err;
  }
  return result;
};

// Following the json-wire-protocol implementation, read one byte at a time
// until the 'separator' character is received. The data preceding the
// delimiter describes the length of the JSON string.
TcpSync.prototype._readResponse = function() {
  var stream = new wire.Stream();
  var char, data, error;

  stream.on('data', function(parsed) {
    debug('read', parsed);
    data = parsed;
  });
  stream.on('error', function(err) {
    error = err;
  });

  while (!data && !error) {
    stream.write(this.sockit.read(1));
  }

  if (error) {
    throw error;
  }

  return data;
};

TcpSync.prototype.send = function(command, callback) {
  debug('write', command);
  this.sockit.write(wire.stringify(command));
  return callback(this._readResponse());
};

TcpSync.prototype.close = function() {
  this.sockit.close();
};

module.exports = TcpSync;