WebsocketLoadBalancer.js 5.1 KB

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