overleaf/services/real-time/app/js/ConnectedUsersManager.js
Jakob Ackermann aa9d6c8dc9 [misc] reland decaff cleanup (#166)
* [misc] decaff cleanup: RoomManager

* [misc] decaff cleanup: RedisClientManager

* [misc] decaff cleanup: SafeJsonParse

* [misc] decaff cleanup: WebApiManager

* [misc] decaff cleanup: WebsocketController

* [misc] decaff cleanup: WebsocketLoadBalancer

* [misc] decaff cleanup: SessionSockets

* [misc] decaff cleanup: HttpController

* [misc] decaff cleanup: HttpApiController

* [misc] decaff cleanup: HealthCheckManager

* [misc] decaff cleanup: EventLogger

* [misc] decaff cleanup: Errors

o-error will eliminate most of it -- when we migrate over.

* [misc] decaff cleanup: DrainManager

* [misc] decaff cleanup: DocumentUpdaterManager

* [misc] decaff cleanup: DocumentUpdaterController: no-unused-vars

* [misc] decaff cleanup: DocumentUpdaterController: Array.from

* [misc] decaff cleanup: DocumentUpdaterController: implicit return

* [misc] decaff cleanup: DocumentUpdaterController: IIFE

* [misc] decaff cleanup: DocumentUpdaterController: null checks

* [misc] decaff cleanup: DocumentUpdaterController: simpler loops

* [misc] decaff cleanup: DocumentUpdaterController: move module name def

* [misc] decaff cleanup: ConnectedUsersManager: handle-callback-err

* [misc] decaff cleanup: ConnectedUsersManager: implicit returns

* [misc] decaff cleanup: ConnectedUsersManager: null checks

* [misc] decaff cleanup: ChannelManager: no-unused-vars

* [misc] decaff cleanup: ChannelManager: implicit returns

* [misc] decaff cleanup: ChannelManager: other cleanup

- var -> const
- drop variable assignment before return

* [misc] decaff cleanup: AuthorizationManager: handle-callback-err

Note: This requires a change in WebsocketController to provide a dummy
 callback.

* [misc] decaff cleanup: AuthorizationManager: Array.from

* [misc] decaff cleanup: AuthorizationManager: implicit returns

* [misc] decaff cleanup: AuthorizationManager: null checks

* [misc] decaff cleanup: Router: handle-callback-err

* [misc] decaff cleanup: Router: standard/no-callback-literal

* [misc] decaff cleanup: Router: Array.from

* [misc] decaff cleanup: Router: implicit returns

* [misc] decaff cleanup: Router: refactor __guard__ wrapper

* [misc] decaff cleanup: Router: null checks

And a minor bug fix: user.id -> user._id

* [misc] decaff cleanup: Router: move variable declarations to assignments

* [misc] decaff cleanup: app: implicit returns

* [misc] decaff cleanup: app: __guard__

* [misc] decaff cleanup: app: null checks

* [misc] decaff cleanup: app: function definitions

* [misc] decaff cleanup: app: drop unused next argument

* [misc] decaff cleanup: app: var -> const
2020-07-07 11:06:02 +01:00

170 lines
4.6 KiB
JavaScript

/* eslint-disable
camelcase,
*/
const async = require('async')
const Settings = require('settings-sharelatex')
const logger = require('logger-sharelatex')
const redis = require('redis-sharelatex')
const rclient = redis.createClient(Settings.redis.realtime)
const Keys = Settings.redis.realtime.key_schema
const ONE_HOUR_IN_S = 60 * 60
const ONE_DAY_IN_S = ONE_HOUR_IN_S * 24
const FOUR_DAYS_IN_S = ONE_DAY_IN_S * 4
const USER_TIMEOUT_IN_S = ONE_HOUR_IN_S / 4
const REFRESH_TIMEOUT_IN_S = 10 // only show clients which have responded to a refresh request in the last 10 seconds
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) {
logger.log({ project_id, client_id }, 'marking user as joined or connected')
const multi = rclient.multi()
multi.sadd(Keys.clientsInProject({ project_id }), client_id)
multi.expire(Keys.clientsInProject({ project_id }), FOUR_DAYS_IN_S)
multi.hset(
Keys.connectedUser({ project_id, client_id }),
'last_updated_at',
Date.now()
)
multi.hset(
Keys.connectedUser({ project_id, client_id }),
'user_id',
user._id
)
multi.hset(
Keys.connectedUser({ project_id, client_id }),
'first_name',
user.first_name || ''
)
multi.hset(
Keys.connectedUser({ project_id, client_id }),
'last_name',
user.last_name || ''
)
multi.hset(
Keys.connectedUser({ project_id, client_id }),
'email',
user.email || ''
)
if (cursorData) {
multi.hset(
Keys.connectedUser({ project_id, client_id }),
'cursorData',
JSON.stringify(cursorData)
)
}
multi.expire(
Keys.connectedUser({ project_id, client_id }),
USER_TIMEOUT_IN_S
)
multi.exec(function (err) {
if (err) {
logger.err(
{ err, project_id, client_id },
'problem marking user as connected'
)
}
callback(err)
})
},
refreshClient(project_id, client_id) {
logger.log({ project_id, client_id }, 'refreshing connected client')
const multi = rclient.multi()
multi.hset(
Keys.connectedUser({ project_id, client_id }),
'last_updated_at',
Date.now()
)
multi.expire(
Keys.connectedUser({ project_id, client_id }),
USER_TIMEOUT_IN_S
)
multi.exec(function (err) {
if (err) {
logger.err(
{ err, project_id, client_id },
'problem refreshing connected client'
)
}
})
},
markUserAsDisconnected(project_id, client_id, callback) {
logger.log({ project_id, client_id }, 'marking user as disconnected')
const multi = rclient.multi()
multi.srem(Keys.clientsInProject({ project_id }), client_id)
multi.expire(Keys.clientsInProject({ project_id }), FOUR_DAYS_IN_S)
multi.del(Keys.connectedUser({ project_id, client_id }))
multi.exec(callback)
},
_getConnectedUser(project_id, client_id, callback) {
rclient.hgetall(Keys.connectedUser({ project_id, client_id }), function (
err,
result
) {
if (!(result && result.user_id)) {
result = {
connected: false,
client_id
}
} else {
result.connected = true
result.client_id = client_id
result.client_age =
(Date.now() - parseInt(result.last_updated_at, 10)) / 1000
if (result.cursorData) {
try {
result.cursorData = JSON.parse(result.cursorData)
} catch (e) {
logger.error(
{
err: e,
project_id,
client_id,
cursorData: result.cursorData
},
'error parsing cursorData JSON'
)
return callback(e)
}
}
}
callback(err, result)
})
},
getConnectedUsers(project_id, callback) {
const self = this
rclient.smembers(Keys.clientsInProject({ project_id }), function (
err,
results
) {
if (err) {
return callback(err)
}
const jobs = results.map((client_id) => (cb) =>
self._getConnectedUser(project_id, client_id, cb)
)
async.series(jobs, function (err, users) {
if (err) {
return callback(err)
}
users = users.filter(
(user) =>
user && user.connected && user.client_age < REFRESH_TIMEOUT_IN_S
)
callback(null, users)
})
})
}
}