INITIAL_PING_TIMEOUT = 2000 KEEPALIVE_PING_TIMEOUT = 20000
class Firehose.WebSocket extends Firehose.Transport
name: -> 'WebSocket' @ieSupported:-> (document.documentMode || 10) > 9 @supported :-> (if module?.exports? then global else window).WebSocket? # Check if WebSocket is an object in the window. constructor: (args) -> super args # Configrations specifically for web sockets @config.webSocket ||= {} @config.webSocket.connectionVerified = @config.connectionVerified _sendMessage: (message) => @socket?.send(JSON.stringify message) _request: => # Run this in a try/catch block because IE10 inside of a .NET control # complains about security zones. try @socket = new (if module?.exports? then global else window).WebSocket "#{@_protocol()}:#{@config.uri}?#{$.param @_requestParams()}" @socket.onopen = @_open @socket.onclose = @_close @socket.onerror = @_error @socket.onmessage = @_lookForInitialPong catch err console?.log(err) # Protocol schema we should use for talking to firehose server. _protocol: => if @config.ssl then "wss" else "ws" _requestParams: => @config.params _open: => sendPing @socket _lookForInitialPong: (event) => @_restartKeepAlive() if isPong(try JSON.parse event.data catch e then {}) if @_lastMessageSequence? # don't callback to connectionVerified on subsequent reconnects @sendStartingMessageSequence @_lastMessageSequence else @config.webSocket.connectionVerified @ sendStartingMessageSequence: (message_sequence) => @_lastMessageSequence = message_sequence @socket.onmessage = @_message @_sendMessage({message_sequence}) @_needToNotifyOfDisconnect = true Firehose.Transport::_open.call @ stop: => @_cleanUp() _message: (event) => frame = @config.parse event.data @_restartKeepAlive() unless isPong frame try @_lastMessageSequence = frame.last_sequence @config.message @config.parse frame.message catch e _close: (event) => if event?.wasClean then @_cleanUp() else @_error event _error: (event) => @_cleanUp() if @_needToNotifyOfDisconnect @_needToNotifyOfDisconnect = false @config.disconnected() if @_succeeded @connect @_retryDelay else if @config.failed @config.failed @ _cleanUp: => @_clearKeepalive() if @socket? @socket.onopen = null @socket.onclose = null @socket.onerror = null @socket.onmessage = null @socket.close() delete @socket _restartKeepAlive: => doPing = => sendPing @socket setNextKeepAlive() setNextKeepAlive = => @keepaliveTimeout = setTimeout doPing, KEEPALIVE_PING_TIMEOUT @_clearKeepalive() setNextKeepAlive() _clearKeepalive: => if @keepaliveTimeout? clearTimeout @keepaliveTimeout @keepaliveTimeout = null
class Firehose.MultiplexedWebSocket extends Firehose.WebSocket
constructor: (args) -> super args subscribe: (channel, opts) => @_sendMessage multiplex_subscribe: channel: channel message_sequence: opts.last_sequence unsubscribe: (channelNames...) => @_sendMessage multiplex_unsubscribe: channelNames getLastMessageSequence: => @_lastMessageSequence or {} _open: => super() for channel, opts of @config.channels if @_lastMessageSequence opts.last_sequence = @_lastMessageSequence[channel] unless opts.last_sequence opts.last_sequence = 0 @subscribe channel, opts _message: (event) => frame = @config.parse event.data @_restartKeepAlive() unless isPong frame try @_lastMessageSequence ||= {} @_lastMessageSequence[frame.channel] = frame.last_sequence @config.message frame catch e
sendPing = (socket) ->
socket.send JSON.stringify ping: 'PING'
isPong = (o) ->
o.pong is 'PONG'