ConnectionManager.coffee 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. define [], () ->
  2. ONEHOUR = 1000 * 60 * 60
  3. class ConnectionManager
  4. disconnectAfterMs: ONEHOUR * 24
  5. lastUserAction : new Date()
  6. constructor: (@ide, @$scope) ->
  7. if !io?
  8. console.error "Socket.io javascript not loaded. Please check that the real-time service is running and accessible."
  9. @ide.socket =
  10. on: () ->
  11. $scope.$apply () =>
  12. @$scope.state.error = "Could not connect to websocket server :("
  13. return
  14. setInterval(() =>
  15. @disconnectIfInactive()
  16. , ONEHOUR)
  17. @userIsLeavingPage = false
  18. window.addEventListener 'beforeunload', =>
  19. @userIsLeavingPage = true
  20. return # Don't return true or it will show a pop up
  21. @connected = false
  22. @userIsInactive = false
  23. @$scope.connection =
  24. reconnecting: false
  25. # If we need to force everyone to reload the editor
  26. forced_disconnect: false
  27. inactive_disconnect: false
  28. @$scope.tryReconnectNow = () =>
  29. @tryReconnect()
  30. @$scope.$on 'cursor:editor:update', () =>
  31. @lastUserAction = new Date()
  32. if !@connected
  33. @tryReconnect()
  34. document.querySelector('body').addEventListener 'click', (e) =>
  35. if !@connected and e.target.id != 'try-reconnect-now-button'
  36. @tryReconnect()
  37. @ide.socket = io.connect null,
  38. reconnect: false
  39. 'connect timeout': 30 * 1000
  40. "force new connection": true
  41. @ide.socket.on "connect", () =>
  42. sl_console.log "[socket.io connect] Connected"
  43. @connected = true
  44. @ide.pushEvent("connected")
  45. @$scope.$apply () =>
  46. @$scope.connection.reconnecting = false
  47. @$scope.connection.inactive_disconnect = false
  48. if @$scope.state.loading
  49. @$scope.state.load_progress = 70
  50. setTimeout(() =>
  51. @joinProject()
  52. , 100)
  53. @ide.socket.on "connect_failed", () =>
  54. @connected = false
  55. $scope.$apply () =>
  56. @$scope.state.error = "Unable to connect, please view the <u><a href='http://sharelatex.tenderapp.com/help/kb/latex-editor/editor-connection-problems'>connection problems guide</a></u> to fix the issue."
  57. @ide.socket.on 'disconnect', () =>
  58. sl_console.log "[socket.io disconnect] Disconnected"
  59. @connected = false
  60. @ide.pushEvent("disconnected")
  61. @$scope.$apply () =>
  62. @$scope.connection.reconnecting = false
  63. if !$scope.connection.forced_disconnect and !@userIsInactive
  64. @startAutoReconnectCountdown()
  65. @ide.socket.on 'forceDisconnect', (message) =>
  66. @$scope.$apply () =>
  67. @$scope.permissions.write = false
  68. @$scope.connection.forced_disconnect = true
  69. @ide.socket.disconnect()
  70. @ide.showGenericMessageModal("Please Refresh", """
  71. We're performing maintenance on ShareLaTeX and you need to refresh the editor.
  72. Sorry for any inconvenience.
  73. The editor will refresh in automatically in 10 seconds.
  74. """)
  75. setTimeout () ->
  76. location.reload()
  77. , 10 * 1000
  78. joinProject: () ->
  79. sl_console.log "[joinProject] joining..."
  80. @ide.socket.emit 'joinProject', {
  81. project_id: @ide.project_id
  82. }, (err, project, permissionsLevel, protocolVersion) =>
  83. if err?
  84. if err.message == "not authorized"
  85. window.location = "/login?redir=#{encodeURI(window.location.pathname)}"
  86. else
  87. @ide.socket.disconnect()
  88. @ide.showGenericMessageModal("Something went wrong connecting", """
  89. Something went wrong connecting to your project. Please refresh is this continues to happen.
  90. """)
  91. return
  92. if @$scope.protocolVersion? and @$scope.protocolVersion != protocolVersion
  93. location.reload(true)
  94. @$scope.$apply () =>
  95. @$scope.protocolVersion = protocolVersion
  96. @$scope.project = project
  97. @$scope.permissionsLevel = permissionsLevel
  98. @$scope.state.load_progress = 100
  99. @$scope.state.loading = false
  100. @$scope.$broadcast "project:joined"
  101. reconnectImmediately: () ->
  102. @disconnect()
  103. @tryReconnect()
  104. disconnect: () ->
  105. @ide.socket.disconnect()
  106. startAutoReconnectCountdown: () ->
  107. twoMinutes = 2 * 60 * 1000
  108. if @lastUpdated? and new Date() - @lastUpdated > twoMinutes
  109. # between 1 minute and 3 minutes
  110. countdown = 60 + Math.floor(Math.random() * 120)
  111. else
  112. countdown = 3 + Math.floor(Math.random() * 7)
  113. if @userIsLeavingPage #user will have pressed refresh or back etc
  114. return
  115. @$scope.$apply () =>
  116. @$scope.connection.reconnecting = false
  117. @$scope.connection.reconnection_countdown = countdown
  118. setTimeout(=>
  119. if !@connected
  120. @timeoutId = setTimeout (=> @decreaseCountdown()), 1000
  121. , 200)
  122. cancelReconnect: () ->
  123. clearTimeout @timeoutId if @timeoutId?
  124. decreaseCountdown: () ->
  125. return if !@$scope.connection.reconnection_countdown?
  126. @$scope.$apply () =>
  127. @$scope.connection.reconnection_countdown--
  128. if @$scope.connection.reconnection_countdown <= 0
  129. @$scope.$apply () =>
  130. @tryReconnect()
  131. else
  132. @timeoutId = setTimeout (=> @decreaseCountdown()), 1000
  133. tryReconnect: () ->
  134. @cancelReconnect()
  135. delete @$scope.connection.reconnection_countdown
  136. return if @connected
  137. @$scope.connection.reconnecting = true
  138. @ide.socket.socket.reconnect()
  139. setTimeout (=> @startAutoReconnectCountdown() if !@connected), 2000
  140. disconnectIfInactive: ()->
  141. @userIsInactive = (new Date() - @lastUserAction) > @disconnectAfterMs
  142. if @userIsInactive and @connected
  143. @disconnect()
  144. @$scope.$apply () =>
  145. @$scope.connection.inactive_disconnect = true