diff --git a/lib/async.js b/lib/async.js index 676ed9427..43f693f79 100644 --- a/lib/async.js +++ b/lib/async.js @@ -71,16 +71,7 @@ module.exports = { "files:watch", bs.options.get("files"), bs.pluginManager.pluginOptions - ), - mode: (function () { - if (bs.options.get("server")) { - return "server"; - } - if (bs.options.get("proxy")) { - return "proxy"; - } - return "snippet"; - })() + ) } }); }, diff --git a/lib/browser-sync.js b/lib/browser-sync.js index d060b35cd..d5be3c892 100644 --- a/lib/browser-sync.js +++ b/lib/browser-sync.js @@ -73,7 +73,9 @@ BrowserSync.prototype.init = function (options, cb) { * Some options are not compatible and will cause us to * end the process. */ - utils.verifyConfig(options, bs.cb); + if (!utils.verifyConfig(options, bs.cb)) { + return; + } /** * Save a reference to the original options diff --git a/lib/config.js b/lib/config.js index 8326c2978..efc125454 100644 --- a/lib/config.js +++ b/lib/config.js @@ -19,5 +19,9 @@ module.exports = { template: "/cli-template.js", client: { shims: "/client/client-shims.js" + }, + errors: { + "server+proxy": "Invalid config. You cannot specify both server & proxy options.", + "proxy+https": "Invalid config. You set https: true, but your proxy target doesn't reflect this." } }; diff --git a/lib/connect-utils.js b/lib/connect-utils.js index e4a0cb953..4b5321cde 100644 --- a/lib/connect-utils.js +++ b/lib/connect-utils.js @@ -13,7 +13,11 @@ var connectUtils = { scriptTags: function (options) { function getPath(relative, port) { - return options.get("scheme") + "://HOST:" + port + relative; + if (options.get("mode") === "snippet") { + return options.get("scheme") + "://HOST:" + port + relative; + } else { + return "//HOST:" + port + relative; + } } var template = fs.readFileSync(config.templates.scriptTag, "utf-8"); @@ -23,14 +27,15 @@ var connectUtils = { var override = false; if (_.isFunction(options.get("scriptPath"))) { - script = options.get("scriptPath")(scriptPath, options.get("port"), options); + var args = getScriptArgs(options, scriptPath); + script = options.get("scriptPath").apply(null, args); override = true; } else { script = getPath(scriptPath, options.get("port")); } if (!override && (options.get("server") || options.get("proxy"))) { - script = getPath(scriptPath, options.get("port")); + script = scriptPath; } template = template @@ -64,7 +69,7 @@ var connectUtils = { }, getConnectionUrl: function (options) { - var protocol = options.get("scheme") + "://"; + var protocol = ""; var namespace = options.getIn(["socket", "namespace"]); var string = "'%protocol%' + location.%host% + '%ns%'"; var socketOpts = connectUtils.resolveSocketOptions(options); @@ -73,6 +78,10 @@ var connectUtils = { return "'%s'".replace("%s", namespace(defaultConfig.socket.namespace, options)); } + if (options.get("mode") === "snippet") { + protocol = options.get("scheme") + "://"; + } + return string .replace("%protocol%", protocol) .replace("%host%", socketOpts.host) @@ -124,4 +133,17 @@ var connectUtils = { } }; +/** + * @param options + * @returns {*[]} + */ +function getScriptArgs (options, scriptPath) { + var abspath = options.get("scheme") + "://HOST:" + options.get("port") + scriptPath; + return [ + scriptPath, + options.get("port"), + options.set("absolute", abspath) + ]; +} + module.exports = connectUtils; diff --git a/lib/options.js b/lib/options.js index f20e259e3..fe44e8523 100644 --- a/lib/options.js +++ b/lib/options.js @@ -12,6 +12,7 @@ module.exports.update = function (options) { return options.withMutations(function (item) { + setMode(item); setScheme(item); setStartPath(item); setServerOpts(item); @@ -33,6 +34,22 @@ module.exports.update = function (options) { }); }; +/** + * Set the running mode + * @param item + */ +function setMode (item) { + item.set("mode", (function () { + if (item.get("server")) { + return "server"; + } + if (item.get("proxy")) { + return "proxy"; + } + return "snippet"; + })()); +} + /** * @param item */ diff --git a/lib/templates/script-tags.tmpl b/lib/templates/script-tags.tmpl index a0f033596..ec1bdfd8f 100644 --- a/lib/templates/script-tags.tmpl +++ b/lib/templates/script-tags.tmpl @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/lib/utils.js b/lib/utils.js index 010acc8b7..04e93e0c8 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -292,9 +292,7 @@ var utils = { */ getConfigErrors: function (options) { - var messages = { - "server+proxy": "Invalid config. You cannot specify both server & proxy options." - }; + var messages = require("./config").errors; var errors = []; @@ -302,6 +300,12 @@ var utils = { errors.push(messages["server+proxy"]); } + if (options.get("https") && options.get("proxy")) { + if (options.getIn(["proxy", "url", "protocol"]) !== "https:") { + errors.push([messages["proxy+https"], options.getIn(["proxy", "target"])].join(" ")); + } + } + return errors; }, /** @@ -311,7 +315,8 @@ var utils = { verifyConfig: function (options, cb) { var errors = utils.getConfigErrors(options); if (errors.length) { - return utils.fail(true, errors.join("\n"), cb); + utils.fail(true, errors.join("\n"), cb); + return false; } return true; }, diff --git a/test/protractor/_run.js b/test/protractor/_run.js index ec8700873..48bf9c3f7 100644 --- a/test/protractor/_run.js +++ b/test/protractor/_run.js @@ -8,8 +8,10 @@ module.exports = function (logger) { return function (config, configFile, cb) { browserSync.reset(); var instance = browserSync(config.bsConfig, function (err, bs) { - var url = bs.getOptionIn(["urls", "local"]); + var url = bs.getOptionIn(["urls", "local"]); + var uiurl = bs.getOptionIn(["urls", "ui"]); process.env["BS_BASE"] = url; + process.env["BS_UI"] = uiurl; process.env["BS_SCRIPT_PATH"] = bs.getOptionIn(["scriptPaths", "path"]); logger.info("Testing BrowserSync at %s", url); @@ -28,6 +30,7 @@ module.exports = function (logger) { function runTests (config, configFile, bs, cb) { var out = ""; exec("protractor " + configFile, function (err, stdout) { + console.log(stdout); if (err) { doCallback({ code: 1, diff --git a/test/protractor/tests.multi.js b/test/protractor/tests.multi.js index cf48e2f4a..b0d161f97 100644 --- a/test/protractor/tests.multi.js +++ b/test/protractor/tests.multi.js @@ -1,10 +1,23 @@ "use strict"; -var fs = require("fs"); -var path = require("path"); var connect = require("connect"); -var serveStatic = require("serve-static"); -var http = require("http"); +var utils = require("../../lib/server/utils"); +var Immutable = require("immutable"); + +function getApp (bs, options) { + + var html = "BrowsersyncBS"; + var app = connect(); + + app.use("/", function (req, res) { + res.setHeader("content-type", "text/html"); + res.end(html.replace("BS", bs.getOption("snippet"))); + }); + + var appserver = utils.getServer(app, options); + + return appserver; +} module.exports = { "Proxy Test Laravel App": { @@ -28,6 +41,13 @@ module.exports = { logLevel: "silent" } }, + "Secure Proxy": { + bsConfig: { + proxy: "https://grenade.static:8890", + open: false, + logLevel: "silent" + } + }, "Server": { bsConfig: { server: "./test/fixtures", @@ -48,23 +68,35 @@ module.exports = { logLevel: "silent" }, before: function (bs, cb) { - var filepath = path.resolve(__dirname + "/../fixtures/index.html"); - var file = fs.readFileSync(filepath, "utf-8"); - var modded = file.replace("", bs.getOption("snippet")); - var app = connect(); - app.use(serveStatic(path.resolve(__dirname + "/../fixtures"))); - var server = http.createServer(app).listen(); - var port = server.address().port; - var url = "http://localhost:" + port; - process.env["BS_BASE"] = url; - fs.writeFileSync(filepath, modded); + var app = getApp(bs, Immutable.Map({scheme: "http"})); + app.server.listen(); + process.env["BS_BASE"] = "http://localhost:" + app.server.address().port; + cb(); + } + }, + "Secure Snippet on Insecure Website": { + bsConfig: { + logLevel: "silent", + https: true + }, + before: function (bs, cb) { + + var app = getApp(bs, Immutable.Map({scheme: "http"})); + app.server.listen(); + process.env["BS_BASE"] = "http://localhost:" + app.server.address().port; cb(); + } + }, + "Secure Snippet on Secure Website": { + bsConfig: { + logLevel: "silent", + https: true }, - after: function (bs, cb) { - var filepath = path.resolve(__dirname + "/../fixtures/index.html"); - var file = fs.readFileSync(filepath, "utf-8"); - var modded = file.replace(bs.getOption("snippet"), ""); - fs.writeFileSync(filepath, modded); + before: function (bs, cb) { + + var app = getApp(bs, Immutable.Map({scheme: "https"})); + app.server.listen(); + process.env["BS_BASE"] = "https://localhost:" + app.server.address().port; cb(); } } diff --git a/test/protractor/tests/snippet.injection.js b/test/protractor/tests/snippet.injection.js index ec1de8f7d..a79eb2a94 100644 --- a/test/protractor/tests/snippet.injection.js +++ b/test/protractor/tests/snippet.injection.js @@ -4,13 +4,13 @@ * */ // abstract writing screen shot to a file -var fs = require("fs"); -var slugify = require("slugify"); -function writeScreenShot(data, filename) { - var stream = fs.createWriteStream(filename); - stream.write(new Buffer(data, "base64")); - stream.end(); -} +//var fs = require("fs"); +//var slugify = require("slugify"); +//function writeScreenShot(data, filename) { +// var stream = fs.createWriteStream(filename); +// stream.write(new Buffer(data, "base64")); +// stream.end(); +//} /** * @@ -19,15 +19,17 @@ describe("Section Navigation", function () { beforeEach(function () { browser.ignoreSynchronization = true; browser.get("/"); - // within a test: - browser.takeScreenshot().then(function (png) { - writeScreenShot(png, "screenshots/ss_" + slugify(process.env["BS_TEST_NAME"]) + ".png"); - }); }); it("should contain the BS script element", function () { expect(element(by.id("__bs_script__")).isPresent()).toBeTruthy(); }); it("should contain the BS NOTIFY ELEMENT", function () { + //browser.pause(); expect(element(by.id("__bs_notify__")).isPresent()).toBeTruthy(); }); + it("should launch UI", function () { + browser.ignoreSynchronization = false; + browser.get(process.env["BS_UI"]); + expect(element.all(by.css("[bs-heading]")).get(0).isPresent()).toBeTruthy(); + }); }); diff --git a/test/protractor/utils.js b/test/protractor/utils.js new file mode 100644 index 000000000..b1d219385 --- /dev/null +++ b/test/protractor/utils.js @@ -0,0 +1,23 @@ +"use strict"; + +var utils = require("../../lib/server/utils"); +var connect = require("connect"); + +module.exports = { + + getApp: function getApp (options) { + + var html = "BrowsersyncBS"; + var app = connect(); + + app.use("/", function (req, res) { + res.setHeader("content-type", "text/html"); + res.end(html); + }); + + var appserver = utils.getServer(app, options); + appserver.html = html; + + return appserver; + } +}; diff --git a/test/specs/e2e/proxy/e2e.proxy.secure.js b/test/specs/e2e/proxy/e2e.proxy.secure.js new file mode 100644 index 000000000..9779ebfdb --- /dev/null +++ b/test/specs/e2e/proxy/e2e.proxy.secure.js @@ -0,0 +1,93 @@ +"use strict"; + +var browserSync = require("../../../../index"); +var testUtils = require("../../../protractor/utils"); +var Immutable = require("immutable"); +var request = require("supertest"); +var assert = require("chai").assert; + +process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0; + +describe("E2E TLS proxy test", function () { + + this.timeout(15000); + + var bs, app; + + before(function (done) { + + browserSync.reset(); + + app = testUtils.getApp(Immutable.Map({scheme: "https"})); + + app.server.listen(); + + var config = { + proxy: "https://localhost:" + app.server.address().port, + open: false, + logLevel: "silent" + }; + + bs = browserSync.init(config, done).instance; + }); + + after(function () { + bs.cleanup(); + app.server.close(); + }); + + it("Set's a HTTPS url", function () { + var local = bs.options.getIn(["urls", "local"]); + assert.equal("https://localhost:" + bs.options.get("port"), local); + }); + + it("proxies over https and injects snippet", function (done) { + + assert.isString(bs.options.get("snippet")); + + var expected = app.html.replace("BS", bs.options.get("snippet") + "BS"); + + request(bs.options.getIn(["urls", "local"])) + .get("/index.html") + .set("accept", "text/html") + .expect(200, expected, done); + }); +}); + +describe("E2E TLS proxy Options test", function () { + + this.timeout(15000); + + var app; + + before(function () { + + browserSync.reset(); + + app = testUtils.getApp(Immutable.Map({scheme: "https"})); + + app.server.listen(); + }); + + after(function () { + app.server.close(); + }); + + it("Exits if https specified in options, but not in target", function (done) { + var utils = require("../../../../lib/utils"); + var errors = require("../../../../lib/config").errors; + var sinon = require("sinon"); + var config = { + proxy: "http://localhost:" + app.server.address().port, + https: true, + open: false, + logLevel: "silent" + }; + var stub = sinon.stub(utils, "fail"); + browserSync.init(config); + sinon.assert.called(stub); + assert.include(stub.getCall(0).args[1], errors["proxy+https"]); + utils.fail.restore(); + done(); + }); +}); diff --git a/test/specs/utils/utils.connect.js b/test/specs/utils/utils.connect.js index 5f6f4952b..6f80b58d0 100644 --- a/test/specs/utils/utils.connect.js +++ b/test/specs/utils/utils.connect.js @@ -15,7 +15,7 @@ describe("Connection utils", function () { }); it("should return a connection url with http", function () { var actual = utils.getConnectionUrl(options); - var expected = "'http://' + location.host + '/browser-sync'"; + var expected = "'' + location.host + '/browser-sync'"; assert.equal(actual, expected); }); it("should return a connection url for snippet mode", function () { @@ -35,7 +35,7 @@ describe("Connection utils", function () { mode: "proxy" }); var actual = utils.socketConnector(options); - assert.include(actual, "'http://' + location.host + '/browser-sync'"); + assert.include(actual, "'' + location.host + '/browser-sync'"); }); it("should return a connection url for server mode", function () { var options = merge({ @@ -44,7 +44,7 @@ describe("Connection utils", function () { mode: "server" }); var actual = utils.socketConnector(options); - assert.include(actual, "'http://' + location.host + '/browser-sync'"); + assert.include(actual, "'' + location.host + '/browser-sync'"); }); it("should return a connection url for server mode, https", function () { var options = merge({ @@ -53,7 +53,7 @@ describe("Connection utils", function () { mode: "server" }); var actual = utils.socketConnector(options); - assert.include(actual, "'https://' + location.host + '/browser-sync'"); + assert.include(actual, "'' + location.host + '/browser-sync'"); }); it("should return a connection url for snippet mode", function () { var options = merge({ @@ -64,7 +64,7 @@ describe("Connection utils", function () { var actual = utils.socketConnector(options); assert.include(actual, "'http://' + location.hostname + ':4002/browser-sync'"); }); - it("should return a connection url for snippet mode", function () { + it("should return a connection url for secure snippet mode", function () { var options = merge({ port: 4002, scheme: "https",