Selaa lähdekoodia

Add in /clients and /client/:client_id status end points

James Allen 12 vuotta sitten
vanhempi
sitoutus
0b18edeff3

+ 37 - 0
services/real-time/app/coffee/HttpController.coffee

@@ -0,0 +1,37 @@
+Utils = require "./Utils"
+async = require "async"
+
+module.exports = HttpController =
+	# The code in this controller is hard to unit test because of a lot of 
+	# dependencies on internal socket.io methods. It is not critical to the running
+	# of ShareLaTeX, and is only used for getting stats about connected clients,
+	# and for checking internal state in acceptance tests. The acceptances tests
+	# should provide appropriate coverage.
+	_getConnectedClientView: (ioClient, callback = (error, client) ->) ->
+		client_id = ioClient.id
+		Utils.getClientAttributes ioClient, [
+			"project_id", "user_id", "first_name", "last_name", "email", "connected_time"
+		], (error, {project_id, user_id, first_name, last_name, email, connected_time}) ->
+			return callback(error) if error?
+			client = {client_id, project_id, user_id, first_name, last_name, email, connected_time}
+			client.rooms = []
+			for name, joined of ioClient.manager.roomClients[client_id]
+				if joined and name != ""
+					client.rooms.push name.replace(/^\//, "") # Remove leading /
+			callback(null, client)
+
+	getConnectedClients: (req, res, next) ->
+		io = req.app.get("io")
+		ioClients = io.sockets.clients()
+		async.map ioClients, HttpController._getConnectedClientView, (error, clients) ->
+			return next(error) if error?
+			res.json clients
+			
+	getConnectedClient: (req, res, next) ->
+		{client_id} = req.params
+		io = req.app.get("io")
+		ioClient = io.sockets.socket(client_id)
+		HttpController._getConnectedClientView ioClient, (error, client) ->
+			return next(error) if error?
+			res.json client
+		

+ 6 - 1
services/real-time/app/coffee/Router.coffee

@@ -1,6 +1,7 @@
 Metrics = require "metrics-sharelatex"
 logger = require "logger-sharelatex"
 WebsocketController = require "./WebsocketController"
+HttpController = require "./HttpController"
 
 module.exports = Router =
 	# We don't want to send raw errors back to the client, in case they
@@ -71,4 +72,8 @@ module.exports = Router =
 						# Don't return raw error to prevent leaking server side info
 						return callback {message: "Something went wrong"}
 					else
-						callback(null, args...)
+						callback(null, args...)
+		
+		app.set("io", io)
+		app.get "/clients", HttpController.getConnectedClients
+		app.get "/clients/:client_id", HttpController.getConnectedClient

+ 14 - 0
services/real-time/app/coffee/Utils.coffee

@@ -0,0 +1,14 @@
+async = require "async"
+
+module.exports = Utils =
+	getClientAttributes: (client, keys, callback = (error, attributes) ->) ->
+		attributes = {}
+		jobs = keys.map (key) ->
+			(callback) ->
+				client.get key, (error, value) ->
+					return callback(error) if error?
+					attributes[key] = value
+					callback()
+		async.series jobs, (error) ->
+			return callback(error) if error?
+			callback null, attributes

+ 3 - 2
services/real-time/app/coffee/WebsocketController.coffee

@@ -19,6 +19,8 @@ module.exports = WebsocketController =
 				err = new Error("not authorized")
 				logger.error {err, project_id, user_id, client_id: client.id}, "user is not authorized to join project"
 				return callback(err)
+				
+			client.join project_id
 
 			client.set("privilege_level", privilegeLevel)
 			client.set("user_id", user_id)
@@ -67,5 +69,4 @@ module.exports = WebsocketController =
 	_getClientData: (client, callback = (error, data) ->) ->
 		client.get "user_id", (error, user_id) ->
 			client.get "project_id", (error, project_id) ->
-				callback null, {client_id: client.id, project_id, user_id}
-			
+				callback null, {client_id: client.id, project_id, user_id}

+ 1 - 0
services/real-time/package.json

@@ -8,6 +8,7 @@
     "url": "https://github.com/sharelatex/real-time-sharelatex.git"
   },
   "dependencies": {
+    "async": "^0.9.0",
     "connect-redis": "^2.1.0",
     "express": "^4.10.1",
     "express-session": "^1.9.1",

+ 10 - 0
services/real-time/test/acceptance/coffee/JoinProjectTests.coffee

@@ -41,6 +41,11 @@ describe "joinProject", ->
 		it "should return the protocolVersion", ->
 			@protocolVersion.should.equal 2
 			
+		it "should have joined the project room", (done) ->
+			RealTimeClient.getConnectedClient @client.socket.sessionid, (error, client) =>
+				expect(@project_id in client.rooms).to.equal true
+				done()
+			
 	describe "when not authorized", ->
 		before (done) ->
 			FixturesManager.setUpProject {
@@ -60,3 +65,8 @@ describe "joinProject", ->
 		it "should return an error", ->
 			# We don't return specific errors
 			@error.message.should.equal "Something went wrong"
+			
+		it "should not have joined the project room", (done) ->
+			RealTimeClient.getConnectedClient @client.socket.sessionid, (error, client) =>
+				expect(@project_id in client.rooms).to.equal false
+				done()

+ 33 - 3
services/real-time/test/acceptance/coffee/SessionTests.coffee

@@ -5,7 +5,7 @@ RealTimeClient = require "./helpers/RealTimeClient"
 
 describe "Session", ->
 	describe "with an established session", ->
-		beforeEach (done) ->
+		before (done) ->
 			@user_id = "mock-user-id"
 			RealTimeClient.setSession {
 				user: { _id: @user_id }
@@ -22,9 +22,19 @@ describe "Session", ->
 				expect(disconnected).to.equal false
 				done()
 			, 500
+			
+		it "should appear in the list of connected clients", (done) ->
+			RealTimeClient.getConnectedClients (error, clients) =>
+				included = false
+				for client in clients
+					if client.client_id == @client.socket.sessionid
+						included = true
+						break
+				expect(included).to.equal true
+				done()
 		
 	describe "without an established session", ->
-		beforeEach (done) ->
+		before (done) ->
 			RealTimeClient.unsetSession (error) =>
 				throw error if error?
 				@client = RealTimeClient.connect()
@@ -34,8 +44,18 @@ describe "Session", ->
 			@client.on "disconnect", () ->
 				done()
 				
+		it "not should appear in the list of connected clients", (done) ->
+			RealTimeClient.getConnectedClients (error, clients) =>
+				included = false
+				for client in clients
+					if client.client_id == @client.socket.sessionid
+						included = true
+						break
+				expect(included).to.equal false
+				done()
+				
 	describe "without a valid user set on the session", ->
-		beforeEach (done) ->
+		before (done) ->
 			RealTimeClient.setSession {
 				foo: "bar"
 			}, (error) =>
@@ -45,4 +65,14 @@ describe "Session", ->
 				
 		it "should get disconnected", (done) ->
 			@client.on "disconnect", () ->
+				done()
+				
+		it "not should appear in the list of connected clients", (done) ->
+			RealTimeClient.getConnectedClients (error, clients) =>
+				included = false
+				for client in clients
+					if client.client_id == @client.socket.sessionid
+						included = true
+						break
+				expect(included).to.equal false
 				done()

+ 15 - 0
services/real-time/test/acceptance/coffee/helpers/RealTimeClient.coffee

@@ -1,6 +1,7 @@
 XMLHttpRequest = require("../../libs/XMLHttpRequest").XMLHttpRequest
 io = require("socket.io-client")
 
+request = require "request"
 Settings = require "settings-sharelatex"
 redis = require "redis-sharelatex"
 rclient = redis.createClient(Settings.redis.web)
@@ -37,4 +38,18 @@ module.exports = Client =
 	connect: (cookie) ->
 		client = io.connect("http://localhost:3026", 'force new connection': true)
 		return client
+		
+	getConnectedClients: (callback = (error, clients) ->) ->
+		request.get {
+			url: "http://localhost:3026/clients"
+			json: true
+		}, (error, response, data) ->
+			callback error, data
+		
+	getConnectedClient: (client_id, callback = (error, clients) ->) ->
+		request.get {
+			url: "http://localhost:3026/clients/#{client_id}"
+			json: true
+		}, (error, response, data) ->
+			callback error, data
 

+ 3 - 0
services/real-time/test/unit/coffee/WebsocketControllerTests.coffee

@@ -51,6 +51,9 @@ describe 'WebsocketController', ->
 					.calledWith(@project_id, @user._id)
 					.should.equal true
 					
+			it "should join the project room", ->
+				@client.join.calledWith(@project_id).should.equal true
+					
 			it "should set the privilege level on the client", ->
 				@client.set.calledWith("privilege_level", @privilegeLevel).should.equal true