Clean up unused real-time code in web

This commit is contained in:
James Allen
2015-02-05 16:37:37 +00:00
parent 3aad31069c
commit d7afb4e513
19 changed files with 106 additions and 1144 deletions
@@ -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)
+1 -67
View File
@@ -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
+1 -1
View File
@@ -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.