WebsocketLoadBalancer.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. /* eslint-disable
  2. camelcase,
  3. */
  4. const Settings = require('@overleaf/settings')
  5. const logger = require('@overleaf/logger')
  6. const RedisClientManager = require('./RedisClientManager')
  7. const SafeJsonParse = require('./SafeJsonParse')
  8. const EventLogger = require('./EventLogger')
  9. const HealthCheckManager = require('./HealthCheckManager')
  10. const RoomManager = require('./RoomManager')
  11. const ChannelManager = require('./ChannelManager')
  12. const ConnectedUsersManager = require('./ConnectedUsersManager')
  13. const RESTRICTED_USER_MESSAGE_TYPE_PASS_LIST = [
  14. 'connectionAccepted',
  15. 'otUpdateApplied',
  16. 'otUpdateError',
  17. 'joinDoc',
  18. 'reciveNewDoc',
  19. 'reciveNewFile',
  20. 'reciveNewFolder',
  21. 'removeEntity',
  22. 'accept-changes',
  23. ]
  24. let WebsocketLoadBalancer
  25. module.exports = WebsocketLoadBalancer = {
  26. rclientPubList: RedisClientManager.createClientList(Settings.redis.pubsub),
  27. rclientSubList: RedisClientManager.createClientList(Settings.redis.pubsub),
  28. emitToRoom(room_id, message, ...payload) {
  29. if (!room_id) {
  30. logger.warn(
  31. { message, payload },
  32. 'no room_id provided, ignoring emitToRoom'
  33. )
  34. return
  35. }
  36. const data = JSON.stringify({
  37. room_id,
  38. message,
  39. payload,
  40. })
  41. logger.debug(
  42. { room_id, message, payload, length: data.length },
  43. 'emitting to room'
  44. )
  45. this.rclientPubList.map(rclientPub =>
  46. ChannelManager.publish(rclientPub, 'editor-events', room_id, data)
  47. )
  48. },
  49. emitToAll(message, ...payload) {
  50. this.emitToRoom('all', message, ...payload)
  51. },
  52. listenForEditorEvents(io) {
  53. logger.debug(
  54. { rclients: this.rclientSubList.length },
  55. 'listening for editor events'
  56. )
  57. for (const rclientSub of this.rclientSubList) {
  58. rclientSub.subscribe('editor-events')
  59. rclientSub.on('message', function (channel, message) {
  60. if (Settings.debugEvents > 0) {
  61. EventLogger.debugEvent(channel, message)
  62. }
  63. WebsocketLoadBalancer._processEditorEvent(io, channel, message)
  64. })
  65. }
  66. this.handleRoomUpdates(this.rclientSubList)
  67. },
  68. handleRoomUpdates(rclientSubList) {
  69. const roomEvents = RoomManager.eventSource()
  70. roomEvents.on('project-active', function (project_id) {
  71. const subscribePromises = rclientSubList.map(rclient =>
  72. ChannelManager.subscribe(rclient, 'editor-events', project_id)
  73. )
  74. RoomManager.emitOnCompletion(
  75. subscribePromises,
  76. `project-subscribed-${project_id}`
  77. )
  78. })
  79. roomEvents.on('project-empty', project_id =>
  80. rclientSubList.map(rclient =>
  81. ChannelManager.unsubscribe(rclient, 'editor-events', project_id)
  82. )
  83. )
  84. },
  85. _processEditorEvent(io, channel, message) {
  86. SafeJsonParse.parse(message, function (error, message) {
  87. if (error) {
  88. logger.error({ err: error, channel }, 'error parsing JSON')
  89. return
  90. }
  91. if (message.room_id === 'all') {
  92. io.sockets.emit(message.message, ...message.payload)
  93. } else if (
  94. message.message === 'clientTracking.refresh' &&
  95. message.room_id
  96. ) {
  97. const clientList = io.sockets.clients(message.room_id)
  98. logger.debug(
  99. {
  100. channel,
  101. message: message.message,
  102. room_id: message.room_id,
  103. message_id: message._id,
  104. socketIoClients: clientList.map(client => client.id),
  105. },
  106. 'refreshing client list'
  107. )
  108. for (const client of clientList) {
  109. ConnectedUsersManager.refreshClient(message.room_id, client.publicId)
  110. }
  111. } else if (message.room_id) {
  112. if (message._id && Settings.checkEventOrder) {
  113. const status = EventLogger.checkEventOrder(
  114. 'editor-events',
  115. message._id,
  116. message
  117. )
  118. if (status === 'duplicate') {
  119. return // skip duplicate events
  120. }
  121. }
  122. const is_restricted_message =
  123. !RESTRICTED_USER_MESSAGE_TYPE_PASS_LIST.includes(message.message)
  124. // send messages only to unique clients (due to duplicate entries in io.sockets.clients)
  125. const clientList = io.sockets
  126. .clients(message.room_id)
  127. .filter(
  128. client =>
  129. !(is_restricted_message && client.ol_context.is_restricted_user)
  130. )
  131. // avoid unnecessary work if no clients are connected
  132. if (clientList.length === 0) {
  133. return
  134. }
  135. logger.debug(
  136. {
  137. channel,
  138. message: message.message,
  139. room_id: message.room_id,
  140. message_id: message._id,
  141. socketIoClients: clientList.map(client => client.id),
  142. },
  143. 'distributing event to clients'
  144. )
  145. const seen = new Map()
  146. for (const client of clientList) {
  147. if (!seen.has(client.id)) {
  148. seen.set(client.id, true)
  149. client.emit(message.message, ...message.payload)
  150. }
  151. }
  152. } else if (message.health_check) {
  153. logger.debug(
  154. { message },
  155. 'got health check message in editor events channel'
  156. )
  157. HealthCheckManager.check(channel, message.key)
  158. }
  159. })
  160. },
  161. }