firehose 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -22,22 +22,25 @@ class Firehose.Consumer
22
22
  this
23
23
 
24
24
  connect: (delay=0) =>
25
+ @config.connectionVerified = @_upgradeTransport
25
26
  if Firehose.WebSocket.supported()
26
27
  @upgradeTimeout = setTimeout =>
27
- @config.connectionVerified = @_upgradeTransport
28
28
  ws = new Firehose.WebSocket @config
29
29
  ws.connect delay
30
30
  , 500
31
31
  @transport = new Firehose.LongPoll @config
32
32
  @transport.connect delay
33
+ return
33
34
 
34
35
  stop: =>
35
36
  if @upgradeTimeout?
36
37
  clearTimeout @upgradeTimeout
37
38
  @upgradeTimeout = null
38
39
  @transport.stop()
40
+ return
39
41
 
40
42
  _upgradeTransport: (ws) =>
41
43
  @transport.stop()
42
44
  ws.sendStartingMessageSequence @transport.getLastMessageSequence()
43
45
  @transport = ws
46
+ return
@@ -26,18 +26,8 @@ class Firehose.LongPoll extends Firehose.Transport
26
26
  @_lagTime = 5000
27
27
  @_timeout = @config.longPoll.timeout + @_lagTime
28
28
  @_okInterval = 0
29
- @_isConnected = false
30
29
  @_stopRequestLoop = false
31
30
 
32
- connect: (delay = 0) =>
33
- unless @_isConnected
34
- @_isConnected = true
35
- @config.connected()
36
- super(delay)
37
-
38
- getLastMessageSequence: =>
39
- @_lastMessageSequence or 0
40
-
41
31
  _request: =>
42
32
  return if @_stopRequestLoop
43
33
  # Set the Last Message Sequence in a query string.
@@ -47,7 +37,7 @@ class Firehose.LongPoll extends Firehose.Transport
47
37
  data.last_message_sequence = @_lastMessageSequence
48
38
  # TODO: Some of these options will be deprecated in jQuery 1.8
49
39
  # See: http://api.jquery.com/jQuery.ajax/#jqXHR
50
- $.ajax
40
+ @_lastRequest = $.ajax
51
41
  url: @config.longPoll.url
52
42
  crossDomain: true
53
43
  data: data
@@ -58,9 +48,17 @@ class Firehose.LongPoll extends Firehose.Transport
58
48
 
59
49
  stop: =>
60
50
  @_stopRequestLoop = true
51
+ if @_lastRequest?
52
+ try @_lastRequest.abort() catch e
53
+ delete @_lastRequest
54
+ if @_lastPingRequest?
55
+ try @_lastPingRequest.abort() catch e
56
+ delete @_lastPingRequest
61
57
 
62
58
  _success: (data, status, jqXhr) =>
63
- @_open data unless @_succeeded
59
+ if @_needToNotifyOfReconnect or not @_succeeded
60
+ @_needToNotifyOfReconnect = false
61
+ @_open data
64
62
  return if @_stopRequestLoop
65
63
  if jqXhr.status is 200
66
64
  # Of course, IE's XDomainRequest doesn't support non-200 success codes.
@@ -75,18 +73,22 @@ class Firehose.LongPoll extends Firehose.Transport
75
73
  # Ping long poll server to verify internet connectivity
76
74
  # jQuery CORS doesn't support timeouts and there is no way to access xhr2 object
77
75
  # directly so we can't manually set a timeout.
78
- $.ajax
79
- url: @config.longPoll.url
80
- method: 'HEAD'
81
- crossDomain: true
82
- data: @config.params
83
- success: @config.connected
76
+ @_lastPingRequest = $.ajax
77
+ url: @config.longPoll.url
78
+ method: 'HEAD'
79
+ crossDomain: true
80
+ data: @config.params
81
+ success: =>
82
+ if @_needToNotifyOfReconnect
83
+ @_needToNotifyOfReconnect = false
84
+ @config.connected @
84
85
 
85
86
  # We need this custom handler to have the connection status
86
87
  # properly displayed
87
88
  _error: (jqXhr, status, error) =>
88
- @_isConnected = false
89
- @config.disconnected()
89
+ unless @_needToNotifyOfReconnect or @_stopRequestLoop
90
+ @_needToNotifyOfReconnect = true
91
+ @config.disconnected()
90
92
  unless @_stopRequestLoop
91
93
  # Ping the server to make sure this isn't a network connectivity error
92
94
  setTimeout @_ping, @_retryDelay + @_lagTime
@@ -6,8 +6,8 @@ class Firehose.Transport
6
6
  false
7
7
 
8
8
  constructor: (config={}) ->
9
- @config = config
10
- @_retryDelay = 5000
9
+ @config = config
10
+ @_retryDelay = 3000
11
11
 
12
12
  # Lets rock'n'roll! Connect to the server.
13
13
  connect: (delay = 0) =>
@@ -34,4 +34,8 @@ class Firehose.Transport
34
34
 
35
35
  # Default connection closed handler
36
36
  _close: (event) =>
37
- @config.disconnected()
37
+ @config.disconnected()
38
+
39
+ # Useful for reconnecting after any networking hiccups
40
+ getLastMessageSequence: =>
41
+ @_lastMessageSequence or 0
@@ -27,25 +27,35 @@ class Firehose.WebSocket extends Firehose.Transport
27
27
  @socket.onerror = @_error
28
28
  @socket.onmessage = @_lookForInitialPong
29
29
 
30
+ _open: =>
31
+ sendPing @socket
32
+
30
33
  _lookForInitialPong: (event) =>
34
+ @_restartKeepAlive()
31
35
  if isPong(try JSON.parse event.data catch e then {})
32
- @config.webSocket.connectionVerified @
36
+ if @_lastMessageSequence?
37
+ # don't callback to connectionVerified on subsequent reconnects
38
+ @sendStartingMessageSequence @_lastMessageSequence
39
+ else @config.webSocket.connectionVerified @
33
40
 
34
41
  sendStartingMessageSequence: (message_sequence) =>
35
- @socket.onmessage = @_message
42
+ @_lastMessageSequence = message_sequence
43
+ @socket.onmessage = @_message
36
44
  @socket.send JSON.stringify {message_sequence}
45
+ @_needToNotifyOfDisconnect = true
46
+ Firehose.Transport::_open.call @
37
47
 
38
48
  stop: =>
39
49
  @_cleanUp()
40
50
 
41
- _open: =>
42
- sendPing @socket
43
- super
44
-
45
51
  _message: (event) =>
52
+ frame = @config.parse event.data
46
53
  @_restartKeepAlive()
47
- msg = @config.parse event.data
48
- @config.message msg if not isPong msg
54
+ unless isPong frame
55
+ try
56
+ @_lastMessageSequence = frame.last_sequence
57
+ @config.message @config.parse frame.message
58
+ catch e
49
59
 
50
60
  _close: (event) =>
51
61
  if event?.wasClean then @_cleanUp()
@@ -53,7 +63,11 @@ class Firehose.WebSocket extends Firehose.Transport
53
63
 
54
64
  _error: (event) =>
55
65
  @_cleanUp()
56
- super
66
+ if @_needToNotifyOfDisconnect
67
+ @_needToNotifyOfDisconnect = false
68
+ @config.disconnected()
69
+ if @_succeeded then @connect @_retryDelay
70
+ else @config.failed @
57
71
 
58
72
  _cleanUp: =>
59
73
  @_clearKeepalive()
@@ -93,74 +93,69 @@ module Firehose
93
93
  end
94
94
 
95
95
 
96
- # It _may_ be more memory efficient if we used the same instance of this
97
- # class (or even if we just used a proc/lambda) for every
98
- # request/connection. However, we couldn't use instance variables, and
99
- # so I'd need to confirm that local variables would be accessible from
100
- # the callback blocks.
96
+ # It _may_ be more memory efficient if we used the same instance of
97
+ # this class (or if we even just used a lambda) for every connection.
101
98
  class WebSocket
102
99
  def call(env)
103
- req = ::Rack::Request.new(env)
104
- ws = Faye::WebSocket.new(env)
100
+ req = ::Rack::Request.new env
101
+ @ws = Faye::WebSocket.new env
105
102
  @path = req.path
103
+ @ws.onopen = method :handle_open
104
+ @ws.onclose = method :handle_close
105
+ @ws.onerror = method :handle_error
106
+ @ws.onmessage = method :handle_message
107
+ return @ws.rack_response
108
+ end
106
109
 
107
- subscribe = Proc.new do |last_sequence|
108
- @channel = Channel.new(@path)
109
- @deferrable = @channel.next_message(last_sequence).callback do |message, sequence|
110
- Firehose.logger.debug "WS sent `#{message}` to `#{@path}` with sequence `#{sequence}`"
111
- ws.send message
112
- subscribe.call(sequence)
113
- end.errback { |e| EM.next_tick { raise e.inspect } unless e == :disconnect }
114
- end
110
+ private
115
111
 
116
- handle_ping = lambda do |event|
117
- msg = JSON.parse(event.data, :symbolize_names => true) rescue {}
118
- if msg[:ping] == 'PING'
119
- Firehose.logger.debug "WS ping received, sending pong"
120
- ws.send JSON.generate :pong => 'PONG'
121
- end
112
+ def subscribe(last_sequence)
113
+ @subscribed = true
114
+ @channel = Channel.new @path
115
+ @deferrable = @channel.next_message last_sequence
116
+ @deferrable.callback do |message, sequence|
117
+ Firehose.logger.debug "WS sent `#{message}` to `#{@path}` with sequence `#{sequence}`"
118
+ @ws.send self.class.wrap_frame(message, last_sequence)
119
+ subscribe sequence
122
120
  end
123
-
124
- wait_for_starting_sequence = lambda do |event|
125
- msg = JSON.parse(event.data, :symbolize_names => true) rescue {}
126
- seq = msg[:message_sequence]
127
- if seq.kind_of? Integer
128
- Firehose.logger.debug "Subscribing at message_sequence #{seq}"
129
- subscribe.call seq
130
- ws.onmessage = handle_ping
131
- end
121
+ @deferrable.errback do |e|
122
+ EM.next_tick { raise e.inspect } unless e == :disconnect
132
123
  end
124
+ end
133
125
 
134
- wait_for_ping = lambda do |event|
135
- msg = JSON.parse(event.data, :symbolize_names => true) rescue {}
136
- if msg[:ping] == 'PING'
137
- Firehose.logger.debug "WS ping received, sending pong and waiting for starting sequence..."
138
- ws.send JSON.generate :pong => 'PONG'
139
- ws.onmessage = wait_for_starting_sequence
140
- end
126
+ def handle_message(event)
127
+ msg = JSON.parse(event.data, :symbolize_names => true) rescue {}
128
+ seq = msg[:message_sequence]
129
+ if msg[:ping] == 'PING'
130
+ Firehose.logger.debug "WS ping received, sending pong"
131
+ @ws.send JSON.generate :pong => 'PONG'
132
+ elsif !@subscribed && seq.kind_of?(Integer)
133
+ Firehose.logger.debug "Subscribing at message_sequence #{seq}"
134
+ subscribe seq
141
135
  end
136
+ end
142
137
 
143
- ws.onopen = lambda do |event|
144
- Firehose.logger.debug "WebSocket subscribed to `#{@path}`. Waiting for ping..."
145
- ws.onmessage = wait_for_ping
146
- end
138
+ def handle_open(event)
139
+ Firehose.logger.debug "WebSocket subscribed to `#{@path}`. Waiting for message_sequence..."
140
+ end
147
141
 
148
- ws.onclose = lambda do |event|
149
- if @deferrable
150
- @deferrable.fail :disconnect
151
- @channel.unsubscribe(@deferrable) if @channel
152
- end
153
- Firehose.logger.debug "WS connection `#{@path}` closing. Code: #{event.code.inspect}; Reason #{event.reason.inspect}"
142
+ def handle_close(event)
143
+ if @deferrable
144
+ @deferrable.fail :disconnect
145
+ @channel.unsubscribe(@deferrable) if @channel
154
146
  end
147
+ Firehose.logger.debug "WS connection `#{@path}` closing. Code: #{event.code.inspect}; Reason #{event.reason.inspect}"
148
+ end
155
149
 
156
- ws.onerror = lambda do |event|
157
- Firehose.logger.error "WS connection `#{@path}` error `#{error}`: #{error.backtrace}"
158
- end
150
+ def handle_error(event)
151
+ Firehose.logger.error "WS connection `#{@path}` error `#{error}`: #{error.backtrace}"
152
+ end
159
153
 
160
- # Return async Rack response
161
- ws.rack_response
154
+ def self.wrap_frame(message, last_sequence)
155
+ JSON.generate :message => message, :last_sequence => last_sequence
162
156
  end
163
157
  end
158
+
164
159
  end
165
160
  end
166
161
  end
@@ -1,4 +1,4 @@
1
1
  module Firehose
2
- VERSION = "1.0.0"
3
- CODENAME = "Concerted Connection Upgrades"
2
+ VERSION = "1.1.0"
3
+ CODENAME = "Rockin' Reconnect"
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: firehose
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2012-11-05 00:00:00.000000000 Z
15
+ date: 2012-11-21 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: eventmachine