overleaf/services/real-time/app/coffee/ConnectedUsersManager.js

116 lines
4.5 KiB
JavaScript
Raw Normal View History

/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
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;
2014-11-13 07:27:46 -05: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
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
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){
if (callback == null) { callback = function(err){}; }
logger.log({project_id, client_id}, "marking user as joined or connected");
2014-11-13 07:27:46 -05:00
const multi = rclient.multi();
2014-11-13 07:27:46 -05:00
multi.sadd(Keys.clientsInProject({project_id}), client_id);
multi.expire(Keys.clientsInProject({project_id}), FOUR_DAYS_IN_S);
2014-11-13 07:27:46 -05:00
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 || "");
2014-11-13 07:27:46 -05:00
if (cursorData != null) {
multi.hset(Keys.connectedUser({project_id, client_id}), "cursorData", JSON.stringify(cursorData));
}
multi.expire(Keys.connectedUser({project_id, client_id}), USER_TIMEOUT_IN_S);
2014-11-13 07:27:46 -05:00
return multi.exec(function(err){
if (err != null) {
logger.err({err, project_id, client_id}, "problem marking user as connected");
}
return callback(err);
});
},
2014-11-13 07:27:46 -05:00
refreshClient(project_id, client_id, callback) {
if (callback == null) { callback = function(err) {}; }
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);
return multi.exec(function(err){
if (err != null) {
logger.err({err, project_id, client_id}, "problem refreshing connected client");
}
return callback(err);
});
},
2019-08-13 05:39:52 -04:00
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}));
return multi.exec(callback);
},
2014-11-13 07:27:46 -05:00
_getConnectedUser(project_id, client_id, callback){
return rclient.hgetall(Keys.connectedUser({project_id, client_id}), function(err, result){
if ((result == null) || (Object.keys(result).length === 0) || !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 != null) {
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);
}
}
}
return callback(err, result);
});
},
2014-11-13 07:27:46 -05:00
getConnectedUsers(project_id, callback){
const self = this;
return rclient.smembers(Keys.clientsInProject({project_id}), function(err, results){
if (err != null) { return callback(err); }
const jobs = results.map(client_id => cb => self._getConnectedUser(project_id, client_id, cb));
return async.series(jobs, function(err, users){
if (users == null) { users = []; }
if (err != null) { return callback(err); }
users = users.filter(user => (user != null ? user.connected : undefined) && ((user != null ? user.client_age : undefined) < REFRESH_TIMEOUT_IN_S));
return callback(null, users);
});
});
}
};
2014-11-13 07:27:46 -05:00