# Responsible for ensuring the cable connection is in good health by validating the heartbeat pings sent from the server, and attempting # revival reconnections if things go astray. Internal class, not intended for direct user manipulation. class ActionCable.ConnectionMonitor

@pollInterval:
  min: 3
  max: 30

@staleThreshold: 6 # Server::Connections::BEAT_INTERVAL * 2 (missed two pings)

constructor: (@connection) ->
  @reconnectAttempts = 0

start: ->
  unless @isRunning()
    @startedAt = now()
    delete @stoppedAt
    @startPolling()
    document.addEventListener("visibilitychange", @visibilityDidChange)
    ActionCable.log("ConnectionMonitor started. pollInterval = #{@getPollInterval()} ms")

stop: ->
  if @isRunning()
    @stoppedAt = now()
    @stopPolling()
    document.removeEventListener("visibilitychange", @visibilityDidChange)
    ActionCable.log("ConnectionMonitor stopped")

isRunning: ->
  @startedAt? and not @stoppedAt?

recordPing: ->
  @pingedAt = now()

recordConnect: ->
  @reconnectAttempts = 0
  @recordPing()
  delete @disconnectedAt
  ActionCable.log("ConnectionMonitor recorded connect")

recordDisconnect: ->
  @disconnectedAt = now()
  ActionCable.log("ConnectionMonitor recorded disconnect")

# Private

startPolling: ->
  @stopPolling()
  @poll()

stopPolling: ->
  clearTimeout(@pollTimeout)

poll: ->
  @pollTimeout = setTimeout =>
    @reconnectIfStale()
    @poll()
  , @getPollInterval()

getPollInterval: ->
  {min, max} = @constructor.pollInterval
  interval = 5 * Math.log(@reconnectAttempts + 1)
  Math.round(clamp(interval, min, max) * 1000)

reconnectIfStale: ->
  if @connectionIsStale()
    ActionCable.log("ConnectionMonitor detected stale connection. reconnectAttempts = #{@reconnectAttempts}, pollInterval = #{@getPollInterval()} ms, time disconnected = #{secondsSince(@disconnectedAt)} s, stale threshold = #{@constructor.staleThreshold} s")
    @reconnectAttempts++
    if @disconnectedRecently()
      ActionCable.log("ConnectionMonitor skipping reopening recent disconnect")
    else
      ActionCable.log("ConnectionMonitor reopening")
      @connection.reopen()

connectionIsStale: ->
  secondsSince(@pingedAt ? @startedAt) > @constructor.staleThreshold

disconnectedRecently: ->
  @disconnectedAt and secondsSince(@disconnectedAt) < @constructor.staleThreshold

visibilityDidChange: =>
  if document.visibilityState is "visible"
    setTimeout =>
      if @connectionIsStale() or not @connection.isOpen()
        ActionCable.log("ConnectionMonitor reopening stale connection on visibilitychange. visbilityState = #{document.visibilityState}")
        @connection.reopen()
    , 200

now = ->
  new Date().getTime()

secondsSince = (time) ->
  (now() - time) / 1000

clamp = (number, min, max) ->
  Math.max(min, Math.min(max, number))