WebsocketLoadBalancer.coffee 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. Settings = require 'settings-sharelatex'
  2. logger = require 'logger-sharelatex'
  3. RedisClientManager = require "./RedisClientManager"
  4. SafeJsonParse = require "./SafeJsonParse"
  5. EventLogger = require "./EventLogger"
  6. HealthCheckManager = require "./HealthCheckManager"
  7. RoomManager = require "./RoomManager"
  8. ChannelManager = require "./ChannelManager"
  9. ConnectedUsersManager = require "./ConnectedUsersManager"
  10. Utils = require './Utils'
  11. Async = require 'async'
  12. RESTRICTED_USER_MESSAGE_TYPE_PASS_LIST = [
  13. 'connectionAccepted',
  14. 'otUpdateApplied',
  15. 'otUpdateError',
  16. 'joinDoc',
  17. 'reciveNewDoc',
  18. 'reciveNewFile',
  19. 'reciveNewFolder',
  20. 'removeEntity'
  21. ]
  22. module.exports = WebsocketLoadBalancer =
  23. rclientPubList: RedisClientManager.createClientList(Settings.redis.pubsub)
  24. rclientSubList: RedisClientManager.createClientList(Settings.redis.pubsub)
  25. emitToRoom: (room_id, message, payload...) ->
  26. if !room_id?
  27. logger.warn {message, payload}, "no room_id provided, ignoring emitToRoom"
  28. return
  29. data = JSON.stringify
  30. room_id: room_id
  31. message: message
  32. payload: payload
  33. logger.log {room_id, message, payload, length: data.length}, "emitting to room"
  34. for rclientPub in @rclientPubList
  35. ChannelManager.publish rclientPub, "editor-events", room_id, data
  36. emitToAll: (message, payload...) ->
  37. @emitToRoom "all", message, payload...
  38. listenForEditorEvents: (io) ->
  39. logger.log {rclients: @rclientPubList.length}, "publishing editor events"
  40. logger.log {rclients: @rclientSubList.length}, "listening for editor events"
  41. for rclientSub in @rclientSubList
  42. rclientSub.subscribe "editor-events"
  43. rclientSub.on "message", (channel, message) ->
  44. EventLogger.debugEvent(channel, message) if Settings.debugEvents > 0
  45. WebsocketLoadBalancer._processEditorEvent io, channel, message
  46. @handleRoomUpdates(@rclientSubList)
  47. handleRoomUpdates: (rclientSubList) ->
  48. roomEvents = RoomManager.eventSource()
  49. roomEvents.on 'project-active', (project_id) ->
  50. subscribePromises = for rclient in rclientSubList
  51. ChannelManager.subscribe rclient, "editor-events", project_id
  52. RoomManager.emitOnCompletion(subscribePromises, "project-subscribed-#{project_id}")
  53. roomEvents.on 'project-empty', (project_id) ->
  54. for rclient in rclientSubList
  55. ChannelManager.unsubscribe rclient, "editor-events", project_id
  56. _processEditorEvent: (io, channel, message) ->
  57. SafeJsonParse.parse message, (error, message) ->
  58. if error?
  59. logger.error {err: error, channel}, "error parsing JSON"
  60. return
  61. if message.room_id == "all"
  62. io.sockets.emit(message.message, message.payload...)
  63. else if message.message is 'clientTracking.refresh' && message.room_id?
  64. clientList = io.sockets.clients(message.room_id)
  65. logger.log {channel:channel, message: message.message, room_id: message.room_id, message_id: message._id, socketIoClients: (client.id for client in clientList)}, "refreshing client list"
  66. for client in clientList
  67. ConnectedUsersManager.refreshClient(message.room_id, client.publicId)
  68. else if message.room_id?
  69. if message._id? && Settings.checkEventOrder
  70. status = EventLogger.checkEventOrder("editor-events", message._id, message)
  71. if status is "duplicate"
  72. return # skip duplicate events
  73. # send messages only to unique clients (due to duplicate entries in io.sockets.clients)
  74. clientList = io.sockets.clients(message.room_id)
  75. # avoid unnecessary work if no clients are connected
  76. return if clientList.length is 0
  77. logger.log {
  78. channel: channel,
  79. message: message.message,
  80. room_id: message.room_id,
  81. message_id: message._id,
  82. socketIoClients: (client.id for client in clientList)
  83. }, "distributing event to clients"
  84. seen = {}
  85. # Send the messages to clients async, don't wait for them all to finish
  86. Async.eachLimit clientList
  87. , 2
  88. , (client, cb) ->
  89. Utils.getClientAttributes client, ['is_restricted_user'], (err, {is_restricted_user}) ->
  90. return cb(err) if err?
  91. if !seen[client.id]
  92. seen[client.id] = true
  93. if !(is_restricted_user && message.message not in RESTRICTED_USER_MESSAGE_TYPE_PASS_LIST)
  94. client.emit(message.message, message.payload...)
  95. cb()
  96. , (err) ->
  97. if err?
  98. logger.err {err, message}, "Error sending message to clients"
  99. else if message.health_check?
  100. logger.debug {message}, "got health check message in editor events channel"
  101. HealthCheckManager.check channel, message.key