diff --git a/services/web/public/coffee/ide/connection/ConnectionManager.coffee b/services/web/public/coffee/ide/connection/ConnectionManager.coffee
index 61d3d1c37d..afbc656a02 100644
--- a/services/web/public/coffee/ide/connection/ConnectionManager.coffee
+++ b/services/web/public/coffee/ide/connection/ConnectionManager.coffee
@@ -20,6 +20,12 @@ define [], () ->
@disconnectIfInactive()
, ONEHOUR)
+ # trigger a reconnect immediately if network comes back online
+ window.addEventListener 'online', =>
+ sl_console.log "[online] browser notified online"
+ if !@connected
+ @tryReconnectWithRateLimit({force:true})
+
@userIsLeavingPage = false
window.addEventListener 'beforeunload', =>
@userIsLeavingPage = true
@@ -94,7 +100,7 @@ define [], () ->
@ide.socket.on "connect_failed", () =>
@connected = false
$scope.$apply () =>
- @$scope.state.error = "Unable to connect, please view the connection problems guide to fix the issue."
+ @$scope.state.error = "Unable to connect, please view the connection problems guide to fix the issue."
# We can get a "disconnect" event at any point after the
# "connect" event.
@@ -226,8 +232,8 @@ define [], () ->
@lastConnectionAttempt = new Date()
setTimeout (=> @startAutoReconnectCountdown() if !@connected), 2000
- MIN_RETRY_INTERVAL: 1000 # ms
- BACKGROUND_RETRY_INTERVAL : 30 * 1000 # ms
+ MIN_RETRY_INTERVAL: 1000 # ms, rate limit on reconnects for user clicking "try now"
+ BACKGROUND_RETRY_INTERVAL : 5 * 1000 # ms, rate limit on reconnects for other user activity (e.g. cursor moves)
tryReconnectWithRateLimit: (options) ->
# bail out if the reconnect is already in progress
diff --git a/services/web/public/coffee/ide/editor/Document.coffee b/services/web/public/coffee/ide/editor/Document.coffee
index 005d722a56..9226473387 100644
--- a/services/web/public/coffee/ide/editor/Document.coffee
+++ b/services/web/public/coffee/ide/editor/Document.coffee
@@ -69,6 +69,12 @@ define [
getPendingOp: () ->
@doc?.getPendingOp()
+ getRecentAck: () ->
+ @doc?.getRecentAck()
+
+ getOpSize: (op) ->
+ @doc?.getOpSize(op)
+
hasBufferedOps: () ->
@doc?.hasBufferedOps()
@@ -143,24 +149,34 @@ define [
clearChaosMonkey: () ->
clearTimeout @_cm
+ MAX_PENDING_OP_SIZE: 30 # pending ops bigger than this are always considered unsaved
+
pollSavedStatus: () ->
# returns false if doc has ops waiting to be acknowledged or
# sent that haven't changed since the last time we checked.
# Otherwise returns true.
inflightOp = @getInflightOp()
pendingOp = @getPendingOp()
+ recentAck = @getRecentAck()
+ pendingOpSize = pendingOp? && @getOpSize(pendingOp)
if !inflightOp? and !pendingOp?
- # there's nothing going on
+ # there's nothing going on, this is ok.
saved = true
sl_console.log "[pollSavedStatus] no inflight or pending ops"
else if inflightOp? and inflightOp == @oldInflightOp
# The same inflight op has been sitting unacked since we
- # last checked.
+ # last checked, this is bad.
saved = false
sl_console.log "[pollSavedStatus] inflight op is same as before"
- else
+ else if pendingOp? and recentAck && pendingOpSize < @MAX_PENDING_OP_SIZE
+ # There is an op waiting to go to server but it is small and
+ # within the flushDelay, this is ok for now.
saved = true
- sl_console.log "[pollSavedStatus] assuming saved (inflightOp?: #{inflightOp?}, pendingOp?: #{pendingOp?})"
+ sl_console.log "[pollSavedStatus] pending op (small with recent ack) assume ok", pendingOp, pendingOpSize
+ else
+ # In any other situation, assume the document is unsaved.
+ saved = false
+ sl_console.log "[pollSavedStatus] assuming not saved (inflightOp?: #{inflightOp?}, pendingOp?: #{pendingOp?})"
@oldInflightOp = inflightOp
return saved
diff --git a/services/web/public/coffee/ide/editor/EditorManager.coffee b/services/web/public/coffee/ide/editor/EditorManager.coffee
index 698604c9c7..f0cbb4c048 100644
--- a/services/web/public/coffee/ide/editor/EditorManager.coffee
+++ b/services/web/public/coffee/ide/editor/EditorManager.coffee
@@ -109,7 +109,7 @@ define [
@ide.reportError(error, meta)
@ide.showGenericMessageModal(
"Out of sync"
- "Sorry, this file has gone out of sync and we need to do a full refresh.
Please see this help guide for more information"
+ "Sorry, this file has gone out of sync and we need to do a full refresh.
Please see this help guide for more information"
)
@openDoc(doc, forceReopen: true)
diff --git a/services/web/public/coffee/ide/editor/ShareJsDoc.coffee b/services/web/public/coffee/ide/editor/ShareJsDoc.coffee
index 4f9960f9a0..5d8b4ef11a 100644
--- a/services/web/public/coffee/ide/editor/ShareJsDoc.coffee
+++ b/services/web/public/coffee/ide/editor/ShareJsDoc.coffee
@@ -46,6 +46,7 @@ define [
@_doc.on "change", () =>
@trigger "change"
@_doc.on "acknowledge", () =>
+ @lastAcked = new Date() # note time of last ack from server for an op we sent
@trigger "acknowledge"
@_doc.on "remoteop", (args...) =>
# As soon as we're working with a collaborator, start sending
@@ -101,12 +102,26 @@ define [
@connection.id = @socket.socket.sessionid
@_doc.autoOpen = false
@_doc._connectionStateChanged(state)
+ @lastAcked = null # reset the last ack time when connection changes
hasBufferedOps: () ->
@_doc.inflightOp? or @_doc.pendingOp?
getInflightOp: () -> @_doc.inflightOp
getPendingOp: () -> @_doc.pendingOp
+ getRecentAck: () ->
+ # check if we have received an ack recently (within the flush delay)
+ @lastAcked? and new Date() - @lastAcked < @_doc._flushDelay
+ getOpSize: (op) ->
+ # compute size of an op from its components
+ # (total number of characters inserted and deleted)
+ size = 0
+ for component in op or []
+ if component?.i?
+ size += component.i.length
+ if component?.d?
+ size += component.d.length
+ return size
attachToAce: (ace) -> @_doc.attach_ace(ace, false, window.maxDocLength)
detachFromAce: () -> @_doc.detach_ace?()