Version 1
[yaffs-website] / node_modules / phridge / lib / spawn.js
diff --git a/node_modules/phridge/lib/spawn.js b/node_modules/phridge/lib/spawn.js
new file mode 100644 (file)
index 0000000..5b3af24
--- /dev/null
@@ -0,0 +1,159 @@
+"use strict";
+
+var childProcess = require("child_process");
+var phantomjs = require("phantomjs-prebuilt");
+var config = require("./config.js");
+var fs = require("fs");
+var temp = require("temp");
+var path = require("path");
+var Phantom = require("./Phantom.js");
+var forkStdout = require("./forkStdout.js");
+var lift = require("./lift.js");
+
+var startScript = path.resolve(__dirname, "./phantom/start.js");
+var writeFile = lift(fs.writeFile);
+var close = lift(fs.close);
+var open = lift(temp.open);
+var initialMessage = "message to node: hi";
+
+/**
+ * Spawns a new PhantomJS process with the given phantom config. Returns a Promises/A+ compliant promise
+ * which resolves when the process is ready to execute commands.
+ *
+ * @see http://phantomjs.org/api/command-line.html
+ * @param {Object} phantomJsConfig
+ * @returns {Promise}
+ */
+function spawn(phantomJsConfig) {
+    var args;
+    var configPath;
+    var stdout;
+    var stderr;
+    var child;
+
+    phantomJsConfig = phantomJsConfig || {};
+
+    // Saving a reference of the current stdout and stderr because this is (probably) the expected behaviour.
+    // If we wouldn't save a reference, the config of a later state would be applied because we have to
+    // do asynchronous tasks before piping the streams.
+    stdout = config.stdout;
+    stderr = config.stderr;
+
+    /**
+     * Step 1: Write the config
+     */
+    return open(null)
+        .then(function writeConfig(info) {
+            configPath = info.path;
+
+            // Pass config items in CLI style (--some-config) separately to avoid Phantom's JSON config bugs
+            // @see https://github.com/peerigon/phridge/issues/31
+            args = Object.keys(phantomJsConfig)
+                .filter(function filterCliStyle(configKey) {
+                    return configKey.charAt(0) === "-";
+                })
+                .map(function returnConfigValue(configKey) {
+                    var configValue = phantomJsConfig[configKey];
+
+                    delete phantomJsConfig[configKey];
+
+                    return configKey + "=" + configValue;
+                });
+
+            return writeFile(info.path, JSON.stringify(phantomJsConfig))
+                .then(function () {
+                    return close(info.fd);
+                });
+        })
+    /**
+     * Step 2: Start PhantomJS with the config path and pipe stderr and stdout.
+     */
+        .then(function startPhantom() {
+            return new Promise(function (resolve, reject) {
+                function onStdout(chunk) {
+                    var message = chunk.toString("utf8");
+
+                    child.stdout.removeListener("data", onStdout);
+                    child.stderr.removeListener("data", onStderr);
+
+                    if (message.slice(0, initialMessage.length) === initialMessage) {
+                        resolve();
+                    } else {
+                        reject(new Error(message));
+                    }
+                }
+
+                // istanbul ignore next because there is no way to trigger stderr artificially in a test
+                function onStderr(chunk) {
+                    var message = chunk.toString("utf8");
+
+                    child.stdout.removeListener("data", onStdout);
+                    child.stderr.removeListener("data", onStderr);
+
+                    reject(new Error(message));
+                }
+
+                args.push(
+                  "--config=" + configPath,
+                  startScript,
+                  configPath
+                );
+
+                child = childProcess.spawn(phantomjs.path, args);
+
+                prepareChildProcess(child);
+
+                child.stdout.on("data", onStdout);
+                child.stderr.on("data", onStderr);
+
+                // Our destination streams should not be ended if the childProcesses exists
+                // thus { end: false }
+                // @see https://github.com/peerigon/phridge/issues/27
+                if (stdout) {
+                    child.cleanStdout.pipe(stdout, { end: false });
+                }
+                if (stderr) {
+                    child.stderr.pipe(stderr, { end: false });
+                }
+            });
+        })
+    /**
+     * Step 3: Create the actual Phantom-instance and return it.
+     */
+        .then(function () {
+            return new Phantom(child);
+        });
+}
+
+/**
+ * Prepares childProcess' stdout for communication with phridge. The childProcess gets two new properties:
+ * - phridge: A stream which provides all messages to the phridge module
+ * - cleanStdout: A stream which provides all the other data piped to stdout.
+ *
+ * @param {child_process.ChildProcess} childProcess
+ * @private
+ */
+function prepareChildProcess(childProcess) {
+    var stdoutFork = forkStdout(childProcess.stdout);
+
+    childProcess.cleanStdout = stdoutFork.cleanStdout;
+    childProcess.phridge = stdoutFork.phridge;
+    childProcess.on("exit", disposeChildProcess);
+}
+
+/**
+ * Clean up our childProcess extensions
+ *
+ * @private
+ * @this ChildProcess
+ */
+function disposeChildProcess() {
+    var childProcess = this;
+
+    childProcess.phridge.removeAllListeners();
+    childProcess.phridge = null;
+    childProcess.cleanStdout.removeAllListeners();
+    childProcess.cleanStdout = null;
+}
+
+module.exports = spawn;