mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-07 20:31:06 -05:00
264 lines
No EOL
9.4 KiB
JavaScript
264 lines
No EOL
9.4 KiB
JavaScript
/*
|
|
* decaffeinate suggestions:
|
|
* DS101: Remove unnecessary use of Array.from
|
|
* DS102: Remove unnecessary code created because of implicit returns
|
|
* DS103: Rewrite code to no longer use __guard__
|
|
* DS207: Consider shorter variations of null checks
|
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
|
*/
|
|
let Router;
|
|
const metrics = require("metrics-sharelatex");
|
|
const logger = require("logger-sharelatex");
|
|
const settings = require("settings-sharelatex");
|
|
const WebsocketController = require("./WebsocketController");
|
|
const HttpController = require("./HttpController");
|
|
const HttpApiController = require("./HttpApiController");
|
|
const bodyParser = require("body-parser");
|
|
const base64id = require("base64id");
|
|
|
|
const basicAuth = require('basic-auth-connect');
|
|
const httpAuth = basicAuth(function(user, pass){
|
|
const isValid = (user === settings.internal.realTime.user) && (pass === settings.internal.realTime.pass);
|
|
if (!isValid) {
|
|
logger.err({user, pass}, "invalid login details");
|
|
}
|
|
return isValid;
|
|
});
|
|
|
|
module.exports = (Router = {
|
|
_handleError(callback, error, client, method, attrs) {
|
|
if (callback == null) { callback = function(error) {}; }
|
|
if (attrs == null) { attrs = {}; }
|
|
for (let key of ["project_id", "doc_id", "user_id"]) {
|
|
attrs[key] = client.ol_context[key];
|
|
}
|
|
attrs.client_id = client.id;
|
|
attrs.err = error;
|
|
if (error.name === "CodedError") {
|
|
logger.warn(attrs, error.message, {code: error.code});
|
|
return callback({message: error.message, code: error.code});
|
|
}
|
|
if (error.message === 'unexpected arguments') {
|
|
// the payload might be very large, put it on level info
|
|
logger.log(attrs, 'unexpected arguments');
|
|
metrics.inc('unexpected-arguments', 1, { status: method });
|
|
return callback({ message: error.message });
|
|
}
|
|
if (["not authorized", "doc updater could not load requested ops", "no project_id found on client"].includes(error.message)) {
|
|
logger.warn(attrs, error.message);
|
|
return callback({message: error.message});
|
|
} else {
|
|
logger.error(attrs, `server side error in ${method}`);
|
|
// Don't return raw error to prevent leaking server side info
|
|
return callback({message: "Something went wrong in real-time service"});
|
|
}
|
|
},
|
|
|
|
_handleInvalidArguments(client, method, args) {
|
|
const error = new Error("unexpected arguments");
|
|
let callback = args[args.length - 1];
|
|
if (typeof callback !== 'function') {
|
|
callback = (function() {});
|
|
}
|
|
const attrs = {arguments: args};
|
|
return Router._handleError(callback, error, client, method, attrs);
|
|
},
|
|
|
|
configure(app, io, session) {
|
|
app.set("io", io);
|
|
app.get("/clients", HttpController.getConnectedClients);
|
|
app.get("/clients/:client_id", HttpController.getConnectedClient);
|
|
|
|
app.post("/project/:project_id/message/:message", httpAuth, bodyParser.json({limit: "5mb"}), HttpApiController.sendMessage);
|
|
|
|
app.post("/drain", httpAuth, HttpApiController.startDrain);
|
|
app.post("/client/:client_id/disconnect", httpAuth, HttpApiController.disconnectClient);
|
|
|
|
return session.on('connection', function(error, client, session) {
|
|
// init client context, we may access it in Router._handleError before
|
|
// setting any values
|
|
let user;
|
|
client.ol_context = {};
|
|
|
|
if (client != null) {
|
|
client.on("error", function(err) {
|
|
logger.err({ clientErr: err }, "socket.io client error");
|
|
if (client.connected) {
|
|
client.emit("reconnectGracefully");
|
|
return client.disconnect();
|
|
}
|
|
});
|
|
}
|
|
|
|
if (settings.shutDownInProgress) {
|
|
client.emit("connectionRejected", {message: "retry"});
|
|
client.disconnect();
|
|
return;
|
|
}
|
|
|
|
if ((client != null) && __guard__(error != null ? error.message : undefined, x => x.match(/could not look up session by key/))) {
|
|
logger.warn({err: error, client: (client != null), session: (session != null)}, "invalid session");
|
|
// tell the client to reauthenticate if it has an invalid session key
|
|
client.emit("connectionRejected", {message: "invalid session"});
|
|
client.disconnect();
|
|
return;
|
|
}
|
|
|
|
if (error != null) {
|
|
logger.err({err: error, client: (client != null), session: (session != null)}, "error when client connected");
|
|
if (client != null) {
|
|
client.emit("connectionRejected", {message: "error"});
|
|
}
|
|
if (client != null) {
|
|
client.disconnect();
|
|
}
|
|
return;
|
|
}
|
|
|
|
// send positive confirmation that the client has a valid connection
|
|
client.publicId = 'P.' + base64id.generateId();
|
|
client.emit("connectionAccepted", null, client.publicId);
|
|
|
|
metrics.inc('socket-io.connection');
|
|
metrics.gauge('socket-io.clients', __guard__(io.sockets.clients(), x1 => x1.length));
|
|
|
|
logger.log({session, client_id: client.id}, "client connected");
|
|
|
|
if (__guard__(session != null ? session.passport : undefined, x2 => x2.user) != null) {
|
|
({
|
|
user
|
|
} = session.passport);
|
|
} else if ((session != null ? session.user : undefined) != null) {
|
|
({
|
|
user
|
|
} = session);
|
|
} else {
|
|
user = {_id: "anonymous-user"};
|
|
}
|
|
|
|
client.on("joinProject", function(data, callback) {
|
|
if (data == null) { data = {}; }
|
|
if (typeof callback !== 'function') {
|
|
return Router._handleInvalidArguments(client, 'joinProject', arguments);
|
|
}
|
|
|
|
if (data.anonymousAccessToken) {
|
|
user.anonymousAccessToken = data.anonymousAccessToken;
|
|
}
|
|
return WebsocketController.joinProject(client, user, data.project_id, function(err, ...args) {
|
|
if (err != null) {
|
|
return Router._handleError(callback, err, client, "joinProject", {project_id: data.project_id, user_id: (user != null ? user.id : undefined)});
|
|
} else {
|
|
return callback(null, ...Array.from(args));
|
|
}
|
|
});
|
|
});
|
|
|
|
client.on("disconnect", function() {
|
|
metrics.inc('socket-io.disconnect');
|
|
metrics.gauge('socket-io.clients', __guard__(io.sockets.clients(), x3 => x3.length) - 1);
|
|
|
|
return WebsocketController.leaveProject(io, client, function(err) {
|
|
if (err != null) {
|
|
return Router._handleError((function() {}), err, client, "leaveProject");
|
|
}
|
|
});
|
|
});
|
|
|
|
// Variadic. The possible arguments:
|
|
// doc_id, callback
|
|
// doc_id, fromVersion, callback
|
|
// doc_id, options, callback
|
|
// doc_id, fromVersion, options, callback
|
|
client.on("joinDoc", function(doc_id, fromVersion, options, callback) {
|
|
if ((typeof fromVersion === "function") && !options) {
|
|
callback = fromVersion;
|
|
fromVersion = -1;
|
|
options = {};
|
|
} else if ((typeof fromVersion === "number") && (typeof options === "function")) {
|
|
callback = options;
|
|
options = {};
|
|
} else if ((typeof fromVersion === "object") && (typeof options === "function")) {
|
|
callback = options;
|
|
options = fromVersion;
|
|
fromVersion = -1;
|
|
} else if ((typeof fromVersion === "number") && (typeof options === "object") && (typeof callback === 'function')) {
|
|
// Called with 4 args, things are as expected
|
|
} else {
|
|
return Router._handleInvalidArguments(client, 'joinDoc', arguments);
|
|
}
|
|
|
|
return WebsocketController.joinDoc(client, doc_id, fromVersion, options, function(err, ...args) {
|
|
if (err != null) {
|
|
return Router._handleError(callback, err, client, "joinDoc", {doc_id, fromVersion});
|
|
} else {
|
|
return callback(null, ...Array.from(args));
|
|
}
|
|
});
|
|
});
|
|
|
|
client.on("leaveDoc", function(doc_id, callback) {
|
|
if (typeof callback !== 'function') {
|
|
return Router._handleInvalidArguments(client, 'leaveDoc', arguments);
|
|
}
|
|
|
|
return WebsocketController.leaveDoc(client, doc_id, function(err, ...args) {
|
|
if (err != null) {
|
|
return Router._handleError(callback, err, client, "leaveDoc");
|
|
} else {
|
|
return callback(null, ...Array.from(args));
|
|
}
|
|
});
|
|
});
|
|
|
|
client.on("clientTracking.getConnectedUsers", function(callback) {
|
|
if (callback == null) { callback = function(error, users) {}; }
|
|
if (typeof callback !== 'function') {
|
|
return Router._handleInvalidArguments(client, 'clientTracking.getConnectedUsers', arguments);
|
|
}
|
|
|
|
return WebsocketController.getConnectedUsers(client, function(err, users) {
|
|
if (err != null) {
|
|
return Router._handleError(callback, err, client, "clientTracking.getConnectedUsers");
|
|
} else {
|
|
return callback(null, users);
|
|
}
|
|
});
|
|
});
|
|
|
|
client.on("clientTracking.updatePosition", function(cursorData, callback) {
|
|
if (callback == null) { callback = function(error) {}; }
|
|
if (typeof callback !== 'function') {
|
|
return Router._handleInvalidArguments(client, 'clientTracking.updatePosition', arguments);
|
|
}
|
|
|
|
return WebsocketController.updateClientPosition(client, cursorData, function(err) {
|
|
if (err != null) {
|
|
return Router._handleError(callback, err, client, "clientTracking.updatePosition");
|
|
} else {
|
|
return callback();
|
|
}
|
|
});
|
|
});
|
|
|
|
return client.on("applyOtUpdate", function(doc_id, update, callback) {
|
|
if (callback == null) { callback = function(error) {}; }
|
|
if (typeof callback !== 'function') {
|
|
return Router._handleInvalidArguments(client, 'applyOtUpdate', arguments);
|
|
}
|
|
|
|
return WebsocketController.applyOtUpdate(client, doc_id, update, function(err) {
|
|
if (err != null) {
|
|
return Router._handleError(callback, err, client, "applyOtUpdate", {doc_id, update});
|
|
} else {
|
|
return callback();
|
|
}
|
|
});
|
|
});
|
|
});
|
|
}
|
|
});
|
|
|
|
function __guard__(value, transform) {
|
|
return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined;
|
|
} |