3 var childProcess = require("child_process");
4 var phantomjs = require("phantomjs-prebuilt");
5 var config = require("./config.js");
6 var fs = require("fs");
7 var temp = require("temp");
8 var path = require("path");
9 var Phantom = require("./Phantom.js");
10 var forkStdout = require("./forkStdout.js");
11 var lift = require("./lift.js");
13 var startScript = path.resolve(__dirname, "./phantom/start.js");
14 var writeFile = lift(fs.writeFile);
15 var close = lift(fs.close);
16 var open = lift(temp.open);
17 var initialMessage = "message to node: hi";
20 * Spawns a new PhantomJS process with the given phantom config. Returns a Promises/A+ compliant promise
21 * which resolves when the process is ready to execute commands.
23 * @see http://phantomjs.org/api/command-line.html
24 * @param {Object} phantomJsConfig
27 function spawn(phantomJsConfig) {
34 phantomJsConfig = phantomJsConfig || {};
36 // Saving a reference of the current stdout and stderr because this is (probably) the expected behaviour.
37 // If we wouldn't save a reference, the config of a later state would be applied because we have to
38 // do asynchronous tasks before piping the streams.
39 stdout = config.stdout;
40 stderr = config.stderr;
43 * Step 1: Write the config
46 .then(function writeConfig(info) {
47 configPath = info.path;
49 // Pass config items in CLI style (--some-config) separately to avoid Phantom's JSON config bugs
50 // @see https://github.com/peerigon/phridge/issues/31
51 args = Object.keys(phantomJsConfig)
52 .filter(function filterCliStyle(configKey) {
53 return configKey.charAt(0) === "-";
55 .map(function returnConfigValue(configKey) {
56 var configValue = phantomJsConfig[configKey];
58 delete phantomJsConfig[configKey];
60 return configKey + "=" + configValue;
63 return writeFile(info.path, JSON.stringify(phantomJsConfig))
65 return close(info.fd);
69 * Step 2: Start PhantomJS with the config path and pipe stderr and stdout.
71 .then(function startPhantom() {
72 return new Promise(function (resolve, reject) {
73 function onStdout(chunk) {
74 var message = chunk.toString("utf8");
76 child.stdout.removeListener("data", onStdout);
77 child.stderr.removeListener("data", onStderr);
79 if (message.slice(0, initialMessage.length) === initialMessage) {
82 reject(new Error(message));
86 // istanbul ignore next because there is no way to trigger stderr artificially in a test
87 function onStderr(chunk) {
88 var message = chunk.toString("utf8");
90 child.stdout.removeListener("data", onStdout);
91 child.stderr.removeListener("data", onStderr);
93 reject(new Error(message));
97 "--config=" + configPath,
102 child = childProcess.spawn(phantomjs.path, args);
104 prepareChildProcess(child);
106 child.stdout.on("data", onStdout);
107 child.stderr.on("data", onStderr);
109 // Our destination streams should not be ended if the childProcesses exists
110 // thus { end: false }
111 // @see https://github.com/peerigon/phridge/issues/27
113 child.cleanStdout.pipe(stdout, { end: false });
116 child.stderr.pipe(stderr, { end: false });
121 * Step 3: Create the actual Phantom-instance and return it.
124 return new Phantom(child);
129 * Prepares childProcess' stdout for communication with phridge. The childProcess gets two new properties:
130 * - phridge: A stream which provides all messages to the phridge module
131 * - cleanStdout: A stream which provides all the other data piped to stdout.
133 * @param {child_process.ChildProcess} childProcess
136 function prepareChildProcess(childProcess) {
137 var stdoutFork = forkStdout(childProcess.stdout);
139 childProcess.cleanStdout = stdoutFork.cleanStdout;
140 childProcess.phridge = stdoutFork.phridge;
141 childProcess.on("exit", disposeChildProcess);
145 * Clean up our childProcess extensions
150 function disposeChildProcess() {
151 var childProcess = this;
153 childProcess.phridge.removeAllListeners();
154 childProcess.phridge = null;
155 childProcess.cleanStdout.removeAllListeners();
156 childProcess.cleanStdout = null;
159 module.exports = spawn;