mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Clean up unused real-time code in web
This commit is contained in:
parent
3aad31069c
commit
d7afb4e513
19 changed files with 106 additions and 1144 deletions
|
@ -6,10 +6,8 @@ logger.logger.serializers.project = require("./app/js/infrastructure/LoggerSeria
|
|||
metrics = require("metrics-sharelatex")
|
||||
metrics.initialize("web")
|
||||
Server = require("./app/js/infrastructure/Server")
|
||||
BackgroundTasks = require("./app/js/infrastructure/BackgroundTasks")
|
||||
Errors = require "./app/js/errors"
|
||||
|
||||
|
||||
argv = require("optimist")
|
||||
.options("user", {alias : "u", description : "Run the server with permissions of the specified user"})
|
||||
.options("group", {alias : "g", description : "Run the server with permissions of the specified group"})
|
||||
|
@ -32,8 +30,6 @@ if Settings.catchErrors
|
|||
process.on "uncaughtException", (error) ->
|
||||
logger.error err: error, "uncaughtException"
|
||||
|
||||
BackgroundTasks.run()
|
||||
|
||||
port = Settings.port or Settings.internal?.web?.port or 3000
|
||||
host = Settings.internal.web.host or "localhost"
|
||||
Server.server.listen port, host, ->
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
ConnectedUsersManager = require("./ConnectedUsersManager")
|
||||
logger = require("logger-sharelatex")
|
||||
|
||||
module.exports =
|
||||
|
||||
getConnectedUsers: (req, res)->
|
||||
project_id = req.params.Project_id
|
||||
ConnectedUsersManager.getConnectedUsers project_id, (err, users)->
|
||||
if err?
|
||||
logger.err err:err, project_id:project_id, "problem getting connected users"
|
||||
return res.send 500
|
||||
res.send(users)
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
_ = require("underscore")
|
||||
async = require("async")
|
||||
Settings = require('settings-sharelatex')
|
||||
logger = require("logger-sharelatex")
|
||||
redis = require("redis-sharelatex")
|
||||
rclient = redis.createClient(Settings.redis.web)
|
||||
|
||||
|
||||
ONE_HOUR_IN_S = 60 * 60
|
||||
ONE_DAY_IN_S = ONE_HOUR_IN_S * 24
|
||||
FOUR_DAYS_IN_S = ONE_DAY_IN_S * 4
|
||||
|
||||
USER_TIMEOUT_IN_S = ONE_HOUR_IN_S / 4
|
||||
|
||||
buildProjectSetKey = (project_id)-> return "clients_in_project:#{project_id}"
|
||||
buildUserKey = (project_id, client_id)-> return "connected_user:#{project_id}:#{client_id}"
|
||||
|
||||
|
||||
module.exports =
|
||||
|
||||
# Use the same method for when a user connects, and when a user sends a cursor
|
||||
# update. This way we don't care if the connected_user key has expired when
|
||||
# we receive a cursor update.
|
||||
updateUserPosition: (project_id, client_id, user, cursorData, callback = (err)->)->
|
||||
logger.log project_id:project_id, client_id:client_id, "marking user as connected"
|
||||
|
||||
multi = rclient.multi()
|
||||
|
||||
multi.sadd buildProjectSetKey(project_id), client_id
|
||||
multi.expire buildProjectSetKey(project_id), FOUR_DAYS_IN_S
|
||||
|
||||
multi.hset buildUserKey(project_id, client_id), "last_updated_at", Date.now()
|
||||
multi.hset buildUserKey(project_id, client_id), "user_id", user._id
|
||||
multi.hset buildUserKey(project_id, client_id), "first_name", user.first_name
|
||||
multi.hset buildUserKey(project_id, client_id), "last_name", user.last_name
|
||||
multi.hset buildUserKey(project_id, client_id), "email", user.email
|
||||
|
||||
if cursorData?
|
||||
multi.hset buildUserKey(project_id, client_id), "cursorData", JSON.stringify(cursorData)
|
||||
multi.expire buildUserKey(project_id, client_id), USER_TIMEOUT_IN_S
|
||||
|
||||
multi.exec (err)->
|
||||
if err?
|
||||
logger.err err:err, project_id:project_id, client_id:client_id, "problem marking user as connected"
|
||||
callback(err)
|
||||
|
||||
markUserAsDisconnected: (project_id, client_id, callback)->
|
||||
logger.log project_id:project_id, client_id:client_id, "marking user as disconnected"
|
||||
multi = rclient.multi()
|
||||
multi.srem buildProjectSetKey(project_id), client_id
|
||||
multi.expire buildProjectSetKey(project_id), FOUR_DAYS_IN_S
|
||||
multi.del buildUserKey(project_id, client_id)
|
||||
multi.exec callback
|
||||
|
||||
|
||||
_getConnectedUser: (project_id, client_id, callback)->
|
||||
rclient.hgetall buildUserKey(project_id, client_id), (err, result)->
|
||||
if !result?
|
||||
result =
|
||||
connected : false
|
||||
client_id:client_id
|
||||
else
|
||||
result.connected = true
|
||||
result.client_id = client_id
|
||||
if result.cursorData?
|
||||
result.cursorData = JSON.parse(result.cursorData)
|
||||
callback err, result
|
||||
|
||||
getConnectedUsers: (project_id, callback)->
|
||||
self = @
|
||||
rclient.smembers buildProjectSetKey(project_id), (err, results)->
|
||||
jobs = results.map (client_id)->
|
||||
(cb)->
|
||||
self._getConnectedUser(project_id, client_id, cb)
|
||||
async.series jobs, (err, users)->
|
||||
users = _.filter users, (user)->
|
||||
user.connected
|
||||
callback err, users
|
||||
|
|
@ -6,17 +6,13 @@ ProjectEntityHandler = require('../Project/ProjectEntityHandler')
|
|||
ProjectOptionsHandler = require('../Project/ProjectOptionsHandler')
|
||||
ProjectDetailsHandler = require('../Project/ProjectDetailsHandler')
|
||||
ProjectDeleter = require("../Project/ProjectDeleter")
|
||||
ProjectGetter = require('../Project/ProjectGetter')
|
||||
UserGetter = require('../User/UserGetter')
|
||||
CollaboratorsHandler = require("../Collaborators/CollaboratorsHandler")
|
||||
DocumentUpdaterHandler = require('../DocumentUpdater/DocumentUpdaterHandler')
|
||||
LimitationsManager = require("../Subscription/LimitationsManager")
|
||||
AuthorizationManager = require("../Security/AuthorizationManager")
|
||||
EditorRealTimeController = require("./EditorRealTimeController")
|
||||
TrackChangesManager = require("../TrackChanges/TrackChangesManager")
|
||||
Settings = require('settings-sharelatex')
|
||||
async = require('async')
|
||||
ConnectedUsersManager = require("../ConnectedUsers/ConnectedUsersManager")
|
||||
LockManager = require("../../infrastructure/LockManager")
|
||||
_ = require('underscore')
|
||||
redis = require("redis-sharelatex")
|
||||
|
@ -24,144 +20,6 @@ rclientPub = redis.createClient(Settings.redis.web)
|
|||
rclientSub = redis.createClient(Settings.redis.web)
|
||||
|
||||
module.exports = EditorController =
|
||||
protocolVersion: 2
|
||||
|
||||
reportError: (client, clientError, callback = () ->) ->
|
||||
client.get "project_id", (error, project_id) ->
|
||||
client.get "user_id", (error, user_id) ->
|
||||
logger.error err: clientError, project_id: project_id, user_id: user_id, "client error"
|
||||
callback()
|
||||
|
||||
joinProject: (client, user, project_id, callback) ->
|
||||
logger.log user_id:user._id, project_id:project_id, "user joining project"
|
||||
Metrics.inc "editor.join-project"
|
||||
EditorController.buildJoinProjectView project_id, user._id, (error, project, privilegeLevel, protocolVersion) ->
|
||||
return callback(error) if error?
|
||||
if !privilegeLevel
|
||||
callback new Error("Not authorized")
|
||||
else
|
||||
client.join(project_id)
|
||||
client.set("project_id", project_id)
|
||||
client.set("owner_id", project.owner._id)
|
||||
client.set("user_id", user._id)
|
||||
client.set("first_name", user.first_name)
|
||||
client.set("last_name", user.last_name)
|
||||
client.set("email", user.email)
|
||||
client.set("connected_time", new Date())
|
||||
client.set("signup_date", user.signUpDate)
|
||||
client.set("login_count", user.loginCount)
|
||||
AuthorizationManager.setPrivilegeLevelOnClient client, privilegeLevel
|
||||
|
||||
callback null, project, privilegeLevel, EditorController.protocolVersion
|
||||
|
||||
# can be done after the connection has happened
|
||||
ConnectedUsersManager.updateUserPosition project_id, client.id, user, null, ->
|
||||
|
||||
# Only show the 'renamed or deleted' message once
|
||||
if project.deletedByExternalDataSource
|
||||
ProjectDeleter.unmarkAsDeletedByExternalSource project_id
|
||||
|
||||
buildJoinProjectView: (project_id, user_id, callback = (error, project, privilegeLevel) ->) ->
|
||||
ProjectGetter.getProjectWithoutDocLines project_id, (error, project) ->
|
||||
return callback(error) if error?
|
||||
return callback(new Error("not found")) if !project?
|
||||
ProjectGetter.populateProjectWithUsers project, (error, project) ->
|
||||
return callback(error) if error?
|
||||
UserGetter.getUser user_id, { isAdmin: true }, (error, user) ->
|
||||
return callback(error) if error?
|
||||
AuthorizationManager.getPrivilegeLevelForProject project, user, (error, canAccess, privilegeLevel) ->
|
||||
return callback(error) if error?
|
||||
if !canAccess
|
||||
callback null, null, false
|
||||
else
|
||||
callback(null,
|
||||
ProjectEditorHandler.buildProjectModelView(project),
|
||||
privilegeLevel
|
||||
)
|
||||
|
||||
leaveProject: (client, user) ->
|
||||
self = @
|
||||
client.get "project_id", (error, project_id) ->
|
||||
return if error? or !project_id?
|
||||
EditorRealTimeController.emitToRoom(project_id, "clientTracking.clientDisconnected", client.id)
|
||||
ConnectedUsersManager.markUserAsDisconnected project_id, client.id, ->
|
||||
logger.log user_id:user._id, project_id:project_id, "user leaving project"
|
||||
self.flushProjectIfEmpty(project_id)
|
||||
|
||||
joinDoc: (client, project_id, doc_id, fromVersion, callback = (error, docLines, version) ->) ->
|
||||
# fromVersion is optional
|
||||
if typeof fromVersion == "function"
|
||||
callback = fromVersion
|
||||
fromVersion = -1
|
||||
|
||||
client.get "user_id", (error, user_id) ->
|
||||
logger.log user_id: user_id, project_id: project_id, doc_id: doc_id, "user joining doc"
|
||||
Metrics.inc "editor.join-doc"
|
||||
client.join doc_id
|
||||
DocumentUpdaterHandler.getDocument project_id, doc_id, fromVersion, (err, docLines, version, ops)->
|
||||
# Encode any binary bits of data so it can go via WebSockets
|
||||
# See http://ecmanaut.blogspot.co.uk/2006/07/encoding-decoding-utf8-in-javascript.html
|
||||
if docLines?
|
||||
docLines = for line in docLines
|
||||
if line.text?
|
||||
try
|
||||
line.text = unescape(encodeURIComponent(line.text))
|
||||
catch err
|
||||
logger.err err:err, project_id:project_id, doc_id:doc_id, fromVersion:fromVersion, line:line, "error encoding line.text uri component"
|
||||
else
|
||||
try
|
||||
line = unescape(encodeURIComponent(line))
|
||||
catch err
|
||||
logger.err err:err, project_id:project_id, doc_id:doc_id, fromVersion:fromVersion, line:line, "error encoding line uri component"
|
||||
line
|
||||
callback(err, docLines, version, ops)
|
||||
|
||||
leaveDoc: (client, project_id, doc_id, callback = (error) ->) ->
|
||||
client.get "user_id", (error, user_id) ->
|
||||
logger.log user_id: user_id, project_id: project_id, doc_id: doc_id, "user leaving doc"
|
||||
Metrics.inc "editor.leave-doc"
|
||||
client.leave doc_id
|
||||
callback()
|
||||
|
||||
flushProjectIfEmpty: (project_id, callback = ->)->
|
||||
setTimeout (->
|
||||
io = require('../../infrastructure/Server').io
|
||||
peopleStillInProject = io.sockets.clients(project_id).length
|
||||
logger.log project_id: project_id, connectedCount: peopleStillInProject, "flushing if empty"
|
||||
if peopleStillInProject == 0
|
||||
DocumentUpdaterHandler.flushProjectToMongoAndDelete(project_id)
|
||||
TrackChangesManager.flushProject(project_id)
|
||||
callback()
|
||||
), 500
|
||||
|
||||
updateClientPosition: (client, cursorData, callback = (error) ->) ->
|
||||
async.parallel {
|
||||
project_id: (cb)-> client.get "project_id", cb
|
||||
first_name: (cb)-> client.get "first_name", cb
|
||||
last_name: (cb)-> client.get "last_name", cb
|
||||
email: (cb)-> client.get "email", cb
|
||||
user_id: (cb)-> client.get "user_id", cb
|
||||
}, (err, results)->
|
||||
{first_name, last_name, user_id, email, project_id} = results
|
||||
cursorData.id = client.id
|
||||
cursorData.user_id = user_id if user_id?
|
||||
cursorData.email = email if email?
|
||||
if first_name? and last_name?
|
||||
cursorData.name = first_name + " " + last_name
|
||||
ConnectedUsersManager.updateUserPosition(project_id, client.id, {
|
||||
first_name: first_name,
|
||||
last_name: last_name,
|
||||
email: email,
|
||||
user_id: user_id
|
||||
}, {
|
||||
row: cursorData.row,
|
||||
column: cursorData.column,
|
||||
doc_id: cursorData.doc_id
|
||||
}, ->)
|
||||
else
|
||||
cursorData.name = "Anonymous"
|
||||
EditorRealTimeController.emitToRoom(project_id, "clientTracking.clientUpdated", cursorData)
|
||||
|
||||
addUserToProject: (project_id, email, privileges, callback = (error, collaborator_added)->)->
|
||||
email = email.toLowerCase()
|
||||
LimitationsManager.isCollaboratorLimitReached project_id, (error, limit_reached) =>
|
||||
|
|
|
@ -3,6 +3,10 @@ ProjectDeleter = require "../Project/ProjectDeleter"
|
|||
logger = require "logger-sharelatex"
|
||||
EditorRealTimeController = require "./EditorRealTimeController"
|
||||
EditorController = require "./EditorController"
|
||||
ProjectGetter = require('../Project/ProjectGetter')
|
||||
UserGetter = require('../User/UserGetter')
|
||||
AuthorizationManager = require("../Security/AuthorizationManager")
|
||||
ProjectEditorHandler = require('../Project/ProjectEditorHandler')
|
||||
Metrics = require('../../infrastructure/Metrics')
|
||||
|
||||
module.exports = EditorHttpController =
|
||||
|
@ -11,7 +15,7 @@ module.exports = EditorHttpController =
|
|||
user_id = req.query.user_id
|
||||
logger.log {user_id, project_id}, "join project request"
|
||||
Metrics.inc "editor.join-project"
|
||||
EditorController.buildJoinProjectView project_id, user_id, (error, project, privilegeLevel) ->
|
||||
EditorHttpController._buildJoinProjectView project_id, user_id, (error, project, privilegeLevel) ->
|
||||
return next(error) if error?
|
||||
res.json {
|
||||
project: project
|
||||
|
@ -21,6 +25,24 @@ module.exports = EditorHttpController =
|
|||
if project?.deletedByExternalDataSource
|
||||
ProjectDeleter.unmarkAsDeletedByExternalSource project_id
|
||||
|
||||
_buildJoinProjectView: (project_id, user_id, callback = (error, project, privilegeLevel) ->) ->
|
||||
ProjectGetter.getProjectWithoutDocLines project_id, (error, project) ->
|
||||
return callback(error) if error?
|
||||
return callback(new Error("not found")) if !project?
|
||||
ProjectGetter.populateProjectWithUsers project, (error, project) ->
|
||||
return callback(error) if error?
|
||||
UserGetter.getUser user_id, { isAdmin: true }, (error, user) ->
|
||||
return callback(error) if error?
|
||||
AuthorizationManager.getPrivilegeLevelForProject project, user, (error, canAccess, privilegeLevel) ->
|
||||
return callback(error) if error?
|
||||
if !canAccess
|
||||
callback null, null, false
|
||||
else
|
||||
callback(null,
|
||||
ProjectEditorHandler.buildProjectModelView(project),
|
||||
privilegeLevel
|
||||
)
|
||||
|
||||
restoreDoc: (req, res, next) ->
|
||||
project_id = req.params.Project_id
|
||||
doc_id = req.params.doc_id
|
||||
|
|
|
@ -16,15 +16,3 @@ module.exports = EditorRealTimeController =
|
|||
emitToAll: (message, payload...) ->
|
||||
@emitToRoom "all", message, payload...
|
||||
|
||||
listenForEditorEvents: () ->
|
||||
@rclientSub.subscribe "editor-events"
|
||||
@rclientSub.on "message", @_processEditorEvent.bind(@)
|
||||
|
||||
_processEditorEvent: (channel, message) ->
|
||||
io = require('../../infrastructure/Server').io
|
||||
message = JSON.parse(message)
|
||||
if message.room_id == "all"
|
||||
io.sockets.emit(message.message, message.payload...)
|
||||
else
|
||||
io.sockets.in(message.room_id).emit(message.message, message.payload...)
|
||||
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
logger = require "logger-sharelatex"
|
||||
metrics = require('../../infrastructure/Metrics')
|
||||
Settings = require 'settings-sharelatex'
|
||||
redis = require("redis-sharelatex")
|
||||
rclient = redis.createClient(Settings.redis.web)
|
||||
DocumentUpdaterHandler = require('../DocumentUpdater/DocumentUpdaterHandler')
|
||||
EditorRealTimeController = require("./EditorRealTimeController")
|
||||
|
||||
module.exports = EditorUpdatesController =
|
||||
_applyUpdate: (client, project_id, doc_id, update, callback = (error) ->) ->
|
||||
metrics.inc "editor.doc-update", 0.3
|
||||
metrics.set "editor.active-projects", project_id, 0.3
|
||||
client.get "user_id", (error, user_id) ->
|
||||
metrics.set "editor.active-users", user_id, 0.3
|
||||
|
||||
logger.log doc_id: doc_id, project_id: project_id, client_id: update.meta?.source, version: update.v, "sending update to doc updater"
|
||||
|
||||
DocumentUpdaterHandler.queueChange project_id, doc_id, update, (error) ->
|
||||
if error?
|
||||
logger.error err:error, project_id: project_id, doc_id: doc_id, client_id: update.meta?.source, version: update.v, "document was not available for update"
|
||||
client.disconnect()
|
||||
callback(error)
|
||||
|
||||
applyOtUpdate: (client, project_id, doc_id, update) ->
|
||||
update.meta ||= {}
|
||||
update.meta.source = client.id
|
||||
client.get "user_id", (error, user_id) ->
|
||||
update.meta.user_id = user_id
|
||||
EditorUpdatesController._applyUpdate client, project_id, doc_id, update
|
||||
|
||||
listenForUpdatesFromDocumentUpdater: () ->
|
||||
rclient.subscribe "applied-ops"
|
||||
rclient.on "message", @_processMessageFromDocumentUpdater.bind(@)
|
||||
|
||||
_processMessageFromDocumentUpdater: (channel, message) ->
|
||||
message = JSON.parse message
|
||||
if message.op?
|
||||
@_applyUpdateFromDocumentUpdater(message.doc_id, message.op)
|
||||
else if message.error?
|
||||
@_processErrorFromDocumentUpdater(message.doc_id, message.error, message)
|
||||
|
||||
_applyUpdateFromDocumentUpdater: (doc_id, update) ->
|
||||
io = require('../../infrastructure/Server').io
|
||||
for client in io.sockets.clients(doc_id)
|
||||
if client.id == update.meta.source
|
||||
logger.log doc_id: doc_id, version: update.v, source: update.meta?.source, "distributing update to sender"
|
||||
client.emit "otUpdateApplied", v: update.v, doc: update.doc
|
||||
else
|
||||
logger.log doc_id: doc_id, version: update.v, source: update.meta?.source, client_id: client.id, "distributing update to collaborator"
|
||||
client.emit "otUpdateApplied", update
|
||||
|
||||
_processErrorFromDocumentUpdater: (doc_id, error, message) ->
|
||||
io = require('../../infrastructure/Server').io
|
||||
logger.error err: error, doc_id: doc_id, "error from document updater"
|
||||
for client in io.sockets.clients(doc_id)
|
||||
client.emit "otUpdateError", error, message
|
||||
client.disconnect()
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
EditorUpdatesController = require("../Features/Editor/EditorUpdatesController")
|
||||
EditorRealTimeController = require("../Features/Editor/EditorRealTimeController")
|
||||
|
||||
module.exports = BackgroundTasks =
|
||||
run: () ->
|
||||
EditorUpdatesController.listenForUpdatesFromDocumentUpdater()
|
||||
EditorRealTimeController.listenForEditorEvents()
|
|
@ -5,11 +5,8 @@ logger = require 'logger-sharelatex'
|
|||
metrics = require('./Metrics')
|
||||
crawlerLogger = require('./CrawlerLogger')
|
||||
expressLocals = require('./ExpressLocals')
|
||||
socketIoConfig = require('./SocketIoConfig')
|
||||
Router = require('../router')
|
||||
metrics.inc("startup")
|
||||
SessionSockets = require('session.socket.io')
|
||||
|
||||
|
||||
redis = require("redis-sharelatex")
|
||||
rclient = redis.createClient(Settings.redis.web)
|
||||
|
@ -119,13 +116,8 @@ app.get "/profile", (req, res) ->
|
|||
logger.info ("creating HTTP server").yellow
|
||||
server = require('http').createServer(app)
|
||||
|
||||
io = require('socket.io').listen(server)
|
||||
|
||||
sessionSockets = new SessionSockets(io, sessionStore, cookieParser, cookieKey)
|
||||
router = new Router(app, io, sessionSockets)
|
||||
socketIoConfig.configure(io)
|
||||
router = new Router(app)
|
||||
|
||||
module.exports =
|
||||
io: io
|
||||
app: app
|
||||
server: server
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
SocketIoRedisStore = require('socket.io/lib/stores/redis')
|
||||
|
||||
module.exports =
|
||||
configure: (io)->
|
||||
io.configure ->
|
||||
io.enable('browser client minification')
|
||||
io.enable('browser client etag')
|
||||
|
||||
# Fix for Safari 5 error of "Error during WebSocket handshake: location mismatch"
|
||||
# See http://answers.dotcloud.com/question/578/problem-with-websocket-over-ssl-in-safari-with
|
||||
io.set('match origin protocol', true)
|
||||
|
||||
# gzip uses a Node 0.8.x method of calling the gzip program which
|
||||
# doesn't work with 0.6.x
|
||||
#io.enable('browser client gzip')
|
||||
io.set('transports', ['websocket', 'flashsocket', 'htmlfile', 'xhr-polling', 'jsonp-polling'])
|
||||
io.set('log level', 1)
|
||||
|
||||
io.configure 'production', ->
|
||||
io.set('log level', 1)
|
|
@ -7,7 +7,6 @@ SecurityManager = require('./managers/SecurityManager')
|
|||
AuthorizationManager = require('./Features/Security/AuthorizationManager')
|
||||
EditorController = require("./Features/Editor/EditorController")
|
||||
EditorRouter = require("./Features/Editor/EditorRouter")
|
||||
EditorUpdatesController = require("./Features/Editor/EditorUpdatesController")
|
||||
Settings = require('settings-sharelatex')
|
||||
TpdsController = require('./Features/ThirdPartyDataStore/TpdsController')
|
||||
SubscriptionRouter = require './Features/Subscription/SubscriptionRouter'
|
||||
|
@ -34,7 +33,6 @@ StaticPagesRouter = require("./Features/StaticPages/StaticPagesRouter")
|
|||
ChatController = require("./Features/Chat/ChatController")
|
||||
BlogController = require("./Features/Blog/BlogController")
|
||||
WikiController = require("./Features/Wiki/WikiController")
|
||||
ConnectedUsersController = require("./Features/ConnectedUsers/ConnectedUsersController")
|
||||
DropboxRouter = require "./Features/Dropbox/DropboxRouter"
|
||||
dropboxHandler = require "./Features/Dropbox/DropboxHandler"
|
||||
Modules = require "./infrastructure/Modules"
|
||||
|
@ -50,7 +48,7 @@ httpAuth = require('express').basicAuth (user, pass)->
|
|||
return isValid
|
||||
|
||||
module.exports = class Router
|
||||
constructor: (app, io, socketSessions)->
|
||||
constructor: (app)->
|
||||
app.use(app.router)
|
||||
|
||||
app.get '/login', UserPagesController.loginPage
|
||||
|
@ -126,8 +124,6 @@ module.exports = class Router
|
|||
app.get "/project/:Project_id/doc/:doc_id/diff", SecurityManager.requestCanAccessProject, TrackChangesController.proxyToTrackChangesApi
|
||||
app.post "/project/:Project_id/doc/:doc_id/version/:version_id/restore", SecurityManager.requestCanAccessProject, TrackChangesController.proxyToTrackChangesApi
|
||||
|
||||
app.get '/project/:Project_id/connected_users', SecurityManager.requestCanAccessProject, ConnectedUsersController.getConnectedUsers
|
||||
|
||||
app.get '/Project/:Project_id/download/zip', SecurityManager.requestCanAccessProject, ProjectDownloadsController.downloadProject
|
||||
app.get '/project/download/zip', SecurityManager.requestCanAccessMultipleProjects, ProjectDownloadsController.downloadMultipleProjects
|
||||
|
||||
|
@ -226,65 +222,3 @@ module.exports = class Router
|
|||
res.send(204)
|
||||
|
||||
app.get '*', ErrorController.notFound
|
||||
|
||||
socketSessions.on 'connection', (err, client, session)->
|
||||
metrics.inc('socket-io.connection')
|
||||
# This is not ideal - we should come up with a better way of handling
|
||||
# anonymous users, but various logging lines rely on user._id
|
||||
if !session or !session.user?
|
||||
user = {_id: "anonymous-user"}
|
||||
else
|
||||
user = session.user
|
||||
|
||||
client.on 'joinProject', (data, callback) ->
|
||||
EditorController.joinProject(client, user, data.project_id, callback)
|
||||
|
||||
client.on 'disconnect', () ->
|
||||
metrics.inc ('socket-io.disconnect')
|
||||
EditorController.leaveProject client, user
|
||||
|
||||
client.on 'applyOtUpdate', (doc_id, update) ->
|
||||
AuthorizationManager.ensureClientCanEditProject client, (error, project_id) =>
|
||||
EditorUpdatesController.applyOtUpdate(client, project_id, doc_id, update)
|
||||
|
||||
client.on 'clientTracking.updatePosition', (cursorData) ->
|
||||
AuthorizationManager.ensureClientCanViewProject client, (error, project_id) =>
|
||||
EditorController.updateClientPosition(client, cursorData)
|
||||
|
||||
client.on 'leaveDoc', (doc_id, callback)->
|
||||
AuthorizationManager.ensureClientCanViewProject client, (error, project_id) =>
|
||||
EditorController.leaveDoc(client, project_id, doc_id, callback)
|
||||
|
||||
client.on 'joinDoc', (args...)->
|
||||
AuthorizationManager.ensureClientCanViewProject client, (error, project_id) =>
|
||||
EditorController.joinDoc(client, project_id, args...)
|
||||
|
||||
# The remaining can be done via HTTP
|
||||
client.on 'addUserToProject', (email, newPrivalageLevel, callback)->
|
||||
AuthorizationManager.ensureClientCanAdminProject client, (error, project_id) =>
|
||||
EditorController.addUserToProject project_id, email, newPrivalageLevel, callback
|
||||
|
||||
client.on 'removeUserFromProject', (user_id, callback)->
|
||||
AuthorizationManager.ensureClientCanAdminProject client, (error, project_id) =>
|
||||
EditorController.removeUserFromProject(project_id, user_id, callback)
|
||||
|
||||
client.on 'getUserDropboxLinkStatus', (owner_id, callback)->
|
||||
AuthorizationManager.ensureClientCanAdminProject client, (error, project_id) =>
|
||||
dropboxHandler.getUserRegistrationStatus owner_id, callback
|
||||
|
||||
# client.on 'publishProjectAsTemplate', (user_id, callback)->
|
||||
# AuthorizationManager.ensureClientCanAdminProject client, (error, project_id) =>
|
||||
# TemplatesController.publishProject user_id, project_id, callback
|
||||
#
|
||||
# client.on 'unPublishProjectAsTemplate', (user_id, callback)->
|
||||
# AuthorizationManager.ensureClientCanAdminProject client, (error, project_id) =>
|
||||
# TemplatesController.unPublishProject user_id, project_id, callback
|
||||
#
|
||||
# client.on 'updateProjectDescription', (description, callback)->
|
||||
# AuthorizationManager.ensureClientCanEditProject client, (error, project_id) =>
|
||||
# EditorController.updateProjectDescription project_id, description, callback
|
||||
#
|
||||
# client.on "getPublishedDetails", (user_id, callback)->
|
||||
# AuthorizationManager.ensureClientCanViewProject client, (error, project_id) =>
|
||||
# TemplatesController.getTemplateDetails user_id, project_id, callback
|
||||
|
||||
|
|
|
@ -75,7 +75,7 @@ block content
|
|||
.modal-footer
|
||||
button.btn.btn-info(ng-click="done()") #{translate("ok")}
|
||||
|
||||
script(src='/socket.io/socket.io.js')
|
||||
script(src='#{settings.websocketsUrl}/socket.io/socket.io.js')
|
||||
|
||||
//- We need to do .replace(/\//g, '\\/') do that '</script>' -> '<\/script>'
|
||||
//- and doesn't prematurely end the script tag.
|
||||
|
|
|
@ -12,26 +12,24 @@ define [
|
|||
@sendCursorPositionUpdate(position)
|
||||
|
||||
@$scope.$on "project:joined", () =>
|
||||
ide.$http
|
||||
.get "/project/#{@ide.$scope.project._id}/connected_users"
|
||||
.success (connectedUsers) =>
|
||||
@$scope.onlineUsers = {}
|
||||
for user in connectedUsers or []
|
||||
if user.client_id == @ide.socket.socket.sessionid
|
||||
# Don't store myself
|
||||
continue
|
||||
# Store data in the same format returned by clientTracking.clientUpdated
|
||||
@ide.socket.emit "clientTracking.getConnectedUsers", (error, connectedUsers) =>
|
||||
@$scope.onlineUsers = {}
|
||||
for user in connectedUsers or []
|
||||
if user.client_id == @ide.socket.socket.sessionid
|
||||
# Don't store myself
|
||||
continue
|
||||
# Store data in the same format returned by clientTracking.clientUpdated
|
||||
|
||||
@$scope.onlineUsers[user.client_id] = {
|
||||
id: user.client_id
|
||||
user_id: user.user_id
|
||||
email: user.email
|
||||
name: "#{user.first_name} #{user.last_name}"
|
||||
doc_id: user.cursorData?.doc_id
|
||||
row: user.cursorData?.row
|
||||
column: user.cursorData?.column
|
||||
}
|
||||
@refreshOnlineUsers()
|
||||
@$scope.onlineUsers[user.client_id] = {
|
||||
id: user.client_id
|
||||
user_id: user.user_id
|
||||
email: user.email
|
||||
name: "#{user.first_name} #{user.last_name}"
|
||||
doc_id: user.cursorData?.doc_id
|
||||
row: user.cursorData?.row
|
||||
column: user.cursorData?.column
|
||||
}
|
||||
@refreshOnlineUsers()
|
||||
|
||||
@ide.socket.on "clientTracking.clientUpdated", (client) =>
|
||||
if client.id != @ide.socket.socket.sessionid # Check it's not me!
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
should = require('chai').should()
|
||||
SandboxedModule = require('sandboxed-module')
|
||||
assert = require('assert')
|
||||
path = require('path')
|
||||
sinon = require('sinon')
|
||||
modulePath = path.join __dirname, "../../../../app/js/Features/ConnectedUsers/ConnectedUsersController"
|
||||
expect = require("chai").expect
|
||||
|
||||
describe "ConnectedUsersController", ->
|
||||
|
||||
beforeEach ->
|
||||
|
||||
@settings = {}
|
||||
@ConnectedUsersManager =
|
||||
getConnectedUsers:sinon.stub()
|
||||
@ConnectedUsersController = SandboxedModule.require modulePath, requires:
|
||||
"settings-sharelatex":@settings
|
||||
"./ConnectedUsersManager":@ConnectedUsersManager
|
||||
"logger-sharelatex":
|
||||
log:->
|
||||
err:->
|
||||
@project_id = "231312390128309"
|
||||
@req =
|
||||
params:
|
||||
project_id:@project_id
|
||||
@res = {}
|
||||
|
||||
|
||||
describe "getConnectedUsers", ->
|
||||
|
||||
beforeEach ->
|
||||
@connectedUsersData = [{user_id:"312321"}, {user_id:"3213213"}]
|
||||
|
||||
it "should get the connected user data for that project", (done)->
|
||||
@ConnectedUsersManager.getConnectedUsers.callsArgWith(1, null, @connectedUsersData)
|
||||
@res.send = (d)=>
|
||||
d.should.deep.equal @connectedUsersData
|
||||
done()
|
||||
@ConnectedUsersController.getConnectedUsers @req, @res
|
||||
|
||||
it "should send a 500 on an error", (done)->
|
||||
@ConnectedUsersManager.getConnectedUsers.callsArgWith(1, "error")
|
||||
@res.send = (code)=>
|
||||
code.should.equal 500
|
||||
done()
|
||||
@ConnectedUsersController.getConnectedUsers @req, @res
|
|
@ -1,154 +0,0 @@
|
|||
|
||||
should = require('chai').should()
|
||||
SandboxedModule = require('sandboxed-module')
|
||||
assert = require('assert')
|
||||
path = require('path')
|
||||
sinon = require('sinon')
|
||||
modulePath = path.join __dirname, "../../../../app/js/Features/ConnectedUsers/ConnectedUsersManager"
|
||||
expect = require("chai").expect
|
||||
tk = require("timekeeper")
|
||||
|
||||
|
||||
describe "ConnectedUsersManager", ->
|
||||
|
||||
beforeEach ->
|
||||
|
||||
@settings =
|
||||
redis:
|
||||
web:{}
|
||||
@rClient =
|
||||
auth:->
|
||||
setex:sinon.stub()
|
||||
sadd:sinon.stub()
|
||||
get: sinon.stub()
|
||||
srem:sinon.stub()
|
||||
del:sinon.stub()
|
||||
smembers:sinon.stub()
|
||||
expire:sinon.stub()
|
||||
hset:sinon.stub()
|
||||
hgetall:sinon.stub()
|
||||
exec:sinon.stub()
|
||||
multi: => return @rClient
|
||||
tk.freeze(new Date())
|
||||
|
||||
@ConnectedUsersManager = SandboxedModule.require modulePath, requires:
|
||||
"settings-sharelatex":@settings
|
||||
"logger-sharelatex": log:->
|
||||
"redis-sharelatex": createClient:=>
|
||||
return @rClient
|
||||
@client_id = "32132132"
|
||||
@project_id = "dskjh2u21321"
|
||||
@user = {
|
||||
_id: "user-id-123"
|
||||
first_name: "Joe"
|
||||
last_name: "Bloggs"
|
||||
email: "joe@example.com"
|
||||
}
|
||||
@cursorData = { row: 12, column: 9, doc_id: '53c3b8c85fee64000023dc6e' }
|
||||
|
||||
afterEach ->
|
||||
tk.reset()
|
||||
|
||||
describe "updateUserPosition", ->
|
||||
beforeEach ->
|
||||
@rClient.exec.callsArgWith(0)
|
||||
|
||||
it "should set a key with the date and give it a ttl", (done)->
|
||||
@ConnectedUsersManager.updateUserPosition @project_id, @client_id, @user, null, (err)=>
|
||||
@rClient.hset.calledWith("connected_user:#{@project_id}:#{@client_id}", "last_updated_at", Date.now()).should.equal true
|
||||
done()
|
||||
|
||||
it "should set a key with the user_id", (done)->
|
||||
@ConnectedUsersManager.updateUserPosition @project_id, @client_id, @user, null, (err)=>
|
||||
@rClient.hset.calledWith("connected_user:#{@project_id}:#{@client_id}", "user_id", @user._id).should.equal true
|
||||
done()
|
||||
|
||||
it "should set a key with the first_name", (done)->
|
||||
@ConnectedUsersManager.updateUserPosition @project_id, @client_id, @user, null, (err)=>
|
||||
@rClient.hset.calledWith("connected_user:#{@project_id}:#{@client_id}", "first_name", @user.first_name).should.equal true
|
||||
done()
|
||||
|
||||
it "should set a key with the last_name", (done)->
|
||||
@ConnectedUsersManager.updateUserPosition @project_id, @client_id, @user, null, (err)=>
|
||||
@rClient.hset.calledWith("connected_user:#{@project_id}:#{@client_id}", "last_name", @user.last_name).should.equal true
|
||||
done()
|
||||
|
||||
it "should set a key with the email", (done)->
|
||||
@ConnectedUsersManager.updateUserPosition @project_id, @client_id, @user, null, (err)=>
|
||||
@rClient.hset.calledWith("connected_user:#{@project_id}:#{@client_id}", "email", @user.email).should.equal true
|
||||
done()
|
||||
|
||||
it "should push the client_id on to the project list", (done)->
|
||||
@ConnectedUsersManager.updateUserPosition @project_id, @client_id, @user, null, (err)=>
|
||||
@rClient.sadd.calledWith("clients_in_project:#{@project_id}", @client_id).should.equal true
|
||||
done()
|
||||
|
||||
it "should add a ttl to the project set so it stays clean", (done)->
|
||||
@ConnectedUsersManager.updateUserPosition @project_id, @client_id, @user, null, (err)=>
|
||||
@rClient.expire.calledWith("clients_in_project:#{@project_id}", 24 * 4 * 60 * 60).should.equal true
|
||||
done()
|
||||
|
||||
it "should add a ttl to the connected user so it stays clean", (done) ->
|
||||
@ConnectedUsersManager.updateUserPosition @project_id, @client_id, @user, null, (err)=>
|
||||
@rClient.expire.calledWith("connected_user:#{@project_id}:#{@client_id}", 60 * 15).should.equal true
|
||||
done()
|
||||
|
||||
it "should set the cursor position when provided", (done)->
|
||||
@ConnectedUsersManager.updateUserPosition @project_id, @client_id, @user, @cursorData, (err)=>
|
||||
@rClient.hset.calledWith("connected_user:#{@project_id}:#{@client_id}", "cursorData", JSON.stringify(@cursorData)).should.equal true
|
||||
done()
|
||||
|
||||
describe "markUserAsDisconnected", ->
|
||||
beforeEach ->
|
||||
@rClient.exec.callsArgWith(0)
|
||||
|
||||
it "should remove the user from the set", (done)->
|
||||
@ConnectedUsersManager.markUserAsDisconnected @project_id, @client_id, (err)=>
|
||||
@rClient.srem.calledWith("clients_in_project:#{@project_id}", @client_id).should.equal true
|
||||
done()
|
||||
|
||||
it "should delete the connected_user string", (done)->
|
||||
@ConnectedUsersManager.markUserAsDisconnected @project_id, @client_id, (err)=>
|
||||
@rClient.del.calledWith("connected_user:#{@project_id}:#{@client_id}").should.equal true
|
||||
done()
|
||||
|
||||
it "should add a ttl to the connected user set so it stays clean", (done)->
|
||||
@ConnectedUsersManager.markUserAsDisconnected @project_id, @client_id, (err)=>
|
||||
@rClient.expire.calledWith("clients_in_project:#{@project_id}", 24 * 4 * 60 * 60).should.equal true
|
||||
done()
|
||||
|
||||
describe "_getConnectedUser", ->
|
||||
|
||||
it "should get the user returning connected if there is a value", (done)->
|
||||
cursorData = JSON.stringify(cursorData:{row:1})
|
||||
@rClient.hgetall.callsArgWith(1, null, {connected_at:new Date(), cursorData})
|
||||
@ConnectedUsersManager._getConnectedUser @project_id, @client_id, (err, result)=>
|
||||
result.connected.should.equal true
|
||||
result.client_id.should.equal @client_id
|
||||
done()
|
||||
|
||||
it "should get the user returning connected if there is a value", (done)->
|
||||
@rClient.hgetall.callsArgWith(1)
|
||||
@ConnectedUsersManager._getConnectedUser @project_id, @client_id, (err, result)=>
|
||||
result.connected.should.equal false
|
||||
result.client_id.should.equal @client_id
|
||||
done()
|
||||
|
||||
describe "getConnectedUsers", ->
|
||||
|
||||
beforeEach ->
|
||||
@users = ["1234", "5678", "9123"]
|
||||
@rClient.smembers.callsArgWith(1, null, @users)
|
||||
@ConnectedUsersManager._getConnectedUser = sinon.stub()
|
||||
@ConnectedUsersManager._getConnectedUser.withArgs(@project_id, @users[0]).callsArgWith(2, null, {connected:true, client_id:@users[0]})
|
||||
@ConnectedUsersManager._getConnectedUser.withArgs(@project_id, @users[1]).callsArgWith(2, null, {connected:false, client_id:@users[1]})
|
||||
@ConnectedUsersManager._getConnectedUser.withArgs(@project_id, @users[2]).callsArgWith(2, null, {connected:true, client_id:@users[2]})
|
||||
|
||||
|
||||
it "should only return the users in the list which are still in redis", (done)->
|
||||
@ConnectedUsersManager.getConnectedUsers @project_id, (err, users)=>
|
||||
users.length.should.equal 2
|
||||
users[0].should.deep.equal {client_id:@users[0], connected:true}
|
||||
users[1].should.deep.equal {client_id:@users[2], connected:true}
|
||||
done()
|
||||
|
|
@ -18,10 +18,6 @@ describe "EditorController", ->
|
|||
@doc_id = "test-doc-id"
|
||||
@source = "dropbox"
|
||||
|
||||
@projectModelView =
|
||||
_id: @project_id
|
||||
owner:{_id:"something"}
|
||||
|
||||
@user =
|
||||
_id: @user_id = "user-id"
|
||||
projects: {}
|
||||
|
@ -37,12 +33,10 @@ describe "EditorController", ->
|
|||
setSpellCheckLanguage: sinon.spy()
|
||||
@ProjectEntityHandler =
|
||||
flushProjectToThirdPartyDataStore:sinon.stub()
|
||||
@ProjectEditorHandler =
|
||||
buildProjectModelView : sinon.stub().returns(@projectModelView)
|
||||
@ProjectEditorHandler = {}
|
||||
@Project =
|
||||
findPopulatedById: sinon.stub().callsArgWith(1, null, @project)
|
||||
@LimitationsManager = {}
|
||||
@AuthorizationManager = {}
|
||||
@client = new MockClient()
|
||||
|
||||
@settings =
|
||||
|
@ -57,9 +51,6 @@ describe "EditorController", ->
|
|||
addUserToProject: sinon.stub().callsArgWith(3)
|
||||
@ProjectDeleter =
|
||||
deleteProject: sinon.stub()
|
||||
@ConnectedUsersManager =
|
||||
markUserAsDisconnected:sinon.stub()
|
||||
updateUserPosition:sinon.stub()
|
||||
@LockManager =
|
||||
getLock : sinon.stub()
|
||||
releaseLock : sinon.stub()
|
||||
|
@ -70,308 +61,21 @@ describe "EditorController", ->
|
|||
'../Project/ProjectOptionsHandler' : @ProjectOptionsHandler
|
||||
'../Project/ProjectDetailsHandler': @ProjectDetailsHandler
|
||||
'../Project/ProjectDeleter' : @ProjectDeleter
|
||||
'../Project/ProjectGetter' : @ProjectGetter = {}
|
||||
'../User/UserGetter': @UserGetter = {}
|
||||
'../Collaborators/CollaboratorsHandler': @CollaboratorsHandler
|
||||
'../DocumentUpdater/DocumentUpdaterHandler' : @DocumentUpdaterHandler
|
||||
'../Subscription/LimitationsManager' : @LimitationsManager
|
||||
'../Security/AuthorizationManager' : @AuthorizationManager
|
||||
'../../models/Project' : Project: @Project
|
||||
"settings-sharelatex":@settings
|
||||
'../Dropbox/DropboxProjectLinker':@dropboxProjectLinker
|
||||
'./EditorRealTimeController':@EditorRealTimeController = {}
|
||||
"../../infrastructure/Metrics": @Metrics = { inc: sinon.stub() }
|
||||
"../TrackChanges/TrackChangesManager": @TrackChangesManager = {}
|
||||
"../ConnectedUsers/ConnectedUsersManager":@ConnectedUsersManager
|
||||
"../../infrastructure/LockManager":@LockManager
|
||||
'redis-sharelatex':createClient:-> auth:->
|
||||
"logger-sharelatex": @logger =
|
||||
log: sinon.stub()
|
||||
err: sinon.stub()
|
||||
|
||||
describe "joinProject", ->
|
||||
beforeEach ->
|
||||
sinon.spy(@client, "set")
|
||||
sinon.spy(@client, "get")
|
||||
@AuthorizationManager.setPrivilegeLevelOnClient = sinon.stub()
|
||||
@EditorRealTimeController.emitToRoom = sinon.stub()
|
||||
@ConnectedUsersManager.updateUserPosition.callsArgWith(4)
|
||||
@ProjectDeleter.unmarkAsDeletedByExternalSource = sinon.stub()
|
||||
|
||||
describe "when authorized", ->
|
||||
beforeEach ->
|
||||
@EditorController.buildJoinProjectView = sinon.stub().callsArgWith(2, null, @projectModelView, "owner")
|
||||
@EditorController.joinProject(@client, @user, @project_id, @callback)
|
||||
|
||||
it "should set the privilege level on the client", ->
|
||||
@AuthorizationManager.setPrivilegeLevelOnClient
|
||||
.calledWith(@client, "owner")
|
||||
.should.equal.true
|
||||
|
||||
it "should add the client to the project channel", ->
|
||||
@client.join.calledWith(@project_id).should.equal true
|
||||
|
||||
it "should set the project_id of the client", ->
|
||||
@client.set.calledWith("project_id", @project_id).should.equal true
|
||||
|
||||
it "should mark the user as connected with the ConnectedUsersManager", ->
|
||||
@ConnectedUsersManager.updateUserPosition.calledWith(@project_id, @client.id, @user, null).should.equal true
|
||||
|
||||
it "should return the project model view, privilege level and protocol version", ->
|
||||
@callback.calledWith(null, @projectModelView, "owner", @EditorController.protocolVersion).should.equal true
|
||||
|
||||
describe "when not authorized", ->
|
||||
beforeEach ->
|
||||
@EditorController.buildJoinProjectView = sinon.stub().callsArgWith(2, null, null, false)
|
||||
@EditorController.joinProject(@client, @user, @project_id, @callback)
|
||||
|
||||
it "should not set the privilege level on the client", ->
|
||||
@AuthorizationManager.setPrivilegeLevelOnClient
|
||||
.called.should.equal false
|
||||
|
||||
it "should not add the client to the project channel", ->
|
||||
@client.join.called.should.equal false
|
||||
|
||||
it "should not set the project_id of the client", ->
|
||||
@client.set.called.should.equal false
|
||||
|
||||
it "should return an error", ->
|
||||
@callback.calledWith(sinon.match.truthy).should.equal true
|
||||
|
||||
describe "when the project is marked as deleted", ->
|
||||
beforeEach ->
|
||||
@projectModelView.deletedByExternalDataSource = true
|
||||
@EditorController.buildJoinProjectView = sinon.stub().callsArgWith(2, null, @projectModelView, "owner")
|
||||
@EditorController.joinProject(@client, @user, @project_id, @callback)
|
||||
|
||||
it "should remove the flag to send a user a message about the project being deleted", ->
|
||||
@ProjectDeleter.unmarkAsDeletedByExternalSource
|
||||
.calledWith(@project_id)
|
||||
.should.equal true
|
||||
|
||||
describe "buildJoinProjectView", ->
|
||||
beforeEach ->
|
||||
@ProjectGetter.getProjectWithoutDocLines = sinon.stub().callsArgWith(1, null, @project)
|
||||
@ProjectGetter.populateProjectWithUsers = sinon.stub().callsArgWith(1, null, @project)
|
||||
@UserGetter.getUser = sinon.stub().callsArgWith(2, null, @user)
|
||||
|
||||
describe "when authorized", ->
|
||||
beforeEach ->
|
||||
@AuthorizationManager.getPrivilegeLevelForProject =
|
||||
sinon.stub().callsArgWith(2, null, true, "owner")
|
||||
@EditorController.buildJoinProjectView(@project_id, @user_id, @callback)
|
||||
|
||||
it "should find the project without doc lines", ->
|
||||
@ProjectGetter.getProjectWithoutDocLines
|
||||
.calledWith(@project_id)
|
||||
.should.equal true
|
||||
|
||||
it "should populate the user references in the project", ->
|
||||
@ProjectGetter.populateProjectWithUsers
|
||||
.calledWith(@project)
|
||||
.should.equal true
|
||||
|
||||
it "should look up the user", ->
|
||||
@UserGetter.getUser
|
||||
.calledWith(@user_id, { isAdmin: true })
|
||||
.should.equal true
|
||||
|
||||
it "should check the privilege level", ->
|
||||
@AuthorizationManager.getPrivilegeLevelForProject
|
||||
.calledWith(@project, @user)
|
||||
.should.equal true
|
||||
|
||||
it "should return the project model view, privilege level and protocol version", ->
|
||||
@callback.calledWith(null, @projectModelView, "owner").should.equal true
|
||||
|
||||
describe "when not authorized", ->
|
||||
beforeEach ->
|
||||
@AuthorizationManager.getPrivilegeLevelForProject =
|
||||
sinon.stub().callsArgWith(2, null, false, null)
|
||||
@EditorController.buildJoinProjectView(@project_id, @user_id, @callback)
|
||||
|
||||
it "should return false in the callback", ->
|
||||
@callback.calledWith(null, null, false).should.equal true
|
||||
|
||||
|
||||
describe "leaveProject", ->
|
||||
beforeEach ->
|
||||
sinon.stub(@client, "set")
|
||||
sinon.stub(@client, "get").callsArgWith(1, null, @project_id)
|
||||
@EditorRealTimeController.emitToRoom = sinon.stub()
|
||||
@EditorController.flushProjectIfEmpty = sinon.stub()
|
||||
@EditorController.leaveProject @client, @user
|
||||
@ConnectedUsersManager.markUserAsDisconnected.callsArgWith(2)
|
||||
|
||||
it "should call the flush project if empty function", ->
|
||||
@EditorController.flushProjectIfEmpty
|
||||
.calledWith(@project_id)
|
||||
.should.equal true
|
||||
|
||||
it "should emit a clientDisconnect to the project room", ->
|
||||
@EditorRealTimeController.emitToRoom
|
||||
.calledWith(@project_id, "clientTracking.clientDisconnected", @client.id)
|
||||
.should.equal true
|
||||
|
||||
it "should mark the user as connected with the ConnectedUsersManager", ->
|
||||
@ConnectedUsersManager.markUserAsDisconnected.calledWith(@project_id, @client.id).should.equal true
|
||||
|
||||
|
||||
describe "joinDoc", ->
|
||||
beforeEach ->
|
||||
@client.join = sinon.stub()
|
||||
@client.set("user_id", @user_id)
|
||||
@fromVersion = 40
|
||||
@docLines = ["foo", "bar"]
|
||||
@ops = ["mock-op-1", "mock-op-2"]
|
||||
@version = 42
|
||||
@DocumentUpdaterHandler.getDocument = sinon.stub().callsArgWith(3, null, @docLines, @version, @ops)
|
||||
|
||||
describe "with a fromVersion", ->
|
||||
beforeEach ->
|
||||
@EditorController.joinDoc @client, @project_id, @doc_id, @fromVersion, @callback
|
||||
|
||||
it "should add the client to the socket.io room for the doc", ->
|
||||
@client.join.calledWith(@doc_id).should.equal true
|
||||
|
||||
it "should get the document", ->
|
||||
@DocumentUpdaterHandler.getDocument
|
||||
.calledWith(@project_id, @doc_id, @fromVersion)
|
||||
.should.equal true
|
||||
|
||||
it "should return the doclines and version and ops", ->
|
||||
@callback.calledWith(null, @docLines, @version, @ops).should.equal true
|
||||
|
||||
it "should increment the join-doc metric", ->
|
||||
@Metrics.inc.calledWith("editor.join-doc").should.equal true
|
||||
|
||||
it "should log out the request", ->
|
||||
@logger.log
|
||||
.calledWith(user_id: @user_id, project_id: @project_id, doc_id: @doc_id, "user joining doc")
|
||||
.should.equal true
|
||||
|
||||
describe "without a fromVersion", ->
|
||||
beforeEach ->
|
||||
@EditorController.joinDoc @client, @project_id, @doc_id, @callback
|
||||
|
||||
it "should get the document with fromVersion=-1", ->
|
||||
@DocumentUpdaterHandler.getDocument
|
||||
.calledWith(@project_id, @doc_id, -1)
|
||||
.should.equal true
|
||||
|
||||
it "should return the doclines and version and ops", ->
|
||||
@callback.calledWith(null, @docLines, @version, @ops).should.equal true
|
||||
|
||||
describe "leaveDoc", ->
|
||||
beforeEach ->
|
||||
@client.leave = sinon.stub()
|
||||
@client.set("user_id", @user_id)
|
||||
@EditorController.leaveDoc @client, @project_id, @doc_id, @callback
|
||||
|
||||
it "should remove the client from the socket.io room for the doc", ->
|
||||
@client.leave.calledWith(@doc_id).should.equal true
|
||||
|
||||
it "should increment the leave-doc metric", ->
|
||||
@Metrics.inc.calledWith("editor.leave-doc").should.equal true
|
||||
|
||||
it "should log out the request", ->
|
||||
@logger.log
|
||||
.calledWith(user_id: @user_id, project_id: @project_id, doc_id: @doc_id, "user leaving doc")
|
||||
|
||||
.should.equal true
|
||||
|
||||
describe "flushProjectIfEmpty", ->
|
||||
beforeEach ->
|
||||
@DocumentUpdaterHandler.flushProjectToMongoAndDelete = sinon.stub()
|
||||
@TrackChangesManager.flushProject = sinon.stub()
|
||||
|
||||
describe "when a project has no more users", ->
|
||||
it "should do the flush after the config set timeout to ensure that a reconect didn't just happen", (done)->
|
||||
@rooms[@project_id] = []
|
||||
@EditorController.flushProjectIfEmpty @project_id, =>
|
||||
@DocumentUpdaterHandler.flushProjectToMongoAndDelete.calledWith(@project_id).should.equal(true)
|
||||
@TrackChangesManager.flushProject.calledWith(@project_id).should.equal true
|
||||
done()
|
||||
|
||||
describe "when a project still has connected users", ->
|
||||
it "should not flush the project", (done)->
|
||||
@rooms[@project_id] = ["socket-id-1", "socket-id-2"]
|
||||
@EditorController.flushProjectIfEmpty @project_id, =>
|
||||
@DocumentUpdaterHandler.flushProjectToMongoAndDelete.calledWith(@project_id).should.equal(false)
|
||||
@TrackChangesManager.flushProject.calledWith(@project_id).should.equal false
|
||||
done()
|
||||
|
||||
describe "updateClientPosition", ->
|
||||
beforeEach ->
|
||||
@EditorRealTimeController.emitToRoom = sinon.stub()
|
||||
@ConnectedUsersManager.updateUserPosition.callsArgWith(4)
|
||||
@update = {
|
||||
doc_id: @doc_id = "doc-id-123"
|
||||
row: @row = 42
|
||||
column: @column = 37
|
||||
}
|
||||
|
||||
|
||||
describe "with a logged in user", ->
|
||||
beforeEach ->
|
||||
@clientParams = {
|
||||
project_id: @project_id
|
||||
first_name: @first_name = "Douglas"
|
||||
last_name: @last_name = "Adams"
|
||||
email: @email = "joe@example.com"
|
||||
user_id: @user_id = "user-id-123"
|
||||
}
|
||||
@client.get = (param, callback) => callback null, @clientParams[param]
|
||||
@EditorController.updateClientPosition @client, @update
|
||||
|
||||
@populatedCursorData =
|
||||
doc_id: @doc_id,
|
||||
id: @client.id
|
||||
name: "#{@first_name} #{@last_name}"
|
||||
row: @row
|
||||
column: @column
|
||||
email: @email
|
||||
user_id: @user_id
|
||||
|
||||
it "should send the update to the project room with the user's name", ->
|
||||
@EditorRealTimeController.emitToRoom.calledWith(@project_id, "clientTracking.clientUpdated", @populatedCursorData).should.equal true
|
||||
|
||||
it "should send the cursor data to the connected user manager", (done)->
|
||||
@ConnectedUsersManager.updateUserPosition.calledWith(@project_id, @client.id, {
|
||||
user_id: @user_id,
|
||||
email: @email,
|
||||
first_name: @first_name,
|
||||
last_name: @last_name
|
||||
}, {
|
||||
row: @row
|
||||
column: @column
|
||||
doc_id: @doc_id
|
||||
}).should.equal true
|
||||
done()
|
||||
|
||||
describe "with an anonymous user", ->
|
||||
beforeEach ->
|
||||
@clientParams = {
|
||||
project_id: @project_id
|
||||
}
|
||||
@client.get = (param, callback) => callback null, @clientParams[param]
|
||||
@EditorController.updateClientPosition @client, @update
|
||||
|
||||
it "should send the update to the project room with an anonymous name", ->
|
||||
@EditorRealTimeController.emitToRoom
|
||||
.calledWith(@project_id, "clientTracking.clientUpdated", {
|
||||
doc_id: @doc_id,
|
||||
id: @client.id
|
||||
name: "Anonymous"
|
||||
row: @row
|
||||
column: @column
|
||||
})
|
||||
.should.equal true
|
||||
|
||||
it "should not send cursor data to the connected user manager", (done)->
|
||||
@ConnectedUsersManager.updateUserPosition.called.should.equal false
|
||||
done()
|
||||
|
||||
describe "addUserToProject", ->
|
||||
beforeEach ->
|
||||
@email = "Jane.Doe@example.com"
|
||||
|
|
|
@ -8,6 +8,10 @@ describe "EditorHttpController", ->
|
|||
@EditorHttpController = SandboxedModule.require modulePath, requires:
|
||||
'../Project/ProjectEntityHandler' : @ProjectEntityHandler = {}
|
||||
'../Project/ProjectDeleter' : @ProjectDeleter = {}
|
||||
'../Project/ProjectGetter' : @ProjectGetter = {}
|
||||
'../User/UserGetter' : @UserGetter = {}
|
||||
"../Security/AuthorizationManager": @AuthorizationManager = {}
|
||||
'../Project/ProjectEditorHandler': @ProjectEditorHandler = {}
|
||||
"./EditorRealTimeController": @EditorRealTimeController = {}
|
||||
"logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() }
|
||||
"./EditorController": @EditorController = {}
|
||||
|
@ -21,6 +25,7 @@ describe "EditorHttpController", ->
|
|||
@res =
|
||||
send: sinon.stub()
|
||||
json: sinon.stub()
|
||||
@callback = sinon.stub()
|
||||
|
||||
describe "joinProject", ->
|
||||
beforeEach ->
|
||||
|
@ -31,7 +36,7 @@ describe "EditorHttpController", ->
|
|||
@projectView = {
|
||||
_id: @project_id
|
||||
}
|
||||
@EditorController.buildJoinProjectView = sinon.stub().callsArgWith(2, null, @projectView, "owner")
|
||||
@EditorHttpController._buildJoinProjectView = sinon.stub().callsArgWith(2, null, @projectView, "owner")
|
||||
@ProjectDeleter.unmarkAsDeletedByExternalSource = sinon.stub()
|
||||
|
||||
describe "successfully", ->
|
||||
|
@ -39,7 +44,7 @@ describe "EditorHttpController", ->
|
|||
@EditorHttpController.joinProject @req, @res
|
||||
|
||||
it "should get the project view", ->
|
||||
@EditorController.buildJoinProjectView
|
||||
@EditorHttpController._buildJoinProjectView
|
||||
.calledWith(@project_id, @user_id)
|
||||
.should.equal true
|
||||
|
||||
|
@ -71,6 +76,61 @@ describe "EditorHttpController", ->
|
|||
.calledWith(@project_id)
|
||||
.should.equal true
|
||||
|
||||
describe "_buildJoinProjectView", ->
|
||||
beforeEach ->
|
||||
@project =
|
||||
_id: @project_id
|
||||
owner_ref:{_id:"something"}
|
||||
@user =
|
||||
_id: @user_id = "user-id"
|
||||
projects: {}
|
||||
@projectModelView =
|
||||
_id: @project_id
|
||||
owner:{_id:"something"}
|
||||
view: true
|
||||
@ProjectEditorHandler.buildProjectModelView = sinon.stub().returns(@projectModelView)
|
||||
@ProjectGetter.getProjectWithoutDocLines = sinon.stub().callsArgWith(1, null, @project)
|
||||
@ProjectGetter.populateProjectWithUsers = sinon.stub().callsArgWith(1, null, @project)
|
||||
@UserGetter.getUser = sinon.stub().callsArgWith(2, null, @user)
|
||||
|
||||
describe "when authorized", ->
|
||||
beforeEach ->
|
||||
@AuthorizationManager.getPrivilegeLevelForProject =
|
||||
sinon.stub().callsArgWith(2, null, true, "owner")
|
||||
@EditorHttpController._buildJoinProjectView(@project_id, @user_id, @callback)
|
||||
|
||||
it "should find the project without doc lines", ->
|
||||
@ProjectGetter.getProjectWithoutDocLines
|
||||
.calledWith(@project_id)
|
||||
.should.equal true
|
||||
|
||||
it "should populate the user references in the project", ->
|
||||
@ProjectGetter.populateProjectWithUsers
|
||||
.calledWith(@project)
|
||||
.should.equal true
|
||||
|
||||
it "should look up the user", ->
|
||||
@UserGetter.getUser
|
||||
.calledWith(@user_id, { isAdmin: true })
|
||||
.should.equal true
|
||||
|
||||
it "should check the privilege level", ->
|
||||
@AuthorizationManager.getPrivilegeLevelForProject
|
||||
.calledWith(@project, @user)
|
||||
.should.equal true
|
||||
|
||||
it "should return the project model view, privilege level and protocol version", ->
|
||||
@callback.calledWith(null, @projectModelView, "owner").should.equal true
|
||||
|
||||
describe "when not authorized", ->
|
||||
beforeEach ->
|
||||
@AuthorizationManager.getPrivilegeLevelForProject =
|
||||
sinon.stub().callsArgWith(2, null, false, null)
|
||||
@EditorHttpController._buildJoinProjectView(@project_id, @user_id, @callback)
|
||||
|
||||
it "should return false in the callback", ->
|
||||
@callback.calledWith(null, null, false).should.equal true
|
||||
|
||||
describe "restoreDoc", ->
|
||||
beforeEach ->
|
||||
@req.params =
|
||||
|
|
|
@ -41,49 +41,3 @@ describe "EditorRealTimeController", ->
|
|||
@EditorRealTimeController.emitToRoom
|
||||
.calledWith("all", @message, @payload...)
|
||||
.should.equal true
|
||||
|
||||
describe "listenForEditorEvents", ->
|
||||
beforeEach ->
|
||||
@EditorRealTimeController._processEditorEvent = sinon.stub()
|
||||
@EditorRealTimeController.listenForEditorEvents()
|
||||
|
||||
it "should subscribe to the editor-events channel", ->
|
||||
@EditorRealTimeController.rclientSub.subscribe
|
||||
.calledWith("editor-events")
|
||||
.should.equal true
|
||||
|
||||
it "should process the events with _processEditorEvent", ->
|
||||
@EditorRealTimeController.rclientSub.on
|
||||
.calledWith("message", sinon.match.func)
|
||||
.should.equal true
|
||||
|
||||
describe "_processEditorEvent", ->
|
||||
describe "with a designated room", ->
|
||||
beforeEach ->
|
||||
@io.sockets =
|
||||
in: sinon.stub().returns(emit: @emit = sinon.stub())
|
||||
data = JSON.stringify
|
||||
room_id: @room_id
|
||||
message: @message
|
||||
payload: @payload
|
||||
@EditorRealTimeController._processEditorEvent("editor-events", data)
|
||||
|
||||
it "should send the message to all clients in the room", ->
|
||||
@io.sockets.in
|
||||
.calledWith(@room_id)
|
||||
.should.equal true
|
||||
@emit.calledWith(@message, @payload...).should.equal true
|
||||
|
||||
describe "when emitting to all", ->
|
||||
beforeEach ->
|
||||
@io.sockets =
|
||||
emit: @emit = sinon.stub()
|
||||
data = JSON.stringify
|
||||
room_id: "all"
|
||||
message: @message
|
||||
payload: @payload
|
||||
@EditorRealTimeController._processEditorEvent("editor-events", data)
|
||||
|
||||
it "should send the message to all clients", ->
|
||||
@emit.calledWith(@message, @payload...).should.equal true
|
||||
|
||||
|
|
|
@ -1,164 +0,0 @@
|
|||
SandboxedModule = require('sandboxed-module')
|
||||
sinon = require('sinon')
|
||||
require('chai').should()
|
||||
modulePath = require('path').join __dirname, '../../../../app/js/Features/Editor/EditorUpdatesController'
|
||||
MockClient = require "../helpers/MockClient"
|
||||
assert = require('assert')
|
||||
|
||||
describe "EditorUpdatesController", ->
|
||||
beforeEach ->
|
||||
@project_id = "project-id-123"
|
||||
@doc_id = "doc-id-123"
|
||||
@client = new MockClient()
|
||||
@callback = sinon.stub()
|
||||
@EditorUpdatesController = SandboxedModule.require modulePath, requires:
|
||||
"logger-sharelatex": @logger = { error: sinon.stub(), log: sinon.stub() }
|
||||
"./EditorRealTimeController" : @EditorRealTimeController = {}
|
||||
"../DocumentUpdater/DocumentUpdaterHandler" : @DocumentUpdaterHandler = {}
|
||||
"../../infrastructure/Metrics" : @metrics = { set: sinon.stub(), inc: sinon.stub() }
|
||||
"../../infrastructure/Server" : io: @io = {}
|
||||
"redis-sharelatex" :
|
||||
createClient: ()=>
|
||||
@rclient = {auth:->}
|
||||
|
||||
describe "_applyUpdate", ->
|
||||
beforeEach ->
|
||||
@update = {op: {p: 12, t: "foo"}}
|
||||
@client.set("user_id", @user_id = "user-id-123")
|
||||
@DocumentUpdaterHandler.queueChange = sinon.stub().callsArg(3)
|
||||
|
||||
describe "succesfully", ->
|
||||
beforeEach ->
|
||||
@EditorUpdatesController._applyUpdate @client, @project_id, @doc_id, @update, @callback
|
||||
|
||||
it "should queue the update", ->
|
||||
@DocumentUpdaterHandler.queueChange
|
||||
.calledWith(@project_id, @doc_id, @update)
|
||||
.should.equal true
|
||||
|
||||
it "should call the callback", ->
|
||||
@callback.called.should.equal true
|
||||
|
||||
it "should update the active users metric", ->
|
||||
@metrics.set.calledWith("editor.active-users", @user_id).should.equal true
|
||||
|
||||
it "should update the active projects metric", ->
|
||||
@metrics.set.calledWith("editor.active-projects", @project_id).should.equal true
|
||||
|
||||
it "should increment the doc updates", ->
|
||||
@metrics.inc.calledWith("editor.doc-update").should.equal true
|
||||
|
||||
describe "unsuccessfully", ->
|
||||
beforeEach ->
|
||||
@client.disconnect = sinon.stub()
|
||||
@DocumentUpdaterHandler.queueChange = sinon.stub().callsArgWith(3, new Error("Something went wrong"))
|
||||
@EditorUpdatesController._applyUpdate @client, @project_id, @doc_id, @update, @callback
|
||||
|
||||
it "should disconnect the client", ->
|
||||
@client.disconnect.called.should.equal true
|
||||
|
||||
it "should log an error", ->
|
||||
@logger.error.called.should.equal true
|
||||
|
||||
describe "applyOtUpdate", ->
|
||||
beforeEach ->
|
||||
@client.id = "client-id"
|
||||
@client.set("user_id", @user_id = "user-id-123")
|
||||
@update = {op: {p: 12, t: "foo"}}
|
||||
@EditorUpdatesController._applyUpdate = sinon.stub()
|
||||
@EditorUpdatesController.applyOtUpdate @client, @project_id, @doc_id, @update
|
||||
|
||||
it "should set the source of the update to the client id", ->
|
||||
@update.meta.source.should.equal @client.id
|
||||
|
||||
it "should set the user_id of the update to the user id", ->
|
||||
@update.meta.user_id.should.equal @user_id
|
||||
|
||||
it "should apply the update", ->
|
||||
@EditorUpdatesController._applyUpdate
|
||||
.calledWith(@client, @project_id, @doc_id, @update)
|
||||
.should.equal true
|
||||
|
||||
describe "listenForUpdatesFromDocumentUpdater", ->
|
||||
beforeEach ->
|
||||
@rclient.subscribe = sinon.stub()
|
||||
@rclient.on = sinon.stub()
|
||||
@EditorUpdatesController.listenForUpdatesFromDocumentUpdater()
|
||||
|
||||
it "should subscribe to the doc-updater stream", ->
|
||||
@rclient.subscribe.calledWith("applied-ops").should.equal true
|
||||
|
||||
it "should register a callback to handle updates", ->
|
||||
@rclient.on.calledWith("message").should.equal true
|
||||
|
||||
describe "_processMessageFromDocumentUpdater", ->
|
||||
describe "with update", ->
|
||||
beforeEach ->
|
||||
@message =
|
||||
doc_id: @doc_id
|
||||
op: {t: "foo", p: 12}
|
||||
@EditorUpdatesController._applyUpdateFromDocumentUpdater = sinon.stub()
|
||||
@EditorUpdatesController._processMessageFromDocumentUpdater "applied-ops", JSON.stringify(@message)
|
||||
|
||||
it "should apply the update", ->
|
||||
@EditorUpdatesController._applyUpdateFromDocumentUpdater
|
||||
.calledWith(@doc_id, @message.op)
|
||||
.should.equal true
|
||||
|
||||
describe "with error", ->
|
||||
beforeEach ->
|
||||
@message =
|
||||
doc_id: @doc_id
|
||||
error: "Something went wrong"
|
||||
@EditorUpdatesController._processErrorFromDocumentUpdater = sinon.stub()
|
||||
@EditorUpdatesController._processMessageFromDocumentUpdater "applied-ops", JSON.stringify(@message)
|
||||
|
||||
it "should process the error", ->
|
||||
@EditorUpdatesController._processErrorFromDocumentUpdater
|
||||
.calledWith(@doc_id, @message.error)
|
||||
.should.equal true
|
||||
|
||||
describe "_applyUpdateFromDocumentUpdater", ->
|
||||
beforeEach ->
|
||||
@sourceClient = new MockClient()
|
||||
@otherClients = [new MockClient(), new MockClient()]
|
||||
@update =
|
||||
op: [ t: "foo", p: 12 ]
|
||||
meta: source: @sourceClient.id
|
||||
v: @version = 42
|
||||
doc: @doc_id
|
||||
@io.sockets =
|
||||
clients: sinon.stub().returns([@sourceClient, @otherClients...])
|
||||
@EditorUpdatesController._applyUpdateFromDocumentUpdater @doc_id, @update
|
||||
|
||||
it "should send a version bump to the source client", ->
|
||||
@sourceClient.emit
|
||||
.calledWith("otUpdateApplied", v: @version, doc: @doc_id)
|
||||
.should.equal true
|
||||
|
||||
it "should get the clients connected to the document", ->
|
||||
@io.sockets.clients
|
||||
.calledWith(@doc_id)
|
||||
.should.equal true
|
||||
|
||||
it "should send the full update to the other clients", ->
|
||||
for client in @otherClients
|
||||
client.emit
|
||||
.calledWith("otUpdateApplied", @update)
|
||||
.should.equal true
|
||||
|
||||
describe "_processErrorFromDocumentUpdater", ->
|
||||
beforeEach ->
|
||||
@clients = [new MockClient(), new MockClient()]
|
||||
@io.sockets =
|
||||
clients: sinon.stub().returns(@clients)
|
||||
@EditorUpdatesController._processErrorFromDocumentUpdater @doc_id, "Something went wrong"
|
||||
|
||||
it "should log out an error", ->
|
||||
@logger.error.called.should.equal true
|
||||
|
||||
it "should disconnect all clients in that document", ->
|
||||
@io.sockets.clients.calledWith(@doc_id).should.equal true
|
||||
for client in @clients
|
||||
client.disconnect.called.should.equal true
|
||||
|
Loading…
Reference in a new issue