|
|
@@ -28,32 +28,46 @@ define [], () ->
|
|
|
@connected = false
|
|
|
@userIsInactive = false
|
|
|
@gracefullyReconnecting = false
|
|
|
-
|
|
|
- @$scope.connection =
|
|
|
+
|
|
|
+ @$scope.connection =
|
|
|
reconnecting: false
|
|
|
# If we need to force everyone to reload the editor
|
|
|
forced_disconnect: false
|
|
|
inactive_disconnect: false
|
|
|
|
|
|
@$scope.tryReconnectNow = () =>
|
|
|
- @tryReconnect()
|
|
|
+ # user manually requested reconnection via "Try now" button
|
|
|
+ @tryReconnectWithRateLimit({force:true})
|
|
|
|
|
|
@$scope.$on 'cursor:editor:update', () =>
|
|
|
- @lastUserAction = new Date()
|
|
|
+ @lastUserAction = new Date() # time of last edit
|
|
|
if !@connected
|
|
|
- @tryReconnect()
|
|
|
+ # user is editing, try to reconnect
|
|
|
+ @tryReconnectWithRateLimit()
|
|
|
|
|
|
document.querySelector('body').addEventListener 'click', (e) =>
|
|
|
if !@connected and e.target.id != 'try-reconnect-now-button'
|
|
|
- @tryReconnect()
|
|
|
+ # user is editing, try to reconnect
|
|
|
+ @tryReconnectWithRateLimit()
|
|
|
|
|
|
@ide.socket = io.connect null,
|
|
|
reconnect: false
|
|
|
'connect timeout': 30 * 1000
|
|
|
"force new connection": true
|
|
|
|
|
|
+ # The "connect" event is the first event we get back. It only
|
|
|
+ # indicates that the websocket is connected, we still need to
|
|
|
+ # pass authentication to join a project.
|
|
|
+
|
|
|
@ide.socket.on "connect", () =>
|
|
|
sl_console.log "[socket.io connect] Connected"
|
|
|
+
|
|
|
+ # The next event we should get is an authentication response
|
|
|
+ # from the server, either "connectionAccepted" or
|
|
|
+ # "connectionRejected".
|
|
|
+
|
|
|
+ @ide.socket.on 'connectionAccepted', (message) =>
|
|
|
+ sl_console.log "[socket.io connectionAccepted] allowed to connect"
|
|
|
@connected = true
|
|
|
@gracefullyReconnecting = false
|
|
|
@ide.pushEvent("connected")
|
|
|
@@ -64,16 +78,26 @@ define [], () ->
|
|
|
if @$scope.state.loading
|
|
|
@$scope.state.load_progress = 70
|
|
|
|
|
|
+ # we have passed authentication so we can now join the project
|
|
|
setTimeout(() =>
|
|
|
@joinProject()
|
|
|
, 100)
|
|
|
|
|
|
+ @ide.socket.on 'connectionRejected', (err) =>
|
|
|
+ sl_console.log "[socket.io connectionRejected] session not valid or other connection error"
|
|
|
+ # we have failed authentication, usually due to an invalid session cookie
|
|
|
+ return @reportConnectionError(err)
|
|
|
+
|
|
|
+ # Alternatively the attempt to connect can fail completely, so
|
|
|
+ # we never get into the "connect" state.
|
|
|
+
|
|
|
@ide.socket.on "connect_failed", () =>
|
|
|
@connected = false
|
|
|
$scope.$apply () =>
|
|
|
@$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."
|
|
|
|
|
|
-
|
|
|
+ # We can get a "disconnect" event at any point after the
|
|
|
+ # "connect" event.
|
|
|
|
|
|
@ide.socket.on 'disconnect', () =>
|
|
|
sl_console.log "[socket.io disconnect] Disconnected"
|
|
|
@@ -86,6 +110,8 @@ define [], () ->
|
|
|
if !$scope.connection.forced_disconnect and !@userIsInactive and !@gracefullyReconnecting
|
|
|
@startAutoReconnectCountdown()
|
|
|
|
|
|
+ # Site administrators can send the forceDisconnect event to all users
|
|
|
+
|
|
|
@ide.socket.on 'forceDisconnect', (message) =>
|
|
|
@$scope.$apply () =>
|
|
|
@$scope.permissions.write = false
|
|
|
@@ -99,25 +125,33 @@ define [], () ->
|
|
|
setTimeout () ->
|
|
|
location.reload()
|
|
|
, 10 * 1000
|
|
|
-
|
|
|
+
|
|
|
@ide.socket.on "reconnectGracefully", () =>
|
|
|
sl_console.log "Reconnect gracefully"
|
|
|
@reconnectGracefully()
|
|
|
|
|
|
+ # Error reporting, which can reload the page if appropriate
|
|
|
+
|
|
|
+ reportConnectionError: (err) ->
|
|
|
+ sl_console.log "[socket.io] reporting connection error"
|
|
|
+ if err?.message == "not authorized" or err?.message == "invalid session"
|
|
|
+ window.location = "/login?redir=#{encodeURI(window.location.pathname)}"
|
|
|
+ else
|
|
|
+ @ide.socket.disconnect()
|
|
|
+ @ide.showGenericMessageModal("Something went wrong connecting", """
|
|
|
+ Something went wrong connecting to your project. Please refresh is this continues to happen.
|
|
|
+ """)
|
|
|
+
|
|
|
joinProject: () ->
|
|
|
sl_console.log "[joinProject] joining..."
|
|
|
+ # Note: if the "joinProject" message doesn't reach the server
|
|
|
+ # (e.g. if we are in a disconnected state at this point) the
|
|
|
+ # callback will never be executed
|
|
|
@ide.socket.emit 'joinProject', {
|
|
|
project_id: @ide.project_id
|
|
|
}, (err, project, permissionsLevel, protocolVersion) =>
|
|
|
if err?
|
|
|
- if err.message == "not authorized"
|
|
|
- window.location = "/login?redir=#{encodeURI(window.location.pathname)}"
|
|
|
- else
|
|
|
- @ide.socket.disconnect()
|
|
|
- @ide.showGenericMessageModal("Something went wrong connecting", """
|
|
|
- Something went wrong connecting to your project. Please refresh is this continues to happen.
|
|
|
- """)
|
|
|
- return
|
|
|
+ return @reportConnectionError(err)
|
|
|
|
|
|
if @$scope.protocolVersion? and @$scope.protocolVersion != protocolVersion
|
|
|
location.reload(true)
|
|
|
@@ -135,11 +169,13 @@ define [], () ->
|
|
|
@tryReconnect()
|
|
|
|
|
|
disconnect: () ->
|
|
|
+ sl_console.log "[socket.io] disconnecting client"
|
|
|
@ide.socket.disconnect()
|
|
|
|
|
|
startAutoReconnectCountdown: () ->
|
|
|
+ sl_console.log "[ConnectionManager] starting autoreconnect countdown"
|
|
|
twoMinutes = 2 * 60 * 1000
|
|
|
- if @lastUpdated? and new Date() - @lastUpdated > twoMinutes
|
|
|
+ if @lastUserAction? and new Date() - @lastUserAction > twoMinutes
|
|
|
# between 1 minute and 3 minutes
|
|
|
countdown = 60 + Math.floor(Math.random() * 120)
|
|
|
else
|
|
|
@@ -158,10 +194,16 @@ define [], () ->
|
|
|
, 200)
|
|
|
|
|
|
cancelReconnect: () ->
|
|
|
- clearTimeout @timeoutId if @timeoutId?
|
|
|
-
|
|
|
+ # clear timeout and set to null so we know there is no countdown running
|
|
|
+ if @timeoutId?
|
|
|
+ sl_console.log "[ConnectionManager] cancelling existing reconnect timer"
|
|
|
+ clearTimeout @timeoutId
|
|
|
+ @timeoutId = null
|
|
|
+
|
|
|
decreaseCountdown: () ->
|
|
|
+ @timeoutId = null
|
|
|
return if !@$scope.connection.reconnection_countdown?
|
|
|
+ sl_console.log "[ConnectionManager] decreasing countdown", @$scope.connection.reconnection_countdown
|
|
|
@$scope.$apply () =>
|
|
|
@$scope.connection.reconnection_countdown--
|
|
|
|
|
|
@@ -172,13 +214,33 @@ define [], () ->
|
|
|
@timeoutId = setTimeout (=> @decreaseCountdown()), 1000
|
|
|
|
|
|
tryReconnect: () ->
|
|
|
+ sl_console.log "[ConnectionManager] tryReconnect"
|
|
|
@cancelReconnect()
|
|
|
delete @$scope.connection.reconnection_countdown
|
|
|
return if @connected
|
|
|
@$scope.connection.reconnecting = true
|
|
|
- @ide.socket.socket.reconnect()
|
|
|
+ # use socket.io connect() here to make a single attempt, the
|
|
|
+ # reconnect() method makes multiple attempts
|
|
|
+ @ide.socket.socket.connect()
|
|
|
+ # record the time of the last attempt to connect
|
|
|
+ @lastConnectionAttempt = new Date()
|
|
|
setTimeout (=> @startAutoReconnectCountdown() if !@connected), 2000
|
|
|
|
|
|
+ MIN_RETRY_INTERVAL: 1000 # ms
|
|
|
+ BACKGROUND_RETRY_INTERVAL : 30 * 1000 # ms
|
|
|
+
|
|
|
+ tryReconnectWithRateLimit: (options) ->
|
|
|
+ # bail out if the reconnect is already in progress
|
|
|
+ return if @$scope.connection?.reconnecting
|
|
|
+ # bail out if we are going to reconnect soon anyway
|
|
|
+ reconnectingSoon = @$scope.connection?.reconnection_countdown? and @$scope.connection.reconnection_countdown <= 5
|
|
|
+ clickedTryNow = options?.force # user requested reconnection
|
|
|
+ return if reconnectingSoon and not clickedTryNow
|
|
|
+ # bail out if we tried reconnecting recently
|
|
|
+ allowedInterval = if clickedTryNow then @MIN_RETRY_INTERVAL else @BACKGROUND_RETRY_INTERVAL
|
|
|
+ return if @lastConnectionAttempt? and new Date() - @lastConnectionAttempt < allowedInterval
|
|
|
+ @tryReconnect()
|
|
|
+
|
|
|
disconnectIfInactive: ()->
|
|
|
@userIsInactive = (new Date() - @lastUserAction) > @disconnectAfterMs
|
|
|
if @userIsInactive and @connected
|
|
|
@@ -200,10 +262,10 @@ define [], () ->
|
|
|
setTimeout () =>
|
|
|
@reconnectGracefully()
|
|
|
, @RECONNECT_GRACEFULLY_RETRY_INTERVAL
|
|
|
-
|
|
|
+
|
|
|
_reconnectGracefullyNow: () ->
|
|
|
@gracefullyReconnecting = true
|
|
|
@reconnectGracefullyStarted = null
|
|
|
# Clear cookie so we don't go to the same backend server
|
|
|
$.cookie("SERVERID", "", { expires: -1, path: "/" })
|
|
|
- @reconnectImmediately()
|
|
|
+ @reconnectImmediately()
|