From f6a3601f314a36f13e64cf0e8d1ddcea3127acf6 Mon Sep 17 00:00:00 2001 From: Shane Osbourne Date: Tue, 7 Apr 2015 16:32:35 +0100 Subject: [PATCH] feat(http-protocol): Add reload method to http protocol --- lib/async-tasks.js | 4 ++ lib/async.js | 22 ++++++++ lib/default-config.js | 10 ++++ lib/http-protocol.js | 59 ++++++++++++++++++++ package.json | 3 +- test/specs/http-protocol/http.reload.js | 72 +++++++++++++++++++++++++ 6 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 lib/http-protocol.js create mode 100644 test/specs/http-protocol/http.reload.js diff --git a/lib/async-tasks.js b/lib/async-tasks.js index 8432923cd..198639b0c 100644 --- a/lib/async-tasks.js +++ b/lib/async-tasks.js @@ -41,6 +41,10 @@ module.exports = [ step: "Starting the web-socket server", fn: async.startSockets }, + { + step: "Adding the HTTP protocol", + fn: async.addHttpProtocol + }, { step: "Starting the UI", fn: async.startUi diff --git a/lib/async.js b/lib/async.js index 6594b3161..c6a971288 100644 --- a/lib/async.js +++ b/lib/async.js @@ -228,6 +228,28 @@ module.exports = { } }); }, + /** + * @param bs + * @param done + */ + addHttpProtocol: function (bs, done) { + + if (!bs.options.get("httpProtocol")) { + return done(null); + } + + /** + * Add a middleware to listen to http + * requests in the BrowserSync http protocol namespace + */ + bs.addMiddleware( + bs.options.getIn(["httpProtocol", "path"]), + require("./http-protocol").middleware(bs), + {override: true} + ); + + done(); + }, /** * * @param {BrowserSync} bs diff --git a/lib/default-config.js b/lib/default-config.js index 0ad95379a..bf6cd1709 100644 --- a/lib/default-config.js +++ b/lib/default-config.js @@ -339,6 +339,16 @@ module.exports = { } }, + /** + * @property httpProtocol + * @param {String} [path="/__browser_sync__"] + * @since 2.6.0 + * @type Object + */ + httpProtocol: { + path: "/__browser_sync__" + }, + /** * A map of file-extensions -> dom element. * Useful if you're serving files with unusual file extensions diff --git a/lib/http-protocol.js b/lib/http-protocol.js new file mode 100644 index 000000000..0283a6fb9 --- /dev/null +++ b/lib/http-protocol.js @@ -0,0 +1,59 @@ +"use strict"; + +var queryString = require("query-string"); +var proto = exports; + +/** + * Use BrowserSync options + querystring to create a + * full HTTP/HTTTPS url. + * + * Eg. http://localhost:3000/__browser_sync__?method=reload + * Eg. http://localhost:3000/__browser_sync__?method=reload&args=core.css + * Eg. http://localhost:3000/__browser_sync__?method=reload&args=core.css&args=core.min + * + * @param args + * @param bs + * @returns {string} + */ +proto.getUrl = function (args, bs) { + return [ + bs.options.getIn(["urls", "local"]), + bs.options.getIn(["httpProtocol", "path"]), + "?", + queryString.stringify(args) + ].join(""); +}; + +/** + * Return a middleware for handling the requests + * @param {BrowserSync} bs + * @returns {Function} + */ +proto.middleware = function (bs) { + + return function (req, res) { + + var params = queryString.parse(req.url.replace(/^.*\?/, "")); + + if (!params) { + return; + } + + if (require.resolve("./public/" + params.method)) { + + require("./public/" + params.method)(bs.events).apply(null, [params.args]); + + var output = [ + "Called public API method `.%s()`".replace("%s", params.method), + "With args: " + JSON.stringify(params.args) + ]; + + res.end(output.join("\n")); + + } else { + res.end("Public API method `" + params.method + "` not found."); + } + + + }; +}; \ No newline at end of file diff --git a/package.json b/package.json index ab537fe7a..95750c257 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "meow": "^3.1.0", "opn": "^1.0.0", "portscanner": "^1.0.0", + "query-string": "^1.0.0", "resp-modifier": "^2.0.1", "serve-index": "^1.6.3", "serve-static": "^1.9.1", @@ -75,7 +76,7 @@ "istanbul-coveralls": "^1.0.0", "mocha": "*", "q": "^1.1.2", - "request": "^2.51.0", + "request": "^2.55.0", "sinon": "^1.12.1", "slugify": "^0.1.0", "socket.io-client": "^1.3.5", diff --git a/test/specs/http-protocol/http.reload.js b/test/specs/http-protocol/http.reload.js new file mode 100644 index 000000000..3750ad420 --- /dev/null +++ b/test/specs/http-protocol/http.reload.js @@ -0,0 +1,72 @@ +"use strict"; + +var browserSync = require("../../../index"); +var request = require("request"); +var assert = require("chai").assert; +var sinon = require("sinon"); +var proto = require("../../../lib/http-protocol"); + +describe("HTTP protocol", function () { + + var bs, spy; + + before(function (done) { + + browserSync.reset(); + + var config = { + server: "test/fixtures", + logLevel: "info", + open: false, + online: false + }; + + bs = browserSync.init(config, done).instance; + + spy = sinon.spy(bs.events, "emit"); + }); + + afterEach(function () { + spy.reset(); + }); + + after(function () { + bs.cleanup(); + }); + + it("responds to reload event with no args", function (done) { + + var url = proto.getUrl({method: "reload"}, bs); + + request(url, function (e, r, body) { + sinon.assert.calledWith(spy, "browser:reload"); + assert.include(body, "Called public API method `.reload()`"); + assert.include(body, "With args: undefined"); + done(); + }); + }); + it("responds to reload event with multi file paths", function (done) { + + var url = proto.getUrl({method: "reload", args: ["core.min.css", "core.css"]}, bs); + + request(url, function (e, r, body) { + sinon.assert.calledWith(spy, "file:changed"); + sinon.assert.calledWithExactly(spy, "file:changed", { path: "core.min.css", log: true, namespace: "core" }); + assert.include(body, "Called public API method `.reload()`"); + assert.include(body, "With args: [\"core.min.css\",\"core.css\"]"); + done(); + }); + }); + it("responds to reload event with single file path", function (done) { + + var url = proto.getUrl({method: "reload", args: "somefile.php"}, bs); + + request(url, function (e, r, body) { + sinon.assert.calledWith(spy, "file:changed"); + sinon.assert.calledWithExactly(spy, "file:changed", { path: "somefile.php", log: true, namespace: "core" }); + assert.include(body, "Called public API method `.reload()`"); + assert.include(body, "With args: \"somefile.php\""); + done(); + }); + }); +});