diff --git a/lib/async-tasks.js b/lib/async-tasks.js index fa337fa64..a419cf280 100644 --- a/lib/async-tasks.js +++ b/lib/async-tasks.js @@ -5,6 +5,10 @@ module.exports = [ step: "Finding an empty port", fn: async.getEmptyPort }, + { + step: "Getting an extra port for Proxy", + fn: async.getExtraPortForProxy + }, { step: "Checking online status", fn: async.getOnlineStatus diff --git a/lib/async.js b/lib/async.js index 5336daa5a..7a270843b 100644 --- a/lib/async.js +++ b/lib/async.js @@ -27,6 +27,47 @@ module.exports = { }); }); }, + /** + * If the running mode is proxy, we'll use a separate port + * for the Browsersync web-socket server. This is to eliminate any issues + * with trying to proxy web sockets through to the users server. + * @param bs + * @param done + */ + getExtraPortForProxy: function (bs, done) { + /** + * An extra port is not needed in snippet/server mode + */ + if (bs.options.get("mode") !== "proxy") { + return done(); + } + + /** + * Use 1 higher than server port by default... + */ + var socketPort = bs.options.get("port") + 1; + + /** + * Or use the user-defined socket.port option instead + */ + if (bs.options.hasIn(["socket", "port"])) { + socketPort = bs.options.getIn(["socket", "port"]); + } + + utils.getPort(socketPort, null, function (err, port) { + if (err) { + return done(err); + } + done(null, { + optionsIn: [ + { + path: ["socket", "port"], + value: port + } + ] + }); + }); + }, /** * Some features require an internet connection. * If the user did not provide either `true` or `false` diff --git a/lib/cli/command.reload.js b/lib/cli/command.reload.js index ec6771cfa..0dc1a34a3 100644 --- a/lib/cli/command.reload.js +++ b/lib/cli/command.reload.js @@ -17,8 +17,13 @@ module.exports = function (opts) { } var proto = require("../http-protocol"); var scheme = flags.url.match(/^https/) ? "https" : "http"; + var args = {method: "reload"}; - var url = proto.getUrl({method: "reload", args: flags.files}, flags.url); + if (flags.files) { + args.args = flags.files; + } + + var url = proto.getUrl(args, flags.url); require(scheme).get(url, function (res) { res.on("data", function () { diff --git a/lib/connect-utils.js b/lib/connect-utils.js index 506665827..802f9977a 100644 --- a/lib/connect-utils.js +++ b/lib/connect-utils.js @@ -91,15 +91,20 @@ var connectUtils = { var withHostnamePort = "'{protocol}' + location.hostname + ':{port}{ns}'"; var withHost = "'{protocol}' + location.host + '{ns}'"; var withDomain = "'{domain}{ns}'"; + var port = options.get("port"); // default use-case is server/proxy var string = withHost; - if (options.get("mode") === "snippet") { + if (options.get("mode") !== "server") { protocol = options.get("scheme") + "://"; string = withHostnamePort; } + if (options.get("mode") === "proxy") { + port = options.getIn(["socket", "port"]); + } + if (socketOpts.domain) { string = withDomain; if (typeof socketOpts.domain === "function") { @@ -109,7 +114,7 @@ var connectUtils = { return string .replace("{protocol}", protocol) - .replace("{port}", options.get("port")) + .replace("{port}", port) .replace("{domain}", socketOpts.domain) .replace("{ns}", namespace); }, diff --git a/lib/default-config.js b/lib/default-config.js index 7d59d72d9..2d8b0c7ac 100644 --- a/lib/default-config.js +++ b/lib/default-config.js @@ -378,11 +378,15 @@ module.exports = { * @param {String} [clientPath="/browser-sync"] * @param {String|Function} [namespace="/browser-sync"] * @param {String|Function} [domain=undefined] + * @param {String|Function} [port=undefined] * @param {Object} [clients.heartbeatTimeout=5000] * @since 1.6.2 * @type Object */ socket: { + socketIoOptions: { + log: false, + }, path: "/browser-sync/socket.io", clientPath: "/browser-sync", namespace: "/browser-sync", diff --git a/lib/server/proxy-server.js b/lib/server/proxy-server.js index ec634ad00..cb5dcbcdf 100644 --- a/lib/server/proxy-server.js +++ b/lib/server/proxy-server.js @@ -29,9 +29,9 @@ module.exports = function createProxyServer (bs, scripts) { bs.proxy.app.use(bs.options.getIn(["scriptPaths", "path"]), scripts); /** - * How best to handle websockets going forward? + * Also proxy upgrades for Web Socket support */ - //proxy.server.on("upgrade", app.handleUpgrade); + proxy.server.on("upgrade", bs.proxy.app.handleUpgrade); return proxy; }; diff --git a/lib/sockets.js b/lib/sockets.js index 71d171bc8..1c73f5dc6 100644 --- a/lib/sockets.js +++ b/lib/sockets.js @@ -1,6 +1,7 @@ "use strict"; var socket = require("socket.io"); +var utils = require("./server/utils"); var Steward = require("emitter-steward"); /** @@ -18,10 +19,24 @@ module.exports.plugin = function (server, clientEvents, bs) { */ module.exports.init = function (server, clientEvents, bs) { - var emitter = bs.events; - var socketConfig = bs.options.get("socket").toJS(); + var emitter = bs.events; - var io = socket.listen(server, {log: false, path: socketConfig.path}); + var socketConfig = bs.options + .get("socket") + .toJS(); + + if (bs.options.get("mode") === "proxy") { + server = utils.getServer(null, bs.options).server; + server.listen(bs.options.getIn(["socket", "port"])); + bs.registerCleanupTask(function () { + server.close(); + }); + } + + var socketIoConfig = socketConfig.socketIoOptions; + socketIoConfig.path = socketConfig.path; + + var io = socket(server, socketIoConfig); // Override default namespace. io.sockets = io.of(socketConfig.namespace); diff --git a/lib/utils.js b/lib/utils.js index eaae8ce29..b4e69320c 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -119,6 +119,9 @@ var utils = { max = ports.get("max") || null; } + utils.getPort(port, max, cb); + }, + getPort: function (port, max, cb) { portScanner.findAPortNotInUse(port, max, { host: "localhost", timeout: 1000 diff --git a/package.json b/package.json index f2f3d24d7..3b4fa9df0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "browser-sync", "description": "Live CSS Reload & Browser Syncing", - "version": "2.8.0", + "version": "2.7.13", "homepage": "http://www.browsersync.io/", "author": { "name": "Shane Osbourne" @@ -34,7 +34,7 @@ "async-each-series": "^0.1.1", "browser-sync-client": "^2.2.1", "browser-sync-ui": "^0.5.12", - "chokidar": "^1.0.4", + "chokidar": "^1.0.5", "connect": "^3.4.0", "dev-ip": "^1.0.1", "easy-extender": "^2.3.1", @@ -42,15 +42,15 @@ "emitter-steward": "^0.0.1", "foxy": "^11.1.1", "immutable": "^3.7.4", - "localtunnel": "^1.6.0", + "localtunnel": "^1.7.0", "lodash": "^3.9.3", "longest": "^1.0.1", "meow": "^3.3.0", "opn": "^2.0.1", "pad-left": "^1.0.2", "portscanner": "^1.0.0", - "query-string": "^2.3.0", - "resp-modifier": "^4.0.3", + "query-string": "^2.4.0", + "resp-modifier": "^4.0.4", "serve-index": "^1.7.0", "serve-static": "^1.10.0", "socket.io": "^1.3.6", @@ -75,7 +75,7 @@ "istanbul-coveralls": "^1.0.3", "mocha": "^2.2.5", "q": "^1.4.1", - "request": "^2.59.0", + "request": "^2.60.0", "sinon": "^1.15.4", "slugify": "^0.1.1", "socket.io-client": "^1.3.6", diff --git a/test/specs/e2e/e2e.options.port.js b/test/specs/e2e/e2e.options.port.js index d4368edd0..f3df05d44 100644 --- a/test/specs/e2e/e2e.options.port.js +++ b/test/specs/e2e/e2e.options.port.js @@ -27,4 +27,55 @@ describe("E2E `port` option", function () { done(); }); }); + it("sets extra port option for socket in proxy mode", function (done) { + browserSync.reset(); + + var stub = sinon.stub(utils, "getPort"); + + stub.onCall(0).yields(null, 3000); + stub.onCall(1).yields(null, 3001); + + var config = { + logLevel: "silent", + proxy: "localhost", + online: false, + open: false + }; + + browserSync(config, function (err, bs) { + bs.cleanup(); + assert.equal(bs.options.get("port"), 3000); + assert.equal(stub.getCall(1).args[0], 3001); + assert.equal(bs.options.getIn(["socket", "port"]), 3001); + utils.getPort.restore(); + done(); + }); + }); + it("uses user-given extra port option for socket in proxy mode", function (done) { + browserSync.reset(); + + var stub = sinon.stub(utils, "getPort"); + + stub.onCall(0).yields(null, 3000); + stub.onCall(1).yields(null, 8001); + + var config = { + logLevel: "silent", + proxy: "localhost", + socket: { + port: 8001 + }, + online: false, + open: false + }; + + browserSync(config, function (err, bs) { + bs.cleanup(); + assert.equal(bs.options.get("port"), 3000); + assert.equal(stub.getCall(1).args[0], 8001); + assert.equal(bs.options.getIn(["socket", "port"]), 8001); + utils.getPort.restore(); + done(); + }); + }); }); diff --git a/test/specs/utils/utils.connect.js b/test/specs/utils/utils.connect.js index 16d1e2fb8..e96f7450a 100644 --- a/test/specs/utils/utils.connect.js +++ b/test/specs/utils/utils.connect.js @@ -36,10 +36,13 @@ describe("Connection utils", function () { var options = merge({ port: 3002, scheme: "http", - mode: "proxy" + mode: "proxy", + socket: { + port: 4000 + } }); var actual = utils.socketConnector(options); - assert.include(actual, "'' + location.host + '/browser-sync'"); + assert.include(actual, "'http://' + location.hostname + ':4000/browser-sync'"); }); it("should return a connection url for server mode", function () { var options = merge({