/* eslint-disable camelcase, handle-callback-err, */ // TODO: This file was created by bulk-decaffeinate. // Fix any style issues and re-enable lint. /* * 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; 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){ if (callback == null) { callback = function(err){}; } 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 != 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); return multi.exec(function(err){ if (err != null) { logger.err({err, project_id, client_id}, "problem marking user as connected"); } return callback(err); }); }, 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); }); }, 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); }, _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); }); }, 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); }); }); } };