diff --git a/lib/async-tasks.js b/lib/async-tasks.js index 198639b0c..208ebe729 100644 --- a/lib/async-tasks.js +++ b/lib/async-tasks.js @@ -29,6 +29,10 @@ module.exports = [ step: "Merging middlewares from core + plugins", fn: async.mergeMiddlewares }, + { + step: "Setting the rewrite rules middleware for snippet", + fn: async.setRewriteRules + }, { step: "Starting the Server", fn: async.startServer diff --git a/lib/async.js b/lib/async.js index 6406b194d..c70cc31bc 100644 --- a/lib/async.js +++ b/lib/async.js @@ -138,6 +138,18 @@ module.exports = { } }); }, + setRewriteRules: function (bs, done) { + var snippetUtils = require("./snippet").utils; + done(null, { + instance: { + snippetMw: snippetUtils.getSnippetMiddleware( + bs.options.get("snippet"), + bs.options.get("snippetOptions"), + bs.options.get("rewriteRules") + ) + } + }); + }, /** * @param {BrowserSync} bs * @param {Function} done diff --git a/lib/browser-sync.js b/lib/browser-sync.js index 20819c095..941fcc248 100644 --- a/lib/browser-sync.js +++ b/lib/browser-sync.js @@ -575,6 +575,45 @@ BrowserSync.prototype.setMany = function (fn, opts) { return this.options; }; +/** + * Remove a rewrite rule by id + */ +BrowserSync.prototype.removeRewriteRule = function (id) { + var bs = this; + var mode = bs.options.get("mode"); + + if (mode === "server") { + bs.snippetMw.opts.rules = bs.snippetMw.opts.rules.filter(fn); + } + + if (mode === "proxy") { + bs.proxy.config.rules = bs.proxy.config.rules.filter(fn); + } + + function fn (item) { + if (item.id) { + return item.id !== id; + } + return true; + } +}; + +/** + * Add a new rewrite rule to the stack + * @param {Object} rule + */ +BrowserSync.prototype.addRewriteRule = function (rule) { + var bs = this; + + if (bs.options.get("mode") === "server") { + bs.snippetMw.opts.rules.push(rule); + } + + if (bs.options.get("mode") === "proxy") { + bs.proxy.config.rules.push(rule); + } +}; + /** * Handle Browser Reloads */ diff --git a/lib/server/proxy-server.js b/lib/server/proxy-server.js index 2e76d7fc1..eacefef3c 100644 --- a/lib/server/proxy-server.js +++ b/lib/server/proxy-server.js @@ -3,6 +3,7 @@ var snippetUtils = require("./../snippet").utils; var _ = require("lodash"); var utils = require("./utils"); +var Immutable = require("immutable"); /** * @param {BrowserSync} bs @@ -21,9 +22,9 @@ module.exports = function createProxyServer (bs, scripts) { var opts = getOptions(bs, scripts); - var app = require("foxy")(options.getIn(["proxy", "target"]), opts); + bs.proxy = require("foxy").create(options.getIn(["proxy", "target"]), opts); - var proxy = utils.getServer(app, bs.options); + var proxy = utils.getServer(bs.proxy.app, bs.options); /** * How best to handle websockets going forward? @@ -41,19 +42,15 @@ function getOptions (bs, scripts) { var options = bs.options; - var snippetOptions = options.get("snippetOptions").toJS(); - var proxyOptions = options.getIn(["proxy", "proxyOptions"]); - var rewrites = [snippetUtils.getRegex(options.get("snippet"), options.get("snippetOptions"))]; - - if (bs.options.get("rewriteRules")) { - rewrites = rewrites.concat(bs.options.get("rewriteRules").toJS()); - } + var proxyOptions = options.getIn(["proxy", "proxyOptions"]); + var rules = bs.snippetMw; + var cookies = options.getIn(["proxy", "cookies"]); var out = { - rules: rewrites, - whitelist: snippetOptions.whitelist, - blacklist: snippetOptions.blacklist, - middleware: options.get("middleware").push(getPluginMiddleware(bs, scripts)), + rules: rules.opts.rules, + whitelist: rules.opts.whitelist, + blacklist: rules.opts.blacklist, + middleware: options.get("middleware").toJS().concat(getPluginMiddleware(bs, scripts)), errHandler: function (err) { bs.logger.debug("{red:[proxy error]} %s", err.message); } @@ -67,8 +64,10 @@ function getOptions (bs, scripts) { out.reqHeaders = options.getIn(["proxy", "reqHeaders"]); } - if (options.getIn(["proxy", "cookies"])) { - out.cookies = options.getIn(["proxy", "cookies"]); + if (!_.isUndefined(cookies)) { + if (Immutable.Map.isMap(cookies)) { + out.cookies = cookies.toJS(); + } } return out; diff --git a/lib/server/static-server.js b/lib/server/static-server.js index 3bf2de691..06e1174b3 100644 --- a/lib/server/static-server.js +++ b/lib/server/static-server.js @@ -2,7 +2,6 @@ var connect = require("connect"); var utils = require("./utils.js"); -var snippetUtils = require("./../snippet").utils; /** * @param {BrowserSync} bs @@ -39,14 +38,9 @@ module.exports = function createServer (bs, scripts) { /** * Add snippet injection middleware + * This also includes any additional middleware given from the user */ - app.use( - snippetUtils.getSnippetMiddleware( - options.get("snippet"), - options.get("snippetOptions"), - options.get("rewriteRules") - ) - ); + app.use(bs.snippetMw.middleware); /** * Add user-provided middlewares diff --git a/lib/snippet.js b/lib/snippet.js index 740c0ddc9..48fa0d30e 100644 --- a/lib/snippet.js +++ b/lib/snippet.js @@ -47,15 +47,21 @@ var utils = { }; }, getSnippetMiddleware: function (snippet, options, rewriteRules) { + return lrSnippet.create(utils.getRules(snippet, options, rewriteRules)); + }, + getRules: function (snippet, options, rewriteRules) { + var rules = [utils.getRegex(snippet, options)]; + if (rewriteRules) { rules = rules.concat(rewriteRules.toJS()); } - return lrSnippet({ + + return { rules: rules, blacklist: options.get("blacklist").toJS(), whitelist: options.get("whitelist").toJS() - }); + }; }, /** * @param {String} scripts - the client side JS diff --git a/package.json b/package.json index 464177f8b..a311cef46 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "easy-extender": "^2.3.1", "eazy-logger": "^2.1.2", "emitter-steward": "^0.0.1", - "foxy": "^10.1.2", + "foxy": "^11.0.0", "immutable": "^3.6.4", "localtunnel": "^1.3.0", "lodash": "^3.8.0", @@ -50,7 +50,7 @@ "pad-left": "^1.0.2", "portscanner": "^1.0.0", "query-string": "^2.0.0", - "resp-modifier": "^2.1.0", + "resp-modifier": "^3.1.2", "serve-index": "^1.6.3", "serve-static": "^1.9.1", "socket.io": "^1.3.5", diff --git a/test/specs/e2e/proxy/e2e.proxy.cookies.js b/test/specs/e2e/proxy/e2e.proxy.cookies.js index a7888f298..20a62d57c 100644 --- a/test/specs/e2e/proxy/e2e.proxy.cookies.js +++ b/test/specs/e2e/proxy/e2e.proxy.cookies.js @@ -33,7 +33,7 @@ describe("E2E proxy test with custom cookies options passed to foxy", function ( logLevel: "silent" }; - spy = require("sinon").spy(require.cache[foxyPath], "exports"); + spy = require("sinon").spy(require.cache[foxyPath].exports, "create"); bs = browserSync.init(config, done).instance; }); @@ -44,7 +44,7 @@ describe("E2E proxy test with custom cookies options passed to foxy", function ( it("sets cookie stripDomain: false", function (done) { - assert.isFalse(spy.getCall(0).args[1].cookies.toJS().stripDomain); // check fn passed to foxy + assert.isFalse(spy.getCall(0).args[1].cookies.stripDomain); // check fn passed to foxy spy.restore(); diff --git a/test/specs/e2e/proxy/e2e.proxy.proxy.options.js b/test/specs/e2e/proxy/e2e.proxy.proxy.options.js index 79c32de7e..080eabc8d 100644 --- a/test/specs/e2e/proxy/e2e.proxy.proxy.options.js +++ b/test/specs/e2e/proxy/e2e.proxy.proxy.options.js @@ -33,7 +33,7 @@ describe("E2E proxy test with `proxyOptions`", function () { logLevel: "silent" }; - spy = require("sinon").spy(require.cache[foxyPath], "exports"); + spy = require("sinon").spy(require.cache[foxyPath].exports, "create"); bs = browserSync.init(config, done).instance; }); diff --git a/test/specs/e2e/proxy/e2e.proxy.req.headers.js b/test/specs/e2e/proxy/e2e.proxy.req.headers.js index 11cfd0153..607617b22 100644 --- a/test/specs/e2e/proxy/e2e.proxy.req.headers.js +++ b/test/specs/e2e/proxy/e2e.proxy.req.headers.js @@ -35,7 +35,7 @@ describe("E2E proxy test with custom req headers", function () { logLevel: "silent" }; - spy = require("sinon").spy(require.cache[foxyPath], "exports"); + spy = require("sinon").spy(require.cache[foxyPath].exports, "create"); bs = browserSync.init(config, done).instance; }); diff --git a/test/specs/e2e/proxy/e2e.proxy.rewrite.rules.add.js b/test/specs/e2e/proxy/e2e.proxy.rewrite.rules.add.js new file mode 100644 index 000000000..3c9f437a5 --- /dev/null +++ b/test/specs/e2e/proxy/e2e.proxy.rewrite.rules.add.js @@ -0,0 +1,74 @@ +"use strict"; + +var browserSync = require("../../../../index"); + +var connect = require("connect"); +var serveStatic = require("serve-static"); +var request = require("supertest"); +var assert = require("chai").assert; + +describe("E2E proxy test with adding rewrite rules dynamically", function () { + + var bs, server, options; + + before(function (done) { + + browserSync.reset(); + + var app = connect(); + app.use(serveStatic("test/fixtures")); + server = app.listen(); + var proxytarget = "http://localhost:" + server.address().port; + + var config = { + proxy: proxytarget, + logLevel: "silent", + open: false, + rewriteRules: [ + { + match: /BrowserSync/g, + fn: function () { + return "BROWSERSYNC"; + } + } + ] + }; + + bs = browserSync.init([], config, function (err, bs) { + options = bs.options; + done(); + }).instance; + }); + + after(function () { + bs.cleanup(); + server.close(); + }); + + it("can add rules on the fly", function (done) { + + request(bs.server) + .get("/index.html") + .set("accept", "text/html") + .expect(200) + .end(function (err, res) { + + assert.include(res.text, "BROWSERSYNC"); + + bs.addRewriteRule({ + match: "BROWSERSYNC", + replace: "browsersync" + }, {id: "myrule"}); + + request(bs.server) + .get("/index.html") + .set("accept", "text/html") + .expect(200) + .end(function (err, res) { + assert.include(res.text, "browsersync"); + assert.notInclude(res.text, "BROWSERSYNC"); + done(); + }); + }); + }); +}); diff --git a/test/specs/e2e/proxy/e2e.proxy.rewrite.rules.js b/test/specs/e2e/proxy/e2e.proxy.rewrite.rules.js index 2e4ef10f5..e6c2f24f2 100644 --- a/test/specs/e2e/proxy/e2e.proxy.rewrite.rules.js +++ b/test/specs/e2e/proxy/e2e.proxy.rewrite.rules.js @@ -9,14 +9,14 @@ var assert = require("chai").assert; describe("E2E proxy test with rewrite rules", function () { - var instance, server, options; + var bs, server, options; before(function (done) { browserSync.reset(); var app = connect(); - app.use(serveStatic("./test/fixtures")); + app.use(serveStatic("test/fixtures")); server = app.listen(); var proxytarget = "http://localhost:" + server.address().port; @@ -26,33 +26,32 @@ describe("E2E proxy test with rewrite rules", function () { open: false, rewriteRules: [ { - match: /Forms/g, + match: /BrowserSync/g, fn: function () { - return "Shane's forms"; + return "BROWSERSYNC"; } } ] }; - instance = browserSync.init([], config, function (err, bs) { + bs = browserSync.init([], config, function (err, bs) { options = bs.options; done(); }).instance; }); after(function () { - instance.cleanup(); + bs.cleanup(); server.close(); }); - it("serves files with HTML rewritten", function (done) { - - request(instance.server) + it("can add rules on the fly", function (done) { + request(bs.server) .get("/index.html") .set("accept", "text/html") .expect(200) .end(function (err, res) { - assert.include(res.text, "Shane's forms"); + assert.include(res.text, "BROWSERSYNC"); done(); }); }); diff --git a/test/specs/e2e/proxy/e2e.proxy.rewrite.rules.remove.js b/test/specs/e2e/proxy/e2e.proxy.rewrite.rules.remove.js new file mode 100644 index 000000000..16ab206c7 --- /dev/null +++ b/test/specs/e2e/proxy/e2e.proxy.rewrite.rules.remove.js @@ -0,0 +1,70 @@ +"use strict"; + +var browserSync = require("../../../../index"); + +var connect = require("connect"); +var serveStatic = require("serve-static"); +var request = require("supertest"); +var assert = require("chai").assert; + +describe("E2E proxy test with adding and removing rewrite rules dynamically", function () { + + var bs, server, options; + + before(function (done) { + + browserSync.reset(); + + var app = connect(); + app.use(serveStatic("test/fixtures")); + server = app.listen(); + var proxytarget = "http://localhost:" + server.address().port; + + var config = { + proxy: proxytarget, + logLevel: "silent", + open: false, + online: false + }; + + bs = browserSync.init([], config, function (err, bs) { + options = bs.options; + done(); + }).instance; + }); + + after(function () { + bs.cleanup(); + server.close(); + }); + + it("can add rules on the fly", function (done) { + + bs.addRewriteRule({ + match: "BrowserSync", + replace: "BROWSERSYNC", + id: "myrule" + }); + + request(bs.server) + .get("/index.html") + .set("accept", "text/html") + .expect(200) + .end(function (err, res) { + + assert.include(res.text, "BROWSERSYNC"); + + bs.removeRewriteRule("myrule"); + + request(bs.server) + .get("/index.html") + .set("accept", "text/html") + .expect(200) + .end(function (err, res) { + assert.include(res.text, "BrowserSync"); + assert.notInclude(res.text, "BROWSERSYNC"); + done(); + }); + }); + }); +}); diff --git a/test/specs/e2e/server/e2e.server.rewrite.rules.add.js b/test/specs/e2e/server/e2e.server.rewrite.rules.add.js new file mode 100644 index 000000000..d6f0659cf --- /dev/null +++ b/test/specs/e2e/server/e2e.server.rewrite.rules.add.js @@ -0,0 +1,49 @@ +"use strict"; + +var browserSync = require("../../../../index"); + +var request = require("supertest"); +var assert = require("chai").assert; + +describe("E2E server test with rewrite rules added on the fly", function () { + + var bs; + + before(function (done) { + + browserSync.reset(); + + var config = { + server: { + baseDir: "test/fixtures" + }, + logLevel: "silent", + open: false + }; + + bs = browserSync.init(config, done).instance; + }); + + after(function () { + bs.cleanup(); + }); + + it("serves files with HTML rewritten", function (done) { + + bs.addRewriteRule({ + match: /Forms/g, + fn: function () { + return "Shane's forms"; + } + }); + + request(bs.server) + .get("/index.html") + .set("accept", "text/html") + .expect(200) + .end(function (err, res) { + assert.include(res.text, "Shane's forms"); + done(); + }); + }); +}); diff --git a/test/specs/e2e/server/e2e.server.rewrite.rules.remove.js b/test/specs/e2e/server/e2e.server.rewrite.rules.remove.js new file mode 100644 index 000000000..0d15b7cd9 --- /dev/null +++ b/test/specs/e2e/server/e2e.server.rewrite.rules.remove.js @@ -0,0 +1,61 @@ +"use strict"; + +var browserSync = require("../../../../index"); + +var request = require("supertest"); +var assert = require("chai").assert; + +describe("E2E server test with rewrite rules removed on the fly", function () { + + var bs; + + before(function (done) { + + browserSync.reset(); + + var config = { + server: { + baseDir: "test/fixtures" + }, + logLevel: "silent", + open: false + }; + + bs = browserSync.init(config, done).instance; + }); + + after(function () { + bs.cleanup(); + }); + + it("serves files with HTML rewritten", function (done) { + + bs.addRewriteRule({ + id: "myrule", + match: /Forms/g, + fn: function () { + return "Shane's forms"; + } + }); + + request(bs.server) + .get("/index.html") + .set("accept", "text/html") + .expect(200) + .end(function (err, res) { + assert.include(res.text, "Shane's forms"); + + bs.removeRewriteRule("myrule"); + + request(bs.server) + .get("/index.html") + .set("accept", "text/html") + .expect(200) + .end(function (err, res) { + assert.notInclude(res.text, "Shane's forms"); + done(); + }); + + }); + }); +});