UNPKG

node-firefox-start-simulator

Version:
300 lines (227 loc) 7.56 kB
'use strict'; // See https://github.com/jshint/jshint/issues/1747 for context /* global -Promise */ var Promise = require('es6-promise').Promise; var net = require('net'); var spawn = require('child_process').spawn; var fs = require('fs'); var portFinder = require('portfinder'); var findSimulators = require('node-firefox-find-simulators'); module.exports = startSimulator; function startSimulator(options) { options = options || {}; var detached = options.detached ? true : false; var verbose = options.verbose ? true : false; var port = options.port; var timeout = options.timeout || 25000; var simulatorOptions = {}; if (options.version) { simulatorOptions.version = options.version; } return new Promise(function(resolve, reject) { Promise.all([findSimulator(simulatorOptions), findAvailablePort(port)]).then(function(results) { var simulator = results[0]; port = results[1]; launchSimulatorAndWaitUntilReady({ binary: simulator.bin || simulator.binary, profile: simulator.profile, port: port, detached: detached, verbose: verbose, timeout: timeout, version: simulator.version }).then(function(simulatorDetails) { resolve(simulatorDetails); }, function(simulatorLaunchError) { reject(simulatorLaunchError); }); }, function(error) { reject(error); }); }); } // Helper function to start multiple simulators with common default options startSimulator.all = function(commonOptions) { return function(simulators) { // findSimulators() output can include multiple emulators for the same // version. Filter that down to unique versions. var seenVersions = {}; var uniqueVersionSimulators = simulators.filter(function(simulator) { var key = simulator.version; if (key in seenVersions) { return false; } seenVersions[key] = true; return true; }); // Start all the simulators, using the common options for each. return Promise.all(uniqueVersionSimulators.map(function(simulator) { var options = {}; var key; for (key in commonOptions) { options[key] = commonOptions[key]; } for (key in simulator) { options[key] = simulator[key]; } return startSimulator(options); })); }; }; // Find a simulator that matches the options function findSimulator(options) { return new Promise(function(resolve, reject) { findSimulators(options).then(function(results) { if (!results || results.length === 0) { reject(new Error('No simulators installed, or cannot find them')); } // just returning the first result for now resolve(results[0]); }, function(error) { reject(error); }); }); } function findAvailablePort(preferredPort) { return new Promise(function(resolve, reject) { // Start searching with the preferred port, if specified if (preferredPort !== undefined) { portFinder.basePort = preferredPort; } portFinder.getPort({ // Ensure we're looking at localhost, rather than 0.0.0.0 // see: https://github.com/indexzero/node-portfinder/issues/13 host: '127.0.0.1' }, function(err, port) { if (err) { reject(err); } else { resolve(port); } }); }); } // Launches the simulator and wait until it's ready to be used function launchSimulatorAndWaitUntilReady(options) { var port = options.port; var binary = options.binary; var profile = options.profile; var timeout = options.timeout; return new Promise(function(resolve, reject) { launchSimulator(options).then(function(simulatorProcess) { waitUntilSimulatorIsReady({ port: port, timeout: timeout }).then(function() { resolve({ process: simulatorProcess, pid: simulatorProcess.pid, port: port, binary: binary, profile: profile }); }, function(timedOutError) { reject(timedOutError); }); }, function(simulatorLaunchError) { reject(simulatorLaunchError); }); }); } // Launches the simulator in the specified port function launchSimulator(options) { var detached = options.detached; return new Promise(function(resolve, reject) { startSimulatorProcess(options).then(function(simulatorProcess) { // If the simulator is not detached, we need to kill its process // once our own process exits if (!detached) { process.once('exit', function() { simulatorProcess.kill('SIGTERM'); }); process.once('uncaughtException', function(error) { if (process.listeners('uncaughtException').length === 0) { simulatorProcess.kill('SIGTERM'); throw error; } }); } else { // Totally make sure we don't keep references to this new child-- // this removes the child from the parent's event loop // See http://nodejs.org/api/child_process.html#child_process_options_detached simulatorProcess.unref(); } resolve(simulatorProcess); }, function(error) { reject(error); }); }); } function startSimulatorProcess(options) { return new Promise(function(resolve, reject) { var simulatorBinary = options.binary; var childOptions = { stdio: ['ignore', 'ignore', 'ignore'] }; var startDebuggerServer = '-start-debugger-server'; // Simple sanity check: make sure the simulator binary exists if (!fs.existsSync(simulatorBinary)) { return reject(new Error(simulatorBinary + ' does not exist')); } if (options.detached) { childOptions.detached = true; } if (options.verbose) { childOptions.stdio = [ process.stdin, process.stdout, process.stderr ]; } if (options.version === '1.3' || options.version === '1.2') { startDebuggerServer = '-dbgport'; } // TODO do we want to pipe stdin/stdout/stderr as in commandB2G? // https://github.com/nicola/fxos-start/blob/6b4794814e3a5c97d60abf2ab8619c635d6c3c94/index.js#L55-L57 var simulatorProcess = spawn( simulatorBinary, [ '-profile', options.profile, startDebuggerServer, options.port, '-no-remote', '-foreground' ], childOptions ); resolve(simulatorProcess); }); } function waitUntilSimulatorIsReady(options) { var attemptInterval = 1000; var elapsedTime = 0; var port = options.port; var timeout = options.timeout; return new Promise(function(resolve, reject) { function ping() { var socket = new net.Socket(); socket.on('connect', function() { resolve(); socket.destroy(); }).on('error', function(error) { if (error && error.code !== 'ECONNREFUSED') { throw error; } socket.destroy(); maybeTryAgain(); }).connect(port, 'localhost'); } function maybeTryAgain() { elapsedTime += attemptInterval; if (elapsedTime < timeout) { setTimeout(ping, attemptInterval); } else { reject(new Error('Timed out trying to connect to the simulator in ' + port)); } } ping(); }); } // These actually make it so that child process get killed when this process // gets killed (except when the child process is detached, obviously) process.once('SIGTERM', function() { process.exit(0); }); process.once('SIGINT', function() { process.exit(0); });