firehose 1.2.20 → 1.3.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.codeclimate.yml +29 -0
- data/.dockerignore +2 -0
- data/.gitignore +3 -1
- data/.rubocop.yml +1156 -0
- data/.ruby-version +1 -0
- data/.travis.yml +3 -7
- data/CHANGELOG.md +15 -0
- data/Dockerfile +11 -0
- data/Gemfile +4 -2
- data/Procfile.dev +0 -1
- data/README.md +66 -8
- data/Rakefile +43 -32
- data/coffeelint.json +129 -0
- data/docker-compose.yml +17 -0
- data/firehose.gemspec +5 -9
- data/karma.config.coffee +89 -0
- data/lib/assets/javascripts/firehose.js.coffee +1 -2
- data/lib/assets/javascripts/firehose/consumer.js.coffee +18 -2
- data/lib/assets/javascripts/firehose/core.js.coffee +2 -1
- data/lib/assets/javascripts/firehose/long_poll.js.coffee +69 -8
- data/lib/assets/javascripts/firehose/multiplexed_consumer.js.coffee +74 -0
- data/lib/assets/javascripts/firehose/transport.js.coffee +4 -2
- data/lib/assets/javascripts/firehose/web_socket.js.coffee +51 -5
- data/lib/firehose/cli.rb +2 -1
- data/lib/firehose/client/producer.rb +10 -4
- data/lib/firehose/rack/consumer.rb +39 -0
- data/lib/firehose/rack/consumer/http_long_poll.rb +118 -45
- data/lib/firehose/rack/consumer/web_socket.rb +133 -28
- data/lib/firehose/rack/ping.rb +1 -1
- data/lib/firehose/rack/publisher.rb +10 -4
- data/lib/firehose/server.rb +9 -9
- data/lib/firehose/server/channel.rb +23 -31
- data/lib/firehose/server/message_buffer.rb +59 -0
- data/lib/firehose/server/publisher.rb +16 -17
- data/lib/firehose/server/redis.rb +32 -0
- data/lib/firehose/server/subscriber.rb +7 -7
- data/lib/firehose/version.rb +2 -2
- data/package.json +14 -2
- data/spec/integrations/shared_examples.rb +89 -7
- data/spec/javascripts/firehose/multiplexed_consumer_spec.coffee +72 -0
- data/spec/javascripts/firehose/transport_spec.coffee +0 -2
- data/spec/javascripts/firehose/websocket_spec.coffee +2 -0
- data/spec/javascripts/helpers/spec_helper.js +1 -0
- data/spec/javascripts/support/jquery-1.11.1.js +10308 -0
- data/{lib/assets/javascripts/vendor → spec/javascripts/support}/json2.js +0 -0
- data/spec/javascripts/support/spec_helper.coffee +3 -0
- data/spec/lib/assets_spec.rb +8 -8
- data/spec/lib/client/producer_spec.rb +14 -14
- data/spec/lib/firehose_spec.rb +2 -2
- data/spec/lib/rack/consumer/http_long_poll_spec.rb +21 -3
- data/spec/lib/rack/consumer_spec.rb +4 -4
- data/spec/lib/rack/ping_spec.rb +4 -4
- data/spec/lib/rack/publisher_spec.rb +5 -5
- data/spec/lib/server/app_spec.rb +2 -2
- data/spec/lib/server/channel_spec.rb +58 -44
- data/spec/lib/server/message_buffer_spec.rb +148 -0
- data/spec/lib/server/publisher_spec.rb +29 -22
- data/spec/lib/server/redis_spec.rb +13 -0
- data/spec/lib/server/subscriber_spec.rb +14 -13
- data/spec/spec_helper.rb +8 -1
- metadata +34 -95
- data/.rbenv-version +0 -1
- data/Guardfile +0 -31
- data/config/evergreen.rb +0 -9
data/karma.config.coffee
ADDED
@@ -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"
|
@@ -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 =
|
46
|
+
ws = @websocketTransport(@config)
|
31
47
|
ws.connect delay
|
32
48
|
, 500
|
33
|
-
@transport =
|
49
|
+
@transport = @longpollTransport(@config)
|
34
50
|
@transport.connect delay
|
35
51
|
|
36
52
|
promise
|
@@ -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
|
29
|
-
@_timeout
|
30
|
-
@_okInterval
|
31
|
-
@_stopRequestLoop
|
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 = @
|
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.
|
88
|
+
url: @config.uri
|
85
89
|
method: 'HEAD'
|
86
90
|
crossDomain: true
|
87
91
|
firehose: true
|
88
|
-
data: @
|
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
|
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
|
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 @
|
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
|
-
@
|
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
|
72
|
-
|
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
|
|
data/lib/firehose/cli.rb
CHANGED
@@ -16,7 +16,8 @@ module Firehose
|
|
16
16
|
|
17
17
|
desc "javascript", "Compile the Firehose JavaScript."
|
18
18
|
def javascript
|
19
|
-
puts Firehose
|
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."
|