| '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, |
| 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); |
| }); |
| |
| }); |
| } |
| |
| // 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({ |
| 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); |
| }); |
| |