diff --git a/packager/react-packager/src/Server/__tests__/Server-test.js b/packager/react-packager/src/Server/__tests__/Server-test.js index f55df7c28..71b7e400f 100644 --- a/packager/react-packager/src/Server/__tests__/Server-test.js +++ b/packager/react-packager/src/Server/__tests__/Server-test.js @@ -45,6 +45,7 @@ describe('processRequest', function() { var invalidatorFunc = jest.genMockFunction(); var watcherFunc = jest.genMockFunction(); var requestHandler; + var triggerFileChange; beforeEach(function() { Packager = require('../../Packager'); @@ -61,7 +62,15 @@ describe('processRequest', function() { }); }; - FileWatcher.prototype.on = watcherFunc; + + FileWatcher.prototype.on = function(eventType, callback) { + if (eventType !== 'all') { + throw new Error('Can only handle "all" event in watcher.'); + } + watcherFunc.apply(this, arguments); + triggerFileChange = callback; + return this; + }; Packager.prototype.invalidateFile = invalidatorFunc; @@ -109,17 +118,6 @@ describe('processRequest', function() { describe('file changes', function() { - var triggerFileChange; - beforeEach(function() { - FileWatcher.prototype.on = function(eventType, callback) { - if (eventType !== 'all') { - throw new Error('Can only handle "all" event in watcher.'); - } - triggerFileChange = callback; - return this; - }; - }); - pit('invalides files in package when file is updated', function() { return makeRequest( requestHandler, @@ -175,4 +173,36 @@ describe('processRequest', function() { }); }); }); + + describe('/onchange endpoint', function() { + var EventEmitter; + var req; + var res; + + beforeEach(function() { + EventEmitter = require.requireActual('events').EventEmitter; + req = new EventEmitter(); + req.url = '/onchange'; + res = { + writeHead: jest.genMockFn(), + end: jest.genMockFn() + }; + }); + + it('should hold on to request and inform on change', function() { + server.processRequest(req, res); + triggerFileChange('all', 'path/file.js', options.projectRoots[0]); + jest.runAllTimers(); + expect(res.end).toBeCalledWith(JSON.stringify({changed: true})); + }); + + it('should not inform changes on disconnected clients', function() { + server.processRequest(req, res); + req.emit('close'); + jest.runAllTimers(); + triggerFileChange('all', 'path/file.js', options.projectRoots[0]); + jest.runAllTimers(); + expect(res.end).not.toBeCalled(); + }); + }); }); diff --git a/packager/react-packager/src/Server/index.js b/packager/react-packager/src/Server/index.js index a918662e5..8982bc916 100644 --- a/packager/react-packager/src/Server/index.js +++ b/packager/react-packager/src/Server/index.js @@ -50,6 +50,7 @@ function Server(options) { this._projectRoots = opts.projectRoots; this._packages = Object.create(null); this._packager = new Packager(opts); + this._changeWatchers = []; this._fileWatcher = options.nonPersistent ? FileWatcher.createDummyWatcher() @@ -65,6 +66,7 @@ Server.prototype._onFileChange = function(type, filepath, root) { // Make sure the file watcher event runs through the system before // we rebuild the packages. setImmediate(this._rebuildPackages.bind(this, absPath)); + setImmediate(this._informChangeWatchers.bind(this)); }; Server.prototype._rebuildPackages = function() { @@ -83,6 +85,20 @@ Server.prototype._rebuildPackages = function() { }); }; +Server.prototype._informChangeWatchers = function() { + var watchers = this._changeWatchers; + var headers = { + 'Content-Type': 'application/json; charset=UTF-8', + }; + + watchers.forEach(function(w) { + w.res.writeHead(205, headers); + w.res.end(JSON.stringify({ changed: true })); + }); + + this._changeWatchers = []; +}; + Server.prototype.end = function() { q.all([ this._fileWatcher.end(), @@ -142,6 +158,24 @@ Server.prototype._processDebugRequest = function(reqUrl, res) { } }; +Server.prototype._processOnChangeRequest = function(req, res) { + var watchers = this._changeWatchers; + + watchers.push({ + req: req, + res: res, + }); + + req.on('close', function() { + for (var i = 0; i < watchers.length; i++) { + if (watchers[i] && watchers[i].req === req) { + watchers.splice(i, 1); + break; + } + } + }); +}; + Server.prototype.processRequest = function(req, res, next) { var urlObj = url.parse(req.url, true); var pathname = urlObj.pathname; @@ -154,6 +188,9 @@ Server.prototype.processRequest = function(req, res, next) { } else if (pathname.match(/^\/debug/)) { this._processDebugRequest(req.url, res); return; + } else if (pathname.match(/^\/onchange\/?$/)) { + this._processOnChangeRequest(req, res); + return; } else { next(); return;