firehose 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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