2014-11-17 08:12:49 -05:00
|
|
|
metrics = require "metrics-sharelatex"
|
2014-11-07 12:38:12 -05:00
|
|
|
logger = require "logger-sharelatex"
|
2015-03-03 12:15:19 -05:00
|
|
|
settings = require "settings-sharelatex"
|
2014-11-10 06:27:08 -05:00
|
|
|
WebsocketController = require "./WebsocketController"
|
2014-11-13 06:48:49 -05:00
|
|
|
HttpController = require "./HttpController"
|
2015-03-03 12:15:19 -05:00
|
|
|
HttpApiController = require "./HttpApiController"
|
2014-11-13 07:03:43 -05:00
|
|
|
Utils = require "./Utils"
|
2015-03-03 12:15:19 -05:00
|
|
|
bodyParser = require "body-parser"
|
2020-06-04 10:52:13 -04:00
|
|
|
base64id = require("base64id")
|
2015-03-03 12:15:19 -05:00
|
|
|
|
|
|
|
basicAuth = require('basic-auth-connect')
|
|
|
|
httpAuth = basicAuth (user, pass)->
|
|
|
|
isValid = user == settings.internal.realTime.user and pass == settings.internal.realTime.pass
|
|
|
|
if !isValid
|
|
|
|
logger.err user:user, pass:pass, "invalid login details"
|
|
|
|
return isValid
|
2014-11-07 12:38:12 -05:00
|
|
|
|
|
|
|
module.exports = Router =
|
2014-11-17 07:23:30 -05:00
|
|
|
_handleError: (callback = ((error) ->), error, client, method, extraAttrs = {}) ->
|
2014-11-13 10:27:18 -05:00
|
|
|
Utils.getClientAttributes client, ["project_id", "doc_id", "user_id"], (_, attrs) ->
|
|
|
|
for key, value of extraAttrs
|
|
|
|
attrs[key] = value
|
|
|
|
attrs.client_id = client.id
|
|
|
|
attrs.err = error
|
2019-08-31 09:04:36 -04:00
|
|
|
if error.name == "CodedError"
|
|
|
|
logger.warn attrs, error.message, code: error.code
|
|
|
|
return callback {message: error.message, code: error.code}
|
2020-05-22 04:17:50 -04:00
|
|
|
if error.message == 'unexpected arguments'
|
|
|
|
logger.log attrs, 'unexpected arguments'
|
|
|
|
metrics.inc 'unexpected-arguments', 1, { status: method }
|
|
|
|
return callback { message: error.message }
|
2017-11-10 10:01:23 -05:00
|
|
|
if error.message in ["not authorized", "doc updater could not load requested ops", "no project_id found on client"]
|
2016-05-31 09:21:23 -04:00
|
|
|
logger.warn attrs, error.message
|
2016-05-31 06:49:51 -04:00
|
|
|
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"}
|
2014-11-13 10:27:18 -05:00
|
|
|
|
2020-05-22 04:17:50 -04:00
|
|
|
_handleInvalidArguments: (client, method, args) ->
|
|
|
|
error = new Error("unexpected arguments")
|
|
|
|
callback = args[args.length - 1]
|
|
|
|
if typeof callback != 'function'
|
|
|
|
callback = (() ->)
|
|
|
|
attrs = {arguments: args}
|
|
|
|
Router._handleError(callback, error, client, method, attrs)
|
|
|
|
|
2014-11-07 12:38:12 -05:00
|
|
|
configure: (app, io, session) ->
|
2014-11-13 07:03:43 -05:00
|
|
|
app.set("io", io)
|
|
|
|
app.get "/clients", HttpController.getConnectedClients
|
|
|
|
app.get "/clients/:client_id", HttpController.getConnectedClient
|
2015-03-03 12:15:19 -05:00
|
|
|
|
|
|
|
app.post "/project/:project_id/message/:message", httpAuth, bodyParser.json(limit: "5mb"), HttpApiController.sendMessage
|
2016-10-24 11:36:09 -04:00
|
|
|
|
|
|
|
app.post "/drain", httpAuth, HttpApiController.startDrain
|
2020-02-24 07:28:22 -05:00
|
|
|
app.post "/client/:client_id/disconnect", httpAuth, HttpApiController.disconnectClient
|
2016-09-07 03:58:35 -04:00
|
|
|
|
2014-11-07 12:38:12 -05:00
|
|
|
session.on 'connection', (error, client, session) ->
|
2019-10-17 07:45:56 -04:00
|
|
|
client?.on "error", (err) ->
|
2020-02-03 09:47:45 -05:00
|
|
|
logger.err { clientErr: err }, "socket.io client error"
|
2019-10-17 07:45:56 -04:00
|
|
|
if client.connected
|
2020-02-05 05:05:36 -05:00
|
|
|
client.emit("reconnectGracefully")
|
|
|
|
client.disconnect()
|
2019-10-17 07:45:56 -04:00
|
|
|
|
2019-08-13 11:59:15 -04:00
|
|
|
if settings.shutDownInProgress
|
2020-02-05 05:05:36 -05:00
|
|
|
client.emit("connectionRejected", {message: "retry"})
|
|
|
|
client.disconnect()
|
2019-08-13 11:59:15 -04:00
|
|
|
return
|
|
|
|
|
2016-10-28 10:40:03 -04:00
|
|
|
if client? and error?.message?.match(/could not look up session by key/)
|
2017-11-10 10:01:23 -05:00
|
|
|
logger.warn err: error, client: client?, session: session?, "invalid session"
|
2016-10-28 10:40:03 -04:00
|
|
|
# tell the client to reauthenticate if it has an invalid session key
|
2020-02-05 05:05:36 -05:00
|
|
|
client.emit("connectionRejected", {message: "invalid session"})
|
|
|
|
client.disconnect()
|
2016-10-28 10:40:03 -04:00
|
|
|
return
|
|
|
|
|
2014-11-07 12:38:12 -05:00
|
|
|
if error?
|
2016-10-28 10:40:03 -04:00
|
|
|
logger.err err: error, client: client?, session: session?, "error when client connected"
|
2020-02-05 05:05:36 -05:00
|
|
|
client?.emit("connectionRejected", {message: "error"})
|
|
|
|
client?.disconnect()
|
2014-11-07 12:38:12 -05:00
|
|
|
return
|
2016-09-07 03:58:35 -04:00
|
|
|
|
2016-10-28 10:40:03 -04:00
|
|
|
# send positive confirmation that the client has a valid connection
|
2020-06-08 06:29:40 -04:00
|
|
|
client.publicId = 'P.' + base64id.generateId()
|
2020-06-04 10:52:13 -04:00
|
|
|
client.emit("connectionAccepted", null, client.publicId)
|
2016-10-28 10:40:03 -04:00
|
|
|
|
2014-11-17 08:12:49 -05:00
|
|
|
metrics.inc('socket-io.connection')
|
2019-08-14 10:22:03 -04:00
|
|
|
metrics.gauge('socket-io.clients', io.sockets.clients()?.length)
|
2016-09-07 03:58:35 -04:00
|
|
|
|
2014-11-12 10:54:55 -05:00
|
|
|
logger.log session: session, client_id: client.id, "client connected"
|
2016-09-07 03:58:35 -04:00
|
|
|
|
|
|
|
if session?.passport?.user?
|
|
|
|
user = session.passport.user
|
|
|
|
else if session?.user?
|
2014-11-21 06:48:59 -05:00
|
|
|
user = session.user
|
2016-09-07 03:58:35 -04:00
|
|
|
else
|
|
|
|
user = {_id: "anonymous-user"}
|
|
|
|
|
2014-11-10 06:27:08 -05:00
|
|
|
client.on "joinProject", (data = {}, callback) ->
|
2020-05-22 04:17:50 -04:00
|
|
|
if typeof callback != 'function'
|
|
|
|
return Router._handleInvalidArguments(client, 'joinProject', arguments)
|
|
|
|
|
2017-10-20 05:10:58 -04:00
|
|
|
if data.anonymousAccessToken
|
|
|
|
user.anonymousAccessToken = data.anonymousAccessToken
|
2014-11-12 10:54:55 -05:00
|
|
|
WebsocketController.joinProject client, user, data.project_id, (err, args...) ->
|
|
|
|
if err?
|
2014-11-13 10:27:18 -05:00
|
|
|
Router._handleError callback, err, client, "joinProject", {project_id: data.project_id, user_id: user?.id}
|
2014-11-12 10:54:55 -05:00
|
|
|
else
|
|
|
|
callback(null, args...)
|
2016-09-07 03:58:35 -04:00
|
|
|
|
2014-11-17 07:23:30 -05:00
|
|
|
client.on "disconnect", () ->
|
2014-11-17 08:12:49 -05:00
|
|
|
metrics.inc('socket-io.disconnect')
|
2019-08-14 10:34:23 -04:00
|
|
|
metrics.gauge('socket-io.clients', io.sockets.clients()?.length - 1)
|
2014-11-17 07:23:30 -05:00
|
|
|
WebsocketController.leaveProject io, client, (err) ->
|
|
|
|
if err?
|
|
|
|
Router._handleError null, err, client, "leaveProject"
|
2016-09-07 03:58:35 -04:00
|
|
|
|
2017-09-21 10:19:19 -04:00
|
|
|
# Variadic. The possible arguments:
|
2017-09-21 09:58:49 -04:00
|
|
|
# doc_id, callback
|
|
|
|
# doc_id, fromVersion, callback
|
|
|
|
# doc_id, options, callback
|
2017-09-21 10:19:19 -04:00
|
|
|
# doc_id, fromVersion, options, callback
|
|
|
|
client.on "joinDoc", (doc_id, fromVersion, options, callback) ->
|
2017-09-21 11:55:49 -04:00
|
|
|
if typeof fromVersion == "function" and !options
|
2014-11-12 10:54:55 -05:00
|
|
|
callback = fromVersion
|
|
|
|
fromVersion = -1
|
2017-09-21 11:55:49 -04:00
|
|
|
options = {}
|
|
|
|
else if typeof fromVersion == "number" and typeof options == "function"
|
2017-09-21 10:19:19 -04:00
|
|
|
callback = options
|
|
|
|
options = {}
|
2017-09-21 11:55:49 -04:00
|
|
|
else if typeof fromVersion == "object" and typeof options == "function"
|
2017-09-21 10:19:19 -04:00
|
|
|
callback = options
|
2017-09-21 11:55:49 -04:00
|
|
|
options = fromVersion
|
|
|
|
fromVersion = -1
|
2020-05-22 04:17:50 -04:00
|
|
|
else if typeof fromVersion == "number" and typeof options == "object" and typeof callback == 'function'
|
2017-09-21 11:55:49 -04:00
|
|
|
# Called with 4 args, things are as expected
|
|
|
|
else
|
2020-05-22 04:17:50 -04:00
|
|
|
return Router._handleInvalidArguments(client, 'joinDoc', arguments)
|
2016-09-07 03:58:35 -04:00
|
|
|
|
2017-09-21 11:56:09 -04:00
|
|
|
WebsocketController.joinDoc client, doc_id, fromVersion, options, (err, args...) ->
|
2014-11-12 10:54:55 -05:00
|
|
|
if err?
|
2014-11-13 10:27:18 -05:00
|
|
|
Router._handleError callback, err, client, "joinDoc", {doc_id, fromVersion}
|
2014-11-12 10:54:55 -05:00
|
|
|
else
|
|
|
|
callback(null, args...)
|
2016-09-07 03:58:35 -04:00
|
|
|
|
2014-11-12 11:51:48 -05:00
|
|
|
client.on "leaveDoc", (doc_id, callback) ->
|
2020-05-22 04:17:50 -04:00
|
|
|
if typeof callback != 'function'
|
|
|
|
return Router._handleInvalidArguments(client, 'leaveDoc', arguments)
|
|
|
|
|
2014-11-12 11:51:48 -05:00
|
|
|
WebsocketController.leaveDoc client, doc_id, (err, args...) ->
|
|
|
|
if err?
|
2014-11-13 10:27:18 -05:00
|
|
|
Router._handleError callback, err, client, "leaveDoc"
|
2014-11-12 11:51:48 -05:00
|
|
|
else
|
2014-11-13 06:48:49 -05:00
|
|
|
callback(null, args...)
|
2016-09-07 03:58:35 -04:00
|
|
|
|
2014-11-13 10:27:18 -05:00
|
|
|
client.on "clientTracking.getConnectedUsers", (callback = (error, users) ->) ->
|
2020-05-22 04:17:50 -04:00
|
|
|
if typeof callback != 'function'
|
|
|
|
return Router._handleInvalidArguments(client, 'clientTracking.getConnectedUsers', arguments)
|
|
|
|
|
2014-11-13 08:05:49 -05:00
|
|
|
WebsocketController.getConnectedUsers client, (err, users) ->
|
|
|
|
if err?
|
2014-11-13 10:27:18 -05:00
|
|
|
Router._handleError callback, err, client, "clientTracking.getConnectedUsers"
|
2014-11-13 08:05:49 -05:00
|
|
|
else
|
|
|
|
callback(null, users)
|
2016-09-07 03:58:35 -04:00
|
|
|
|
2014-11-13 10:27:18 -05:00
|
|
|
client.on "clientTracking.updatePosition", (cursorData, callback = (error) ->) ->
|
2020-05-22 04:17:50 -04:00
|
|
|
if typeof callback != 'function'
|
|
|
|
return Router._handleInvalidArguments(client, 'clientTracking.updatePosition', arguments)
|
|
|
|
|
2014-11-13 10:27:18 -05:00
|
|
|
WebsocketController.updateClientPosition client, cursorData, (err) ->
|
|
|
|
if err?
|
|
|
|
Router._handleError callback, err, client, "clientTracking.updatePosition"
|
2014-11-14 05:12:35 -05:00
|
|
|
else
|
|
|
|
callback()
|
2016-09-07 03:58:35 -04:00
|
|
|
|
2014-11-14 05:12:35 -05:00
|
|
|
client.on "applyOtUpdate", (doc_id, update, callback = (error) ->) ->
|
2020-05-22 04:17:50 -04:00
|
|
|
if typeof callback != 'function'
|
|
|
|
return Router._handleInvalidArguments(client, 'applyOtUpdate', arguments)
|
|
|
|
|
2014-11-14 05:12:35 -05:00
|
|
|
WebsocketController.applyOtUpdate client, doc_id, update, (err) ->
|
|
|
|
if err?
|
|
|
|
Router._handleError callback, err, client, "applyOtUpdate", {doc_id, update}
|
2014-11-13 10:27:18 -05:00
|
|
|
else
|
2016-05-31 06:49:51 -04:00
|
|
|
callback()
|