firehose 1.2.20 → 1.3.6

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.
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."