2020-06-23 13:29:44 -04:00
|
|
|
const async = require('async')
|
2021-07-12 12:47:18 -04:00
|
|
|
const Settings = require('@overleaf/settings')
|
2021-12-14 08:00:35 -05:00
|
|
|
const logger = require('@overleaf/logger')
|
2020-11-10 06:32:06 -05:00
|
|
|
const redis = require('@overleaf/redis-wrapper')
|
2020-08-20 09:05:50 -04:00
|
|
|
const OError = require('@overleaf/o-error')
|
2020-06-23 13:29:44 -04:00
|
|
|
const rclient = redis.createClient(Settings.redis.realtime)
|
|
|
|
const Keys = Settings.redis.realtime.key_schema
|
2014-11-13 07:27:46 -05:00
|
|
|
|
2020-06-23 13:29:44 -04:00
|
|
|
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
|
2014-11-13 07:27:46 -05:00
|
|
|
|
2020-06-23 13:29:44 -04:00
|
|
|
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
|
2014-11-13 07:27:46 -05:00
|
|
|
|
2020-06-23 13:29:34 -04:00
|
|
|
module.exports = {
|
2020-06-23 13:29:44 -04:00
|
|
|
// 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.
|
2023-03-20 10:10:40 -04:00
|
|
|
updateUserPosition(projectId, clientId, user, cursorData, callback) {
|
|
|
|
logger.debug({ projectId, clientId }, 'marking user as joined or connected')
|
2014-11-13 07:27:46 -05:00
|
|
|
|
2020-06-23 13:29:44 -04:00
|
|
|
const multi = rclient.multi()
|
2014-11-13 07:27:46 -05:00
|
|
|
|
2023-03-20 10:10:40 -04:00
|
|
|
multi.sadd(Keys.clientsInProject({ project_id: projectId }), clientId)
|
|
|
|
multi.expire(
|
|
|
|
Keys.clientsInProject({ project_id: projectId }),
|
|
|
|
FOUR_DAYS_IN_S
|
|
|
|
)
|
2019-08-13 05:39:52 -04:00
|
|
|
|
2020-06-23 13:29:44 -04:00
|
|
|
multi.hset(
|
2023-03-20 10:10:40 -04:00
|
|
|
Keys.connectedUser({ project_id: projectId, client_id: clientId }),
|
2020-06-23 13:29:44 -04:00
|
|
|
'last_updated_at',
|
|
|
|
Date.now()
|
|
|
|
)
|
|
|
|
multi.hset(
|
2023-03-20 10:10:40 -04:00
|
|
|
Keys.connectedUser({ project_id: projectId, client_id: clientId }),
|
2020-06-23 13:29:44 -04:00
|
|
|
'user_id',
|
|
|
|
user._id
|
|
|
|
)
|
|
|
|
multi.hset(
|
2023-03-20 10:10:40 -04:00
|
|
|
Keys.connectedUser({ project_id: projectId, client_id: clientId }),
|
2020-06-23 13:29:44 -04:00
|
|
|
'first_name',
|
|
|
|
user.first_name || ''
|
|
|
|
)
|
|
|
|
multi.hset(
|
2023-03-20 10:10:40 -04:00
|
|
|
Keys.connectedUser({ project_id: projectId, client_id: clientId }),
|
2020-06-23 13:29:44 -04:00
|
|
|
'last_name',
|
|
|
|
user.last_name || ''
|
|
|
|
)
|
|
|
|
multi.hset(
|
2023-03-20 10:10:40 -04:00
|
|
|
Keys.connectedUser({ project_id: projectId, client_id: clientId }),
|
2020-06-23 13:29:44 -04:00
|
|
|
'email',
|
|
|
|
user.email || ''
|
|
|
|
)
|
2014-11-13 07:27:46 -05:00
|
|
|
|
2020-07-07 06:06:02 -04:00
|
|
|
if (cursorData) {
|
2020-06-23 13:29:44 -04:00
|
|
|
multi.hset(
|
2023-03-20 10:10:40 -04:00
|
|
|
Keys.connectedUser({ project_id: projectId, client_id: clientId }),
|
2020-06-23 13:29:44 -04:00
|
|
|
'cursorData',
|
|
|
|
JSON.stringify(cursorData)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
multi.expire(
|
2023-03-20 10:10:40 -04:00
|
|
|
Keys.connectedUser({ project_id: projectId, client_id: clientId }),
|
2020-06-23 13:29:44 -04:00
|
|
|
USER_TIMEOUT_IN_S
|
|
|
|
)
|
2014-11-13 07:27:46 -05:00
|
|
|
|
2020-07-07 06:06:02 -04:00
|
|
|
multi.exec(function (err) {
|
|
|
|
if (err) {
|
2020-08-21 07:15:35 -04:00
|
|
|
err = new OError('problem marking user as connected').withCause(err)
|
2020-06-23 13:29:44 -04:00
|
|
|
}
|
2020-07-07 06:06:02 -04:00
|
|
|
callback(err)
|
2020-06-23 13:29:44 -04:00
|
|
|
})
|
|
|
|
},
|
2014-11-13 07:27:46 -05:00
|
|
|
|
2023-03-20 10:10:40 -04:00
|
|
|
refreshClient(projectId, clientId) {
|
|
|
|
logger.debug({ projectId, clientId }, 'refreshing connected client')
|
2020-06-23 13:29:44 -04:00
|
|
|
const multi = rclient.multi()
|
|
|
|
multi.hset(
|
2023-03-20 10:10:40 -04:00
|
|
|
Keys.connectedUser({ project_id: projectId, client_id: clientId }),
|
2020-06-23 13:29:44 -04:00
|
|
|
'last_updated_at',
|
|
|
|
Date.now()
|
|
|
|
)
|
|
|
|
multi.expire(
|
2023-03-20 10:10:40 -04:00
|
|
|
Keys.connectedUser({ project_id: projectId, client_id: clientId }),
|
2020-06-23 13:29:44 -04:00
|
|
|
USER_TIMEOUT_IN_S
|
|
|
|
)
|
2020-07-07 06:06:02 -04:00
|
|
|
multi.exec(function (err) {
|
|
|
|
if (err) {
|
2020-06-23 13:29:44 -04:00
|
|
|
logger.err(
|
2023-03-20 10:10:40 -04:00
|
|
|
{ err, projectId, clientId },
|
2020-06-23 13:29:44 -04:00
|
|
|
'problem refreshing connected client'
|
|
|
|
)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
},
|
2014-11-13 07:27:46 -05:00
|
|
|
|
2023-03-20 10:10:40 -04:00
|
|
|
markUserAsDisconnected(projectId, clientId, callback) {
|
|
|
|
logger.debug({ projectId, clientId }, 'marking user as disconnected')
|
2020-06-23 13:29:44 -04:00
|
|
|
const multi = rclient.multi()
|
2023-03-20 10:10:40 -04:00
|
|
|
multi.srem(Keys.clientsInProject({ project_id: projectId }), clientId)
|
|
|
|
multi.expire(
|
|
|
|
Keys.clientsInProject({ project_id: projectId }),
|
|
|
|
FOUR_DAYS_IN_S
|
|
|
|
)
|
|
|
|
multi.del(
|
|
|
|
Keys.connectedUser({ project_id: projectId, client_id: clientId })
|
|
|
|
)
|
2020-08-20 09:05:50 -04:00
|
|
|
multi.exec(function (err) {
|
|
|
|
if (err) {
|
2020-08-21 07:15:35 -04:00
|
|
|
err = new OError('problem marking user as disconnected').withCause(err)
|
2020-08-20 09:05:50 -04:00
|
|
|
}
|
|
|
|
callback(err)
|
|
|
|
})
|
2020-06-23 13:29:44 -04:00
|
|
|
},
|
|
|
|
|
2023-03-20 10:10:40 -04:00
|
|
|
_getConnectedUser(projectId, clientId, callback) {
|
2021-07-13 07:04:45 -04:00
|
|
|
rclient.hgetall(
|
2023-03-20 10:10:40 -04:00
|
|
|
Keys.connectedUser({ project_id: projectId, client_id: clientId }),
|
2021-07-13 07:04:45 -04:00
|
|
|
function (err, result) {
|
|
|
|
if (err) {
|
|
|
|
err = new OError('problem fetching connected user details', {
|
2023-03-20 10:10:40 -04:00
|
|
|
other_client_id: clientId,
|
2021-07-13 07:04:45 -04:00
|
|
|
}).withCause(err)
|
|
|
|
return callback(err)
|
2020-07-07 06:06:02 -04:00
|
|
|
}
|
2021-07-13 07:04:45 -04:00
|
|
|
if (!(result && result.user_id)) {
|
|
|
|
result = {
|
|
|
|
connected: false,
|
2023-03-20 10:10:40 -04:00
|
|
|
client_id: clientId,
|
2021-07-13 07:04:45 -04:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
result.connected = true
|
2023-03-20 10:10:40 -04:00
|
|
|
result.client_id = clientId
|
2021-07-13 07:04:45 -04:00
|
|
|
result.client_age =
|
|
|
|
(Date.now() - parseInt(result.last_updated_at, 10)) / 1000
|
|
|
|
if (result.cursorData) {
|
|
|
|
try {
|
|
|
|
result.cursorData = JSON.parse(result.cursorData)
|
|
|
|
} catch (e) {
|
|
|
|
OError.tag(e, 'error parsing cursorData JSON', {
|
2023-03-20 10:10:40 -04:00
|
|
|
other_client_id: clientId,
|
2021-07-13 07:04:45 -04:00
|
|
|
cursorData: result.cursorData,
|
|
|
|
})
|
|
|
|
return callback(e)
|
|
|
|
}
|
2020-06-23 13:29:44 -04:00
|
|
|
}
|
|
|
|
}
|
2021-07-13 07:04:45 -04:00
|
|
|
callback(err, result)
|
2020-06-23 13:29:44 -04:00
|
|
|
}
|
2021-07-13 07:04:45 -04:00
|
|
|
)
|
2020-06-23 13:29:44 -04:00
|
|
|
},
|
|
|
|
|
2023-03-20 10:10:40 -04:00
|
|
|
getConnectedUsers(projectId, callback) {
|
2020-06-23 13:29:44 -04:00
|
|
|
const self = this
|
2021-07-13 07:04:45 -04:00
|
|
|
rclient.smembers(
|
2023-03-20 10:10:40 -04:00
|
|
|
Keys.clientsInProject({ project_id: projectId }),
|
2021-07-13 07:04:45 -04:00
|
|
|
function (err, results) {
|
2020-07-07 06:06:02 -04:00
|
|
|
if (err) {
|
2021-07-13 07:04:45 -04:00
|
|
|
err = new OError('problem getting clients in project').withCause(err)
|
2020-06-23 13:29:44 -04:00
|
|
|
return callback(err)
|
|
|
|
}
|
2021-07-13 07:04:45 -04:00
|
|
|
const jobs = results.map(
|
2023-03-20 10:10:40 -04:00
|
|
|
clientId => cb => self._getConnectedUser(projectId, clientId, cb)
|
2020-06-23 13:29:44 -04:00
|
|
|
)
|
2021-07-13 07:04:45 -04:00
|
|
|
async.series(jobs, function (err, users) {
|
|
|
|
if (err) {
|
|
|
|
OError.tag(err, 'problem getting connected users')
|
|
|
|
return callback(err)
|
|
|
|
}
|
|
|
|
users = users.filter(
|
|
|
|
user =>
|
|
|
|
user && user.connected && user.client_age < REFRESH_TIMEOUT_IN_S
|
|
|
|
)
|
|
|
|
callback(null, users)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
)
|
|
|
|
},
|
2020-06-23 13:29:44 -04:00
|
|
|
}
|