firehose 1.2.20 → 1.3.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +29 -0
  3. data/.dockerignore +2 -0
  4. data/.gitignore +3 -1
  5. data/.rubocop.yml +1156 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +3 -7
  8. data/CHANGELOG.md +15 -0
  9. data/Dockerfile +11 -0
  10. data/Gemfile +4 -2
  11. data/Procfile.dev +0 -1
  12. data/README.md +66 -8
  13. data/Rakefile +43 -32
  14. data/coffeelint.json +129 -0
  15. data/docker-compose.yml +17 -0
  16. data/firehose.gemspec +5 -9
  17. data/karma.config.coffee +89 -0
  18. data/lib/assets/javascripts/firehose.js.coffee +1 -2
  19. data/lib/assets/javascripts/firehose/consumer.js.coffee +18 -2
  20. data/lib/assets/javascripts/firehose/core.js.coffee +2 -1
  21. data/lib/assets/javascripts/firehose/long_poll.js.coffee +69 -8
  22. data/lib/assets/javascripts/firehose/multiplexed_consumer.js.coffee +74 -0
  23. data/lib/assets/javascripts/firehose/transport.js.coffee +4 -2
  24. data/lib/assets/javascripts/firehose/web_socket.js.coffee +51 -5
  25. data/lib/firehose/cli.rb +2 -1
  26. data/lib/firehose/client/producer.rb +10 -4
  27. data/lib/firehose/rack/consumer.rb +39 -0
  28. data/lib/firehose/rack/consumer/http_long_poll.rb +118 -45
  29. data/lib/firehose/rack/consumer/web_socket.rb +133 -28
  30. data/lib/firehose/rack/ping.rb +1 -1
  31. data/lib/firehose/rack/publisher.rb +10 -4
  32. data/lib/firehose/server.rb +9 -9
  33. data/lib/firehose/server/channel.rb +23 -31
  34. data/lib/firehose/server/message_buffer.rb +59 -0
  35. data/lib/firehose/server/publisher.rb +16 -17
  36. data/lib/firehose/server/redis.rb +32 -0
  37. data/lib/firehose/server/subscriber.rb +7 -7
  38. data/lib/firehose/version.rb +2 -2
  39. data/package.json +14 -2
  40. data/spec/integrations/shared_examples.rb +89 -7
  41. data/spec/javascripts/firehose/multiplexed_consumer_spec.coffee +72 -0
  42. data/spec/javascripts/firehose/transport_spec.coffee +0 -2
  43. data/spec/javascripts/firehose/websocket_spec.coffee +2 -0
  44. data/spec/javascripts/helpers/spec_helper.js +1 -0
  45. data/spec/javascripts/support/jquery-1.11.1.js +10308 -0
  46. data/{lib/assets/javascripts/vendor → spec/javascripts/support}/json2.js +0 -0
  47. data/spec/javascripts/support/spec_helper.coffee +3 -0
  48. data/spec/lib/assets_spec.rb +8 -8
  49. data/spec/lib/client/producer_spec.rb +14 -14
  50. data/spec/lib/firehose_spec.rb +2 -2
  51. data/spec/lib/rack/consumer/http_long_poll_spec.rb +21 -3
  52. data/spec/lib/rack/consumer_spec.rb +4 -4
  53. data/spec/lib/rack/ping_spec.rb +4 -4
  54. data/spec/lib/rack/publisher_spec.rb +5 -5
  55. data/spec/lib/server/app_spec.rb +2 -2
  56. data/spec/lib/server/channel_spec.rb +58 -44
  57. data/spec/lib/server/message_buffer_spec.rb +148 -0
  58. data/spec/lib/server/publisher_spec.rb +29 -22
  59. data/spec/lib/server/redis_spec.rb +13 -0
  60. data/spec/lib/server/subscriber_spec.rb +14 -13
  61. data/spec/spec_helper.rb +8 -1
  62. metadata +34 -95
  63. data/.rbenv-version +0 -1
  64. data/Guardfile +0 -31
  65. data/config/evergreen.rb +0 -9
@@ -0,0 +1,89 @@
1
+ # Karma configuration
2
+ # Generated on Mon Jul 07 2014 12:32:37 GMT-0700 (PDT)
3
+ module.exports = (config) ->
4
+ config.set
5
+ # base path that will be used to resolve all patterns (eg. files, exclude)
6
+ basePath: ""
7
+
8
+ # frameworks to use
9
+ # available frameworks: https://npmjs.org/browse/keyword/karma-adapter
10
+ frameworks: [
11
+ "sprockets-mincer"
12
+ "jasmine"
13
+ ]
14
+
15
+ # list of files / patterns to load in the browser
16
+ files: []
17
+
18
+ # list of files to exclude
19
+ exclude: []
20
+
21
+ # preprocess matching files before serving them to the browser
22
+ # available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
23
+ preprocessors:
24
+ "**/*.coffee": ["coffee"]
25
+
26
+ # test results reporter to use
27
+ # possible values: 'dots', 'progress'
28
+ # available reporters: https://npmjs.org/browse/keyword/karma-reporter
29
+ reporters: ["progress", "dots"]
30
+
31
+ # web server port
32
+ port: 9876
33
+
34
+ # enable / disable colors in the output (reporters and logs)
35
+ colors: true
36
+
37
+ # level of logging
38
+ # possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
39
+ logLevel: config.LOG_INFO
40
+
41
+ # enable / disable watching file and executing tests whenever any file changes
42
+ autoWatch: true
43
+
44
+ # start these browsers
45
+ # available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
46
+ browsers: ["Chrome", "PhantomJS"]
47
+
48
+ # Continuous Integration mode
49
+ # if true, Karma captures browsers, runs the tests and exits
50
+ singleRun: false
51
+
52
+ plugins: [
53
+ "karma-jasmine"
54
+ "karma-chrome-launcher"
55
+ "karma-coffee-preprocessor"
56
+ "karma-sprockets-mincer"
57
+ "karma-phantomjs-launcher"
58
+ "karma-junit-reporter"
59
+ ]
60
+
61
+ sprocketsPaths: [
62
+ "lib/assets/javascripts"
63
+ "spec/javascripts"
64
+ ]
65
+
66
+ sprocketsBundles: [
67
+ "support/spec_helper.coffee"
68
+ "firehose.js.coffee"
69
+ ]
70
+
71
+ # The spec files to run and watch changes for
72
+ files: [
73
+ 'spec/javascripts/**/*_spec.coffee'
74
+ ]
75
+
76
+ junitReporter: {
77
+ outputFile: 'tmp/spec-results.xml',
78
+ suite: ''
79
+ }
80
+
81
+ coffeePreprocessor:
82
+ # options passed to the coffee compiler
83
+ options:
84
+ bare: true
85
+ sourceMap: false
86
+
87
+ # transforming the filenames
88
+ transformPath: (path) ->
89
+ path.replace /\.js\.coffee$/, ".js"
@@ -1,3 +1,2 @@
1
1
  #= require firehose/core
2
- #= require firehose/version
3
- #= require vendor/json2
2
+ #= require firehose/version
@@ -18,19 +18,35 @@ class Firehose.Consumer
18
18
  # Do stuff before we send the message into config.message. The sensible
19
19
  # default on the webs is to parse JSON.
20
20
  @config.parse ||= JSON.parse
21
+
22
+ @_isConnected = false
23
+ origConnected = @config.connected
24
+ @config.connected = =>
25
+ @_isConnected = true
26
+ origConnected()
27
+
21
28
  # Make sure we return ourself out of the constructor so we can chain.
22
29
  this
23
30
 
31
+ connected: =>
32
+ @_isConnected
33
+
34
+ websocketTransport: (config) =>
35
+ new Firehose.WebSocket(config)
36
+
37
+ longpollTransport: (config) =>
38
+ new Firehose.LongPoll(config)
39
+
24
40
  connect: (delay=0) =>
25
41
  promise = @_connectPromise()
26
42
 
27
43
  @config.connectionVerified = @_upgradeTransport
28
44
  if Firehose.WebSocket.supported()
29
45
  @upgradeTimeout = setTimeout =>
30
- ws = new Firehose.WebSocket @config
46
+ ws = @websocketTransport(@config)
31
47
  ws.connect delay
32
48
  , 500
33
- @transport = new Firehose.LongPoll @config
49
+ @transport = @longpollTransport(@config)
34
50
  @transport.connect delay
35
51
 
36
52
  promise
@@ -2,4 +2,5 @@
2
2
  #= require ./transport
3
3
  #= require ./long_poll
4
4
  #= require ./web_socket
5
- #= require ./consumer
5
+ #= require ./consumer
6
+ #= require ./multiplexed_consumer
@@ -25,10 +25,11 @@ class Firehose.LongPoll extends Firehose.Transport
25
25
  @config.longPoll.timeout ||= 25000
26
26
  # TODO - What is @_lagTime for? Can't we just use the @_timeout value?
27
27
  # We use the lag time to make the client live longer than the server.
28
- @_lagTime = 5000
29
- @_timeout = @config.longPoll.timeout + @_lagTime
30
- @_okInterval = @config.okInterval || 0
31
- @_stopRequestLoop = false
28
+ @_lagTime = 5000
29
+ @_timeout = @config.longPoll.timeout + @_lagTime
30
+ @_okInterval = @config.okInterval || 0
31
+ @_stopRequestLoop = false
32
+ @_lastMessageSequence = 0
32
33
 
33
34
  # Protocol schema we should use for talking to firehose server.
34
35
  _protocol: =>
@@ -39,7 +40,7 @@ class Firehose.LongPoll extends Firehose.Transport
39
40
  # Set the Last Message Sequence in a query string.
40
41
  # Ideally we'd use an HTTP header, but android devices don't let us
41
42
  # set any HTTP headers for CORS requests.
42
- data = @config.params
43
+ data = @_requestParams()
43
44
  data.last_message_sequence = @_lastMessageSequence
44
45
  # TODO: Some of these options will be deprecated in jQuery 1.8
45
46
  # See: http://api.jquery.com/jQuery.ajax/#jqXHR
@@ -53,6 +54,9 @@ class Firehose.LongPoll extends Firehose.Transport
53
54
  error: @_error
54
55
  cache: false
55
56
 
57
+ _requestParams: =>
58
+ @config.params
59
+
56
60
  stop: =>
57
61
  @_stopRequestLoop = true
58
62
  if @_lastRequest?
@@ -71,7 +75,7 @@ class Firehose.LongPoll extends Firehose.Transport
71
75
  # Of course, IE's XDomainRequest doesn't support non-200 success codes.
72
76
  try
73
77
  {message, last_sequence} = JSON.parse jqXhr.responseText
74
- @_lastMessageSequence = last_sequence
78
+ @_lastMessageSequence = last_sequence || 0
75
79
  @config.message @config.parse message
76
80
  catch e
77
81
  @connect @_okInterval
@@ -81,11 +85,11 @@ class Firehose.LongPoll extends Firehose.Transport
81
85
  # jQuery CORS doesn't support timeouts and there is no way to access xhr2 object
82
86
  # directly so we can't manually set a timeout.
83
87
  @_lastPingRequest = $.ajax
84
- url: @config.longPoll.url
88
+ url: @config.uri
85
89
  method: 'HEAD'
86
90
  crossDomain: true
87
91
  firehose: true
88
- data: @config.params
92
+ data: @_requestParams()
89
93
  success: =>
90
94
  if @_needToNotifyOfReconnect
91
95
  @_needToNotifyOfReconnect = false
@@ -147,3 +151,60 @@ if $?.browser?.msie and parseInt($.browser.version, 10) in [8, 9]
147
151
  xdr.onerror = jQuery.noop()
148
152
  xdr.abort()
149
153
  }
154
+
155
+ class Firehose.MultiplexedLongPoll extends Firehose.LongPoll
156
+ constructor: (args) ->
157
+ super args
158
+ @_lastMessageSequence = {}
159
+
160
+ subscribe: (channel, opts) =>
161
+ # nothing to be done
162
+
163
+ unsubscribe: (channelNames...) =>
164
+ # same here
165
+
166
+ _request: =>
167
+ return if @_stopRequestLoop
168
+ data = @_subscriptions()
169
+
170
+ @_lastRequest = $.ajax
171
+ url: @config.uri
172
+ firehose: true
173
+ crossDomain: true
174
+ method: "POST"
175
+ data: data
176
+ dataType: "json"
177
+ timeout: @_timeout
178
+ success: @_success
179
+ error: @_error
180
+ cache: false
181
+
182
+ _updateLastMessageSequences: =>
183
+ for channel, opts of @config.channels
184
+ if seq = @_lastMessageSequence[channel]
185
+ opts.last_sequence = seq
186
+ else
187
+ unless opts.last_sequence
188
+ opts.last_sequence = 0
189
+
190
+ _subscriptions: =>
191
+ @_updateLastMessageSequences()
192
+ subs = {}
193
+ for channel, opts of @config.channels
194
+ subs[channel] = opts.last_sequence || 0
195
+ JSON.stringify(subs)
196
+
197
+ _success: (data, status, jqXhr) =>
198
+ if @_needToNotifyOfReconnect or not @_succeeded
199
+ @_needToNotifyOfReconnect = false
200
+ @_open data
201
+ return if @_stopRequestLoop
202
+ if jqXhr.status is 200
203
+ # Of course, IE's XDomainRequest doesn't support non-200 success codes.
204
+ try
205
+ message = JSON.parse jqXhr.responseText
206
+ @_lastMessageSequence ||= {}
207
+ @_lastMessageSequence[message.channel] = message.last_sequence
208
+ @config.message message
209
+ catch e
210
+ @connect @_okInterval
@@ -0,0 +1,74 @@
1
+ Firehose.multiplexChannel = "channels@firehose"
2
+
3
+ class Firehose.MultiplexedConsumer extends Firehose.Consumer
4
+ @subscriptionQuery: (config) ->
5
+ {
6
+ subscribe: [
7
+ "#{channel}!#{opts.last_sequence || 0}" for channel, opts of config.channels
8
+ ].join(",")
9
+ }
10
+
11
+ @normalizeChannels: (config) ->
12
+ for chan, opts of config.channels
13
+ if chan[0] != "/"
14
+ delete config.channels[chan]
15
+ config.channels["/" + chan] = opts
16
+
17
+ @normalizeChannel: (channel) ->
18
+ if channel[0] != "/"
19
+ return "/" + channel
20
+ else
21
+ return channel
22
+
23
+ constructor: (@config = {}) ->
24
+ @messageHandlers = {}
25
+ @config.message ||= @message
26
+ @config.channels ||= {}
27
+ @config.uri += Firehose.multiplexChannel
28
+
29
+ @_updateSubscriptions()
30
+
31
+ for channel, opts of @config.channels
32
+ @_addSubscriptionHandler(channel, opts)
33
+
34
+ super(@config)
35
+
36
+ websocketTransport: (config) =>
37
+ new Firehose.MultiplexedWebSocket(config)
38
+
39
+ longpollTransport: (config) =>
40
+ new Firehose.MultiplexedLongPoll(config)
41
+
42
+ message: (msg) =>
43
+ if handler = @messageHandlers[msg.channel]
44
+ handler(@config.parse msg.message)
45
+
46
+ _addSubscriptionHandler: (channel, opts) =>
47
+ if opts.message
48
+ @messageHandlers[channel] = opts.message
49
+
50
+ _removeSubscriptionHandler: (channelNames...) =>
51
+ for chan in channelNames
52
+ delete @messageHandlers[chan]
53
+
54
+ _updateSubscriptions: =>
55
+ Firehose.MultiplexedConsumer.normalizeChannels(@config)
56
+
57
+ subscribe: (channel, opts = {}) =>
58
+ channel = Firehose.MultiplexedConsumer.normalizeChannel(channel)
59
+ @config.channels[channel] = opts
60
+
61
+ @_updateSubscriptions()
62
+ @_addSubscriptionHandler(channel, opts)
63
+ @transport.subscribe(channel, opts)
64
+
65
+ unsubscribe: (channelNames...) =>
66
+ return unless @connected()
67
+
68
+ for channel in channelNames
69
+ channel = Firehose.MultiplexedConsumer.normalizeChannel(channel)
70
+ delete @config.channels[channel]
71
+
72
+ @_updateSubscriptions()
73
+ @_removeSubscriptionHandler(channelNames...)
74
+ @transport.unsubscribe(channelNames...)
@@ -1,12 +1,14 @@
1
1
  class Firehose.Transport
2
2
  # Class method to determine whether transport is supported by the current browser. Note that while
3
- # the transport may be supported by the browser, its possible that the network connection won't
3
+ # the transport may be supported by the browser, its possible that the network connection won't
4
4
  # succeed. That should be accounted for during the initial connecting to the server.
5
5
  @supported: =>
6
6
  false
7
7
 
8
8
  constructor: (config={}) ->
9
- @config = config
9
+ @config = config
10
+ @config.params ||= {}
11
+
10
12
  @_retryDelay = 3000
11
13
  @
12
14
 
@@ -13,11 +13,14 @@ class Firehose.WebSocket extends Firehose.Transport
13
13
  @config.webSocket ||= {}
14
14
  @config.webSocket.connectionVerified = @config.connectionVerified
15
15
 
16
+ _sendMessage: (message) =>
17
+ @socket?.send(JSON.stringify message)
18
+
16
19
  _request: =>
17
- # Run this is a try/catch block because IE10 inside of a .NET control
20
+ # Run this in a try/catch block because IE10 inside of a .NET control
18
21
  # complains about security zones.
19
22
  try
20
- @socket = new (if module?.exports? then global else window).WebSocket "#{@_protocol()}:#{@config.uri}?#{$.param @config.params}"
23
+ @socket = new (if module?.exports? then global else window).WebSocket "#{@_protocol()}:#{@config.uri}?#{$.param @_requestParams()}"
21
24
  @socket.onopen = @_open
22
25
  @socket.onclose = @_close
23
26
  @socket.onerror = @_error
@@ -29,6 +32,9 @@ class Firehose.WebSocket extends Firehose.Transport
29
32
  _protocol: =>
30
33
  if @config.ssl then "wss" else "ws"
31
34
 
35
+ _requestParams: =>
36
+ @config.params
37
+
32
38
  _open: =>
33
39
  sendPing @socket
34
40
 
@@ -43,7 +49,7 @@ class Firehose.WebSocket extends Firehose.Transport
43
49
  sendStartingMessageSequence: (message_sequence) =>
44
50
  @_lastMessageSequence = message_sequence
45
51
  @socket.onmessage = @_message
46
- @socket.send JSON.stringify {message_sequence}
52
+ @_sendMessage({message_sequence})
47
53
  @_needToNotifyOfDisconnect = true
48
54
  Firehose.Transport::_open.call @
49
55
 
@@ -68,8 +74,10 @@ class Firehose.WebSocket extends Firehose.Transport
68
74
  if @_needToNotifyOfDisconnect
69
75
  @_needToNotifyOfDisconnect = false
70
76
  @config.disconnected()
71
- if @_succeeded then @connect @_retryDelay
72
- else @config.failed @
77
+ if @_succeeded
78
+ @connect @_retryDelay
79
+ else if @config.failed
80
+ @config.failed @
73
81
 
74
82
  _cleanUp: =>
75
83
  @_clearKeepalive()
@@ -95,6 +103,44 @@ class Firehose.WebSocket extends Firehose.Transport
95
103
  clearTimeout @keepaliveTimeout
96
104
  @keepaliveTimeout = null
97
105
 
106
+ class Firehose.MultiplexedWebSocket extends Firehose.WebSocket
107
+ constructor: (args) ->
108
+ super args
109
+
110
+ subscribe: (channel, opts) =>
111
+ @_sendMessage
112
+ multiplex_subscribe:
113
+ channel: channel
114
+ message_sequence: opts.last_sequence
115
+
116
+ unsubscribe: (channelNames...) =>
117
+ @_sendMessage
118
+ multiplex_unsubscribe: channelNames
119
+
120
+ getLastMessageSequence: =>
121
+ @_lastMessageSequence or {}
122
+
123
+ _open: =>
124
+ super()
125
+ for channel, opts of @config.channels
126
+ if @_lastMessageSequence
127
+ opts.last_sequence = @_lastMessageSequence[channel]
128
+
129
+ unless opts.last_sequence
130
+ opts.last_sequence = 0
131
+
132
+ @subscribe channel, opts
133
+
134
+ _message: (event) =>
135
+ frame = @config.parse event.data
136
+ @_restartKeepAlive()
137
+ unless isPong frame
138
+ try
139
+ @_lastMessageSequence ||= {}
140
+ @_lastMessageSequence[frame.channel] = frame.last_sequence
141
+ @config.message frame
142
+ catch e
143
+
98
144
  sendPing = (socket) ->
99
145
  socket.send JSON.stringify ping: 'PING'
100
146
 
@@ -16,7 +16,8 @@ module Firehose
16
16
 
17
17
  desc "javascript", "Compile the Firehose JavaScript."
18
18
  def javascript
19
- puts Firehose::Assets::Sprockets.javascript
19
+ $stderr.puts "DEPRECATION WARNING: Firehose JS assets have been moved to https://github.com/firehoseio/js_client"
20
+ $stdout.puts Firehose::Assets::Sprockets.javascript
20
21
  end
21
22
 
22
23
  desc "version", "Display the current version."