diff --git a/src/server/_real_time_server_test.js b/src/server/_real_time_server_test.js index 0df87fab8..17088d56b 100644 --- a/src/server/_real_time_server_test.js +++ b/src/server/_real_time_server_test.js @@ -35,22 +35,42 @@ httpServer.start(PORT, done); }); - afterEach(function(done) { - waitForConnectionCount(0, "afterEach() requires all sockets to be closed", function() { - httpServer.stop(done); - }); + afterEach(function(done) { + realTimeServer.disconnectAll(function () { + httpServer.stop(done); + }); }); - it("counts the number of connections", function(done) { - assert.equal(realTimeServer.numberOfActiveConnections(), 0, "before opening connection"); + it("emits event when all sockets have disconnected", function (done) { + var isAfterDisconnect = false; + realTimeServer.on('disconnect_all', function () { + assert.equal(isAfterDisconnect, true, "after closing connection"); + done(); + }); - var socket = createSocket(); - waitForConnectionCount(1, "after opening connection", function() { - assert.equal(realTimeServer.numberOfActiveConnections(), 1, "after opening connection"); - closeSocket(socket, function() { - waitForConnectionCount(0, "after closing connection", done); - }); - }); + var socket = createSocket(); + socket.on('connect', function () { + socket.disconnect(); + isAfterDisconnect = true; + }); + + }); + + it("counts the number of connections", function(done) { + assert.equal(realTimeServer.numberOfActiveConnections(), 0, "before opening connection"); + + var gotConnectionEvent = false; + realTimeServer.on('connection', function () { + assert.equal(realTimeServer.numberOfActiveConnections(), 1, "after opening connection"); + gotConnectionEvent = true; + }).on('disconnect', function () { + assert.equal(gotConnectionEvent, true, "expected connection event before disconnect event"); + assert.equal(realTimeServer.numberOfActiveConnections(), 0, "after closing connection"); + done(); + }); + + var socket = createSocket(); + socket.once('connect', socket.disconnect); }); it("broadcasts pointer events from one client to all others", function(done) { @@ -89,8 +109,8 @@ realTimeServer.handleClientEvent(clientEvent, EMITTER_ID); - function end() { - async.each([ receiver1, receiver2 ], closeSocket, done); + function end() { + setTimeout(done, 0); } }); @@ -111,18 +131,16 @@ client.on(ServerDrawEvent.EVENT_NAME, function(event) { replayedEvents.push(ServerDrawEvent.fromSerializableObject(event)); if (replayedEvents.length === 3) { - try { - // if we don't get the events, the test will time out - assert.deepEqual(replayedEvents, [ - event1.toServerEvent(), - event2.toServerEvent(), - event3.toServerEvent() - ]); - } - finally { - closeSocket(client, done); - } - } + + // if we don't get the events, the test will time out + assert.deepEqual(replayedEvents, [ + event1.toServerEvent(), + event2.toServerEvent(), + event3.toServerEvent() + ]); + + setTimeout(done, 10); + } }); }); @@ -144,28 +162,10 @@ next(); } }); - }, end); + }, done); emitter.emit(clientEvent.name(), clientEvent.toSerializableObject()); - - function end() { - async.each([emitter, receiver1, receiver2], closeSocket, done); - } - } - - function waitForConnectionCount(expectedConnections, message, callback) { - var TIMEOUT = 1000; // milliseconds - var RETRY_PERIOD = 10; // milliseconds - - var retryOptions = { times: TIMEOUT / RETRY_PERIOD, interval: RETRY_PERIOD }; - async.retry(retryOptions, function(next) { - if (realTimeServer.numberOfActiveConnections() === expectedConnections) return next(); - else return next("fail"); - }, function(err) { - if (err) return assert.equal(realTimeServer.numberOfActiveConnections(), expectedConnections, message); - else setTimeout(callback, 0); - }); - } + } function createSocket() { return io("http://localhost:" + PORT); diff --git a/src/server/_server_test.js b/src/server/_server_test.js index f9bb7c40f..16e2ab1d0 100644 --- a/src/server/_server_test.js +++ b/src/server/_server_test.js @@ -65,14 +65,10 @@ receiver.on(ServerPointerEvent.EVENT_NAME, function(data) { assert.deepEqual(data, clientEvent.toServerEvent(emitter.id).toSerializableObject()); - end(); + setTimeout(done, 10); }); emitter.emit(clientEvent.name(), clientEvent.toSerializableObject()); - - function end() { - async.each([ emitter, receiver ], closeSocket, done); - } }); function createSocket() { diff --git a/src/server/real_time_server.js b/src/server/real_time_server.js index fb33068c8..41e363807 100644 --- a/src/server/real_time_server.js +++ b/src/server/real_time_server.js @@ -2,12 +2,14 @@ (function() { "use strict"; - var io = require('socket.io'); + var io = require('socket.io'); + var async = require('async'); var ClientPointerEvent = require("../shared/client_pointer_event.js"); var ClientRemovePointerEvent = require("../shared/client_remove_pointer_event.js"); var ClientDrawEvent = require("../shared/client_draw_event.js"); var ClientClearScreenEvent = require("../shared/client_clear_screen_event.js"); var EventRepository = require("./event_repository.js"); + var EventEmitter = require("../client/network/vendor/emitter-1.2.1.js"); // Consider Jay Bazuzi's suggestions from E494 comments (direct connection from client to server when testing) // http://disq.us/p/1gobws6 http://www.letscodejavascript.com/v3/comments/live/494 @@ -16,10 +18,12 @@ this._socketIoConnections = {}; }; + RealTimeServer.prototype = Object.create(EventEmitter.prototype); + RealTimeServer.prototype.start = function(httpServer) { this._ioServer = io(httpServer); - trackSocketIoConnections(this._socketIoConnections, this._ioServer); + trackSocketIoConnections(this._socketIoConnections, this._ioServer, this); handleSocketIoEvents(this, this._ioServer); }; @@ -30,15 +34,33 @@ RealTimeServer.prototype.numberOfActiveConnections = function() { return Object.keys(this._socketIoConnections).length; - }; + }; + + RealTimeServer.prototype.disconnectAll = function (callback) { + if (this.numberOfActiveConnections() === 0) { + if (callback) callback(); + } + else { + if (callback) + this.once('disconnect_all', callback); - function trackSocketIoConnections(connections, ioServer) { + async.each(this._socketIoConnections, function (socket) { socket.disconnect(); }); + } + }; + + function trackSocketIoConnections(connections, ioServer, self) { // Inspired by isaacs https://github.com/isaacs/server-destroy/commit/71f1a988e1b05c395e879b18b850713d1774fa92 ioServer.on("connection", function(socket) { var key = socket.id; connections[key] = socket; - socket.on("disconnect", function() { + self.emit('connection', key); + + socket.on("disconnect", function () { delete connections[key]; + self.emit('disconnect', key); + + if (self.numberOfActiveConnections() === 0) + self.emit('disconnect_all'); }); }); } diff --git a/src/server/server.js b/src/server/server.js index d5dddca7e..cce35d8e3 100644 --- a/src/server/server.js +++ b/src/server/server.js @@ -13,14 +13,17 @@ this._httpServer = new HttpServer(contentDir, notFoundPageToServe); this._httpServer.start(portNumber, callback); - var realTimeServer = new RealTimeServer(); - realTimeServer.start(this._httpServer.getNodeServer()); + this._realTimeServer = new RealTimeServer(); + this._realTimeServer.start(this._httpServer.getNodeServer()); }; - Server.prototype.stop = function(callback) { - if (this._httpServer === undefined) return callback(new Error("stop() called before server started")); + Server.prototype.stop = function (callback) { + var self = this; + if (self._httpServer === undefined) return callback(new Error("stop() called before server started")); - this._httpServer.stop(callback); + self._realTimeServer.disconnectAll(function () { + self._httpServer.stop(callback); + }); }; }()); \ No newline at end of file