firehose 0.2.alpha.10 → 1.0.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.
- data/.env.sample +10 -3
- data/README.md +12 -17
- data/lib/assets/javascripts/firehose/consumer.js.coffee +27 -38
- data/lib/assets/javascripts/firehose/long_poll.js.coffee +5 -1
- data/lib/assets/javascripts/firehose/web_socket.js.coffee +49 -34
- data/lib/firehose/rack/consumer_app.rb +38 -18
- data/lib/firehose/version.rb +3 -3
- metadata +132 -42
data/.env.sample
CHANGED
@@ -1,10 +1,17 @@
|
|
1
1
|
# This is picked up by em-hiredis
|
2
2
|
REDIS_URL=redis://127.0.0.1:6379/0
|
3
|
-
|
3
|
+
|
4
|
+
# Configure the verbosity of the logger.
|
4
5
|
LOG_LEVEL=debug
|
5
|
-
|
6
|
+
|
7
|
+
# Firehose port binding.
|
6
8
|
PORT=7474
|
7
|
-
|
9
|
+
|
10
|
+
# Firehose IP address binding.
|
8
11
|
HOST=127.0.0.1
|
12
|
+
|
9
13
|
# Can be rainbows or thin
|
10
14
|
SERVER=rainbows
|
15
|
+
|
16
|
+
# Configure a production or development environment for Rainbows! or Thin.
|
17
|
+
RACK_ENV=development
|
data/README.md
CHANGED
@@ -33,7 +33,7 @@ $ firehose server
|
|
33
33
|
>> Listening on 127.0.0.1:7474, CTRL+C to stop
|
34
34
|
```
|
35
35
|
|
36
|
-
In case you're wondering, the Firehose application server runs the Rack app `Firehose::Rack::App.new` inside of Thin or Rainbows
|
36
|
+
In case you're wondering, the Firehose application server runs the Rack app `Firehose::Rack::App.new` inside of Thin or Rainbows! `Firehose::Rack::App` consists of a bunch of smaller apps and a middleware, which is useful for hacking.
|
37
37
|
|
38
38
|
## Publish a message to a bunch of subscribers
|
39
39
|
|
@@ -102,8 +102,6 @@ socket.io attempts to store connection state per node instance. Firehose makes n
|
|
102
102
|
|
103
103
|
Also, socket.io attempts to abstract a low-latency full-duplex port. Firehose assumes that its impossible to simulate this in older web browsers that don't support WebSockets. As such, Firehose focuses on low-latency server-to-client connections and encourages the use of existing HTTP transports, like POST and PUT, for client-to-server communications.
|
104
104
|
|
105
|
-
Finally, Firehose attempts to solve data consistency issues and authentication by encouraging the use of proxying to the web application.
|
106
|
-
|
107
105
|
# The Ruby Publisher
|
108
106
|
|
109
107
|
While you can certainly make your own PUT requests when publishing messages, Firehose includes a Ruby client for easy publishing.
|
@@ -118,25 +116,23 @@ firehose.publish(json).to("/my/messages/path")
|
|
118
116
|
|
119
117
|
# Configuration
|
120
118
|
|
121
|
-
|
119
|
+
Firehose can be configured via environmental variables. Take a look at the [`.env.sample`](./.env.sample) file for more info.
|
122
120
|
|
123
121
|
## Sprockets
|
124
122
|
|
125
123
|
Using Sprockets is the recommended method of including the included client-side assets in a web page.
|
126
124
|
|
127
|
-
1
|
125
|
+
1. Add the firehose gem in your app's Gemfile.
|
128
126
|
|
129
|
-
2
|
127
|
+
2. Append the firehose gem's assets to the sprockets path. In a Rails app, this is usually done in an initializer.
|
130
128
|
|
131
129
|
```ruby
|
132
|
-
|
133
|
-
|
134
|
-
|
130
|
+
# Add firehose to a custom sprockets configuration.
|
131
|
+
my_sprockets_env = Sprockets::Environment.new
|
132
|
+
Firehose::Assets::Sprockets.configure my_sprockets_env
|
135
133
|
```
|
136
134
|
|
137
|
-
|
138
|
-
|
139
|
-
3) Create a firehose config file for setting constants. A good place for this file in a Rails app is `app/assets/javascripts/lib/firehose_config.js.erb`. This gives you a way to configure Flash Web Sockets needed to make some older browser such as IE<10 and Opera<12 support web sockets. Usually this config file will just be:
|
135
|
+
3. Create a firehose config file for setting constants. A good place for this file in a Rails app is `app/assets/javascripts/lib/firehose_config.js.erb`. This gives you a way to configure Flash Web Sockets needed to make some older browser such as IE<10 and Opera<12 support web sockets. Usually this config file will just be:
|
140
136
|
|
141
137
|
```erb
|
142
138
|
window.WEB_SOCKET_SWF_LOCATION = '<%= asset_path('firehose/WebSocketMain.swf') %>'
|
@@ -153,8 +149,7 @@ The options you can set in this file come directly from https://github.com/gimit
|
|
153
149
|
#= require some/more/js/files
|
154
150
|
```
|
155
151
|
|
156
|
-
It is important that your config file comes first.
|
157
|
-
|
152
|
+
It is important that your firehose config file comes first.
|
158
153
|
|
159
154
|
# Web Server
|
160
155
|
|
@@ -195,7 +190,7 @@ In either case, you'll want to be careful about using these hacks on ports that
|
|
195
190
|
|
196
191
|
The recommended method of deploying firehose is to deploy it separately from your main app.
|
197
192
|
|
198
|
-
1
|
193
|
+
1. Create a new project with a Gemfile such as
|
199
194
|
|
200
195
|
```
|
201
196
|
gem "firehose"
|
@@ -207,6 +202,6 @@ gem "capistrano", :require => false
|
|
207
202
|
|
208
203
|
Of course, you could use `exceptional` instead of `airbrake` and `thin` instead of `rainbows`.
|
209
204
|
|
210
|
-
2
|
205
|
+
2. Set up `config/deploy.rb` to your liking. You can follow most directions for using Capistrano and Foreman to deploy Rack apps, such as https://gist.github.com/1027117
|
211
206
|
|
212
|
-
3
|
207
|
+
3. Set up `config/rainbows.rb` (if you are using Rainbows!). The gem includes an example to get you started.
|
@@ -1,54 +1,43 @@
|
|
1
1
|
class Firehose.Consumer
|
2
|
-
|
3
|
-
@transports: [Firehose.WebSocket, Firehose.LongPoll]
|
4
|
-
|
5
|
-
constructor: (config = {}) ->
|
6
|
-
# List of transport stragies we have to use.
|
7
|
-
config.transports ||= Firehose.Consumer.transports
|
8
|
-
unless config.transports.length > 0
|
9
|
-
throw 'You must provide at least one tranport for Firehose.Consumer'
|
10
|
-
unless typeof config.uri is 'string'
|
11
|
-
throw 'You must provide a Firehose server URI for Firehose.Consumer'
|
2
|
+
constructor: (@config = {}) ->
|
12
3
|
# Empty handler for messages.
|
13
|
-
config.message ||= ->
|
4
|
+
@config.message ||= ->
|
14
5
|
# Empty handler for error handling.
|
15
|
-
config.error ||= ->
|
6
|
+
@config.error ||= ->
|
16
7
|
# Empty handler for when we establish a connection.
|
17
|
-
config.connected ||= ->
|
8
|
+
@config.connected ||= ->
|
18
9
|
# Empty handler for when we're disconnected.
|
19
|
-
config.disconnected ||= ->
|
10
|
+
@config.disconnected ||= ->
|
20
11
|
# The initial connection failed. This is probably triggered when a
|
21
12
|
# transport, like WebSockets is supported by the browser, but for whatever
|
22
13
|
# reason it can't connect (probably a firewall)
|
23
|
-
config.failed ||= ->
|
14
|
+
@config.failed ||= ->
|
24
15
|
throw "Could not connect"
|
25
16
|
# Params that we'll tack on to the URL.
|
26
|
-
config.params ||= {}
|
17
|
+
@config.params ||= {}
|
27
18
|
# Do stuff before we send the message into config.message. The sensible
|
28
19
|
# default on the webs is to parse JSON.
|
29
|
-
config.parse ||= JSON.parse
|
30
|
-
# Hang on to these config for when we connect.
|
31
|
-
@config = config
|
20
|
+
@config.parse ||= JSON.parse
|
32
21
|
# Make sure we return ourself out of the constructor so we can chain.
|
33
22
|
this
|
34
23
|
|
35
24
|
connect: (delay=0) =>
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
25
|
+
if Firehose.WebSocket.supported()
|
26
|
+
@upgradeTimeout = setTimeout =>
|
27
|
+
@config.connectionVerified = @_upgradeTransport
|
28
|
+
ws = new Firehose.WebSocket @config
|
29
|
+
ws.connect delay
|
30
|
+
, 500
|
31
|
+
@transport = new Firehose.LongPoll @config
|
32
|
+
@transport.connect delay
|
33
|
+
|
34
|
+
stop: =>
|
35
|
+
if @upgradeTimeout?
|
36
|
+
clearTimeout @upgradeTimeout
|
37
|
+
@upgradeTimeout = null
|
38
|
+
@transport.stop()
|
39
|
+
|
40
|
+
_upgradeTransport: (ws) =>
|
41
|
+
@transport.stop()
|
42
|
+
ws.sendStartingMessageSequence @transport.getLastMessageSequence()
|
43
|
+
@transport = ws
|
@@ -35,7 +35,11 @@ class Firehose.LongPoll extends Firehose.Transport
|
|
35
35
|
@config.connected()
|
36
36
|
super(delay)
|
37
37
|
|
38
|
+
getLastMessageSequence: =>
|
39
|
+
@_lastMessageSequence or 0
|
40
|
+
|
38
41
|
_request: =>
|
42
|
+
return if @_stopRequestLoop
|
39
43
|
# Set the Last Message Sequence in a query string.
|
40
44
|
# Ideally we'd use an HTTP header, but android devices don't let us
|
41
45
|
# set any HTTP headers for CORS requests.
|
@@ -44,7 +48,7 @@ class Firehose.LongPoll extends Firehose.Transport
|
|
44
48
|
# TODO: Some of these options will be deprecated in jQuery 1.8
|
45
49
|
# See: http://api.jquery.com/jQuery.ajax/#jqXHR
|
46
50
|
$.ajax
|
47
|
-
url:
|
51
|
+
url: @config.longPoll.url
|
48
52
|
crossDomain: true
|
49
53
|
data: data
|
50
54
|
timeout: @_timeout
|
@@ -2,70 +2,85 @@
|
|
2
2
|
# hack into the internals of the web_socket.js plugin we are using.
|
3
3
|
window.WEB_SOCKET_SWF_LOCATION = '/assets/firehose/WebSocketMain.swf' if !window.WEB_SOCKET_SWF_LOCATION
|
4
4
|
|
5
|
+
INITIAL_PING_TIMEOUT = 2000
|
6
|
+
KEEPALIVE_PING_TIMEOUT = 20000
|
7
|
+
|
5
8
|
class Firehose.WebSocket extends Firehose.Transport
|
6
9
|
name: -> 'WebSocket'
|
7
10
|
|
8
|
-
@supported:
|
11
|
+
@supported: ->
|
9
12
|
# Compatibility reference: http://caniuse.com/websockets
|
10
13
|
# We don't need to explicitly check for Flash web socket or MozWebSocket
|
11
14
|
# because web_socket.js has already handled that.
|
12
|
-
|
15
|
+
window.WebSocket?
|
13
16
|
|
14
17
|
constructor: (args) ->
|
15
18
|
super args
|
16
|
-
|
17
19
|
# Configrations specifically for web sockets
|
18
20
|
@config.webSocket ||= {}
|
19
|
-
|
20
|
-
@config.webSocket.url ||= "ws:#{@config.uri}?#{$.param(@config.params)}"
|
21
|
+
@config.webSocket.connectionVerified = @config.connectionVerified
|
21
22
|
|
22
23
|
_request: =>
|
23
|
-
@socket = new window.WebSocket @config.
|
24
|
+
@socket = new window.WebSocket "ws:#{@config.uri}?#{$.param @config.params}"
|
24
25
|
@socket.onopen = @_open
|
25
26
|
@socket.onclose = @_close
|
26
27
|
@socket.onerror = @_error
|
27
|
-
@socket.onmessage = @
|
28
|
+
@socket.onmessage = @_lookForInitialPong
|
29
|
+
|
30
|
+
_lookForInitialPong: (event) =>
|
31
|
+
if isPong(try JSON.parse event.data catch e then {})
|
32
|
+
@config.webSocket.connectionVerified @
|
33
|
+
|
34
|
+
sendStartingMessageSequence: (message_sequence) =>
|
35
|
+
@socket.onmessage = @_message
|
36
|
+
@socket.send JSON.stringify {message_sequence}
|
28
37
|
|
29
38
|
stop: =>
|
30
|
-
@
|
39
|
+
@_cleanUp()
|
31
40
|
|
32
41
|
_open: =>
|
33
|
-
|
34
|
-
|
35
|
-
# TODO: consider making this timeout configurable somehow...
|
36
|
-
@pingTimeout = setTimeout @_error, 2000
|
37
|
-
|
38
|
-
_waitForPong: (event) =>
|
39
|
-
o = try JSON.parse event.data catch e then {}
|
40
|
-
if o.pong is 'PONG'
|
41
|
-
# Not quite sure why this doesn't work in IE8:
|
42
|
-
# (Throws "TypeError: Array or arguments object expected")
|
43
|
-
# Firehose.Transport::_open.apply @, event
|
44
|
-
@_succeeded = true
|
45
|
-
@config.connected @
|
46
|
-
clearTimeout @pingTimeout
|
47
|
-
@socket.onmessage = @_message
|
48
|
-
else @_message event
|
42
|
+
sendPing @socket
|
43
|
+
super
|
49
44
|
|
50
45
|
_message: (event) =>
|
51
|
-
@
|
46
|
+
@_restartKeepAlive()
|
47
|
+
msg = @config.parse event.data
|
48
|
+
@config.message msg if not isPong msg
|
52
49
|
|
53
50
|
_close: (event) =>
|
54
|
-
if event?.wasClean
|
55
|
-
|
56
|
-
else
|
57
|
-
# This was not a clean disconnect. An error occurred somewhere.
|
58
|
-
@_error(event)
|
51
|
+
if event?.wasClean then @_cleanUp()
|
52
|
+
else @_error event
|
59
53
|
|
60
54
|
_error: (event) =>
|
61
|
-
@
|
55
|
+
@_cleanUp()
|
62
56
|
super
|
63
57
|
|
64
|
-
|
65
|
-
|
58
|
+
_cleanUp: =>
|
59
|
+
@_clearKeepalive()
|
60
|
+
if @socket?
|
66
61
|
@socket.onopen = null
|
67
62
|
@socket.onclose = null
|
68
63
|
@socket.onerror = null
|
69
64
|
@socket.onmessage = null
|
70
65
|
@socket.close()
|
71
|
-
delete
|
66
|
+
delete @socket
|
67
|
+
|
68
|
+
_restartKeepAlive: =>
|
69
|
+
doPing = =>
|
70
|
+
sendPing @socket
|
71
|
+
setNextKeepAlive()
|
72
|
+
setNextKeepAlive = =>
|
73
|
+
@keepaliveTimeout = setTimeout doPing, KEEPALIVE_PING_TIMEOUT
|
74
|
+
@_clearKeepalive()
|
75
|
+
setNextKeepAlive()
|
76
|
+
|
77
|
+
_clearKeepalive: =>
|
78
|
+
if @keepaliveTimeout?
|
79
|
+
clearTimeout @keepaliveTimeout
|
80
|
+
@keepaliveTimeout = null
|
81
|
+
|
82
|
+
sendPing = (socket) ->
|
83
|
+
socket.send JSON.stringify ping: 'PING'
|
84
|
+
|
85
|
+
isPong = (o) ->
|
86
|
+
o.pong is 'PONG'
|
@@ -100,28 +100,49 @@ module Firehose
|
|
100
100
|
# the callback blocks.
|
101
101
|
class WebSocket
|
102
102
|
def call(env)
|
103
|
-
req
|
104
|
-
|
105
|
-
|
103
|
+
req = ::Rack::Request.new(env)
|
104
|
+
ws = Faye::WebSocket.new(env)
|
105
|
+
@path = req.path
|
106
|
+
|
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
|
106
115
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
@deferrable = @channel.next_message(last_sequence).callback do |message, sequence|
|
113
|
-
Firehose.logger.debug "WS sent `#{message}` to `#{@path}` with sequence `#{sequence}`"
|
114
|
-
ws.send message
|
115
|
-
subscribe.call(sequence)
|
116
|
-
end.errback { |e| EM.next_tick { raise e.inspect } unless e == :disconnect }
|
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'
|
117
121
|
end
|
122
|
+
end
|
118
123
|
|
119
|
-
|
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
|
120
132
|
end
|
121
133
|
|
122
|
-
|
123
|
-
|
124
|
-
|
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
|
141
|
+
end
|
142
|
+
|
143
|
+
ws.onopen = lambda do |event|
|
144
|
+
Firehose.logger.debug "WebSocket subscribed to `#{@path}`. Waiting for ping..."
|
145
|
+
ws.onmessage = wait_for_ping
|
125
146
|
end
|
126
147
|
|
127
148
|
ws.onclose = lambda do |event|
|
@@ -136,7 +157,6 @@ module Firehose
|
|
136
157
|
Firehose.logger.error "WS connection `#{@path}` error `#{error}`: #{error.backtrace}"
|
137
158
|
end
|
138
159
|
|
139
|
-
|
140
160
|
# Return async Rack response
|
141
161
|
ws.rack_response
|
142
162
|
end
|
data/lib/firehose/version.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
1
|
module Firehose
|
2
|
-
VERSION
|
3
|
-
CODENAME = "
|
4
|
-
end
|
2
|
+
VERSION = "1.0.0"
|
3
|
+
CODENAME = "Concerted Connection Upgrades"
|
4
|
+
end
|
metadata
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: firehose
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Brad Gessler
|
@@ -12,11 +12,11 @@ authors:
|
|
12
12
|
autorequire:
|
13
13
|
bindir: bin
|
14
14
|
cert_chain: []
|
15
|
-
date: 2012-
|
15
|
+
date: 2012-11-05 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
name: eventmachine
|
19
|
-
requirement:
|
19
|
+
requirement: !ruby/object:Gem::Requirement
|
20
20
|
none: false
|
21
21
|
requirements:
|
22
22
|
- - ! '>='
|
@@ -24,10 +24,15 @@ dependencies:
|
|
24
24
|
version: 1.0.0.rc
|
25
25
|
type: :runtime
|
26
26
|
prerelease: false
|
27
|
-
version_requirements:
|
27
|
+
version_requirements: !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.0.0.rc
|
28
33
|
- !ruby/object:Gem::Dependency
|
29
34
|
name: em-hiredis
|
30
|
-
requirement:
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
31
36
|
none: false
|
32
37
|
requirements:
|
33
38
|
- - ! '>='
|
@@ -35,10 +40,15 @@ dependencies:
|
|
35
40
|
version: '0'
|
36
41
|
type: :runtime
|
37
42
|
prerelease: false
|
38
|
-
version_requirements:
|
43
|
+
version_requirements: !ruby/object:Gem::Requirement
|
44
|
+
none: false
|
45
|
+
requirements:
|
46
|
+
- - ! '>='
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
39
49
|
- !ruby/object:Gem::Dependency
|
40
50
|
name: thor
|
41
|
-
requirement:
|
51
|
+
requirement: !ruby/object:Gem::Requirement
|
42
52
|
none: false
|
43
53
|
requirements:
|
44
54
|
- - ! '>='
|
@@ -46,10 +56,15 @@ dependencies:
|
|
46
56
|
version: '0'
|
47
57
|
type: :runtime
|
48
58
|
prerelease: false
|
49
|
-
version_requirements:
|
59
|
+
version_requirements: !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ! '>='
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0'
|
50
65
|
- !ruby/object:Gem::Dependency
|
51
66
|
name: faraday
|
52
|
-
requirement:
|
67
|
+
requirement: !ruby/object:Gem::Requirement
|
53
68
|
none: false
|
54
69
|
requirements:
|
55
70
|
- - ! '>='
|
@@ -57,10 +72,15 @@ dependencies:
|
|
57
72
|
version: '0'
|
58
73
|
type: :runtime
|
59
74
|
prerelease: false
|
60
|
-
version_requirements:
|
75
|
+
version_requirements: !ruby/object:Gem::Requirement
|
76
|
+
none: false
|
77
|
+
requirements:
|
78
|
+
- - ! '>='
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
61
81
|
- !ruby/object:Gem::Dependency
|
62
82
|
name: faye-websocket
|
63
|
-
requirement:
|
83
|
+
requirement: !ruby/object:Gem::Requirement
|
64
84
|
none: false
|
65
85
|
requirements:
|
66
86
|
- - ! '>='
|
@@ -68,10 +88,15 @@ dependencies:
|
|
68
88
|
version: '0'
|
69
89
|
type: :runtime
|
70
90
|
prerelease: false
|
71
|
-
version_requirements:
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
92
|
+
none: false
|
93
|
+
requirements:
|
94
|
+
- - ! '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
72
97
|
- !ruby/object:Gem::Dependency
|
73
98
|
name: em-http-request
|
74
|
-
requirement:
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
75
100
|
none: false
|
76
101
|
requirements:
|
77
102
|
- - ~>
|
@@ -79,10 +104,15 @@ dependencies:
|
|
79
104
|
version: 1.0.0
|
80
105
|
type: :runtime
|
81
106
|
prerelease: false
|
82
|
-
version_requirements:
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
none: false
|
109
|
+
requirements:
|
110
|
+
- - ~>
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: 1.0.0
|
83
113
|
- !ruby/object:Gem::Dependency
|
84
114
|
name: json
|
85
|
-
requirement:
|
115
|
+
requirement: !ruby/object:Gem::Requirement
|
86
116
|
none: false
|
87
117
|
requirements:
|
88
118
|
- - ! '>='
|
@@ -90,10 +120,15 @@ dependencies:
|
|
90
120
|
version: '0'
|
91
121
|
type: :runtime
|
92
122
|
prerelease: false
|
93
|
-
version_requirements:
|
123
|
+
version_requirements: !ruby/object:Gem::Requirement
|
124
|
+
none: false
|
125
|
+
requirements:
|
126
|
+
- - ! '>='
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: '0'
|
94
129
|
- !ruby/object:Gem::Dependency
|
95
130
|
name: rspec
|
96
|
-
requirement:
|
131
|
+
requirement: !ruby/object:Gem::Requirement
|
97
132
|
none: false
|
98
133
|
requirements:
|
99
134
|
- - ! '>='
|
@@ -101,10 +136,15 @@ dependencies:
|
|
101
136
|
version: '0'
|
102
137
|
type: :development
|
103
138
|
prerelease: false
|
104
|
-
version_requirements:
|
139
|
+
version_requirements: !ruby/object:Gem::Requirement
|
140
|
+
none: false
|
141
|
+
requirements:
|
142
|
+
- - ! '>='
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '0'
|
105
145
|
- !ruby/object:Gem::Dependency
|
106
146
|
name: webmock
|
107
|
-
requirement:
|
147
|
+
requirement: !ruby/object:Gem::Requirement
|
108
148
|
none: false
|
109
149
|
requirements:
|
110
150
|
- - ! '>='
|
@@ -112,10 +152,15 @@ dependencies:
|
|
112
152
|
version: '0'
|
113
153
|
type: :development
|
114
154
|
prerelease: false
|
115
|
-
version_requirements:
|
155
|
+
version_requirements: !ruby/object:Gem::Requirement
|
156
|
+
none: false
|
157
|
+
requirements:
|
158
|
+
- - ! '>='
|
159
|
+
- !ruby/object:Gem::Version
|
160
|
+
version: '0'
|
116
161
|
- !ruby/object:Gem::Dependency
|
117
162
|
name: guard-rspec
|
118
|
-
requirement:
|
163
|
+
requirement: !ruby/object:Gem::Requirement
|
119
164
|
none: false
|
120
165
|
requirements:
|
121
166
|
- - ! '>='
|
@@ -123,10 +168,15 @@ dependencies:
|
|
123
168
|
version: '0'
|
124
169
|
type: :development
|
125
170
|
prerelease: false
|
126
|
-
version_requirements:
|
171
|
+
version_requirements: !ruby/object:Gem::Requirement
|
172
|
+
none: false
|
173
|
+
requirements:
|
174
|
+
- - ! '>='
|
175
|
+
- !ruby/object:Gem::Version
|
176
|
+
version: '0'
|
127
177
|
- !ruby/object:Gem::Dependency
|
128
178
|
name: guard-bundler
|
129
|
-
requirement:
|
179
|
+
requirement: !ruby/object:Gem::Requirement
|
130
180
|
none: false
|
131
181
|
requirements:
|
132
182
|
- - ! '>='
|
@@ -134,10 +184,15 @@ dependencies:
|
|
134
184
|
version: '0'
|
135
185
|
type: :development
|
136
186
|
prerelease: false
|
137
|
-
version_requirements:
|
187
|
+
version_requirements: !ruby/object:Gem::Requirement
|
188
|
+
none: false
|
189
|
+
requirements:
|
190
|
+
- - ! '>='
|
191
|
+
- !ruby/object:Gem::Version
|
192
|
+
version: '0'
|
138
193
|
- !ruby/object:Gem::Dependency
|
139
194
|
name: guard-coffeescript
|
140
|
-
requirement:
|
195
|
+
requirement: !ruby/object:Gem::Requirement
|
141
196
|
none: false
|
142
197
|
requirements:
|
143
198
|
- - ! '>='
|
@@ -145,10 +200,15 @@ dependencies:
|
|
145
200
|
version: '0'
|
146
201
|
type: :development
|
147
202
|
prerelease: false
|
148
|
-
version_requirements:
|
203
|
+
version_requirements: !ruby/object:Gem::Requirement
|
204
|
+
none: false
|
205
|
+
requirements:
|
206
|
+
- - ! '>='
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: '0'
|
149
209
|
- !ruby/object:Gem::Dependency
|
150
210
|
name: rainbows
|
151
|
-
requirement:
|
211
|
+
requirement: !ruby/object:Gem::Requirement
|
152
212
|
none: false
|
153
213
|
requirements:
|
154
214
|
- - ! '>='
|
@@ -156,10 +216,15 @@ dependencies:
|
|
156
216
|
version: '0'
|
157
217
|
type: :development
|
158
218
|
prerelease: false
|
159
|
-
version_requirements:
|
219
|
+
version_requirements: !ruby/object:Gem::Requirement
|
220
|
+
none: false
|
221
|
+
requirements:
|
222
|
+
- - ! '>='
|
223
|
+
- !ruby/object:Gem::Version
|
224
|
+
version: '0'
|
160
225
|
- !ruby/object:Gem::Dependency
|
161
226
|
name: thin
|
162
|
-
requirement:
|
227
|
+
requirement: !ruby/object:Gem::Requirement
|
163
228
|
none: false
|
164
229
|
requirements:
|
165
230
|
- - ! '>='
|
@@ -167,10 +232,15 @@ dependencies:
|
|
167
232
|
version: '0'
|
168
233
|
type: :development
|
169
234
|
prerelease: false
|
170
|
-
version_requirements:
|
235
|
+
version_requirements: !ruby/object:Gem::Requirement
|
236
|
+
none: false
|
237
|
+
requirements:
|
238
|
+
- - ! '>='
|
239
|
+
- !ruby/object:Gem::Version
|
240
|
+
version: '0'
|
171
241
|
- !ruby/object:Gem::Dependency
|
172
242
|
name: rack-test
|
173
|
-
requirement:
|
243
|
+
requirement: !ruby/object:Gem::Requirement
|
174
244
|
none: false
|
175
245
|
requirements:
|
176
246
|
- - ! '>='
|
@@ -178,10 +248,15 @@ dependencies:
|
|
178
248
|
version: '0'
|
179
249
|
type: :development
|
180
250
|
prerelease: false
|
181
|
-
version_requirements:
|
251
|
+
version_requirements: !ruby/object:Gem::Requirement
|
252
|
+
none: false
|
253
|
+
requirements:
|
254
|
+
- - ! '>='
|
255
|
+
- !ruby/object:Gem::Version
|
256
|
+
version: '0'
|
182
257
|
- !ruby/object:Gem::Dependency
|
183
258
|
name: async_rack_test
|
184
|
-
requirement:
|
259
|
+
requirement: !ruby/object:Gem::Requirement
|
185
260
|
none: false
|
186
261
|
requirements:
|
187
262
|
- - ! '>='
|
@@ -189,10 +264,15 @@ dependencies:
|
|
189
264
|
version: '0'
|
190
265
|
type: :development
|
191
266
|
prerelease: false
|
192
|
-
version_requirements:
|
267
|
+
version_requirements: !ruby/object:Gem::Requirement
|
268
|
+
none: false
|
269
|
+
requirements:
|
270
|
+
- - ! '>='
|
271
|
+
- !ruby/object:Gem::Version
|
272
|
+
version: '0'
|
193
273
|
- !ruby/object:Gem::Dependency
|
194
274
|
name: foreman
|
195
|
-
requirement:
|
275
|
+
requirement: !ruby/object:Gem::Requirement
|
196
276
|
none: false
|
197
277
|
requirements:
|
198
278
|
- - ! '>='
|
@@ -200,10 +280,15 @@ dependencies:
|
|
200
280
|
version: '0'
|
201
281
|
type: :development
|
202
282
|
prerelease: false
|
203
|
-
version_requirements:
|
283
|
+
version_requirements: !ruby/object:Gem::Requirement
|
284
|
+
none: false
|
285
|
+
requirements:
|
286
|
+
- - ! '>='
|
287
|
+
- !ruby/object:Gem::Version
|
288
|
+
version: '0'
|
204
289
|
- !ruby/object:Gem::Dependency
|
205
290
|
name: sprockets
|
206
|
-
requirement:
|
291
|
+
requirement: !ruby/object:Gem::Requirement
|
207
292
|
none: false
|
208
293
|
requirements:
|
209
294
|
- - ! '>='
|
@@ -211,7 +296,12 @@ dependencies:
|
|
211
296
|
version: '0'
|
212
297
|
type: :development
|
213
298
|
prerelease: false
|
214
|
-
version_requirements:
|
299
|
+
version_requirements: !ruby/object:Gem::Requirement
|
300
|
+
none: false
|
301
|
+
requirements:
|
302
|
+
- - ! '>='
|
303
|
+
- !ruby/object:Gem::Version
|
304
|
+
version: '0'
|
215
305
|
description: Firehose is a realtime web application toolkit for building realtime
|
216
306
|
Ruby web applications.
|
217
307
|
email:
|
@@ -299,12 +389,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
299
389
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
300
390
|
none: false
|
301
391
|
requirements:
|
302
|
-
- - ! '
|
392
|
+
- - ! '>='
|
303
393
|
- !ruby/object:Gem::Version
|
304
|
-
version:
|
394
|
+
version: '0'
|
305
395
|
requirements: []
|
306
396
|
rubyforge_project: firehose
|
307
|
-
rubygems_version: 1.8.
|
397
|
+
rubygems_version: 1.8.23
|
308
398
|
signing_key:
|
309
399
|
specification_version: 3
|
310
400
|
summary: Build realtime Ruby web applications
|