firehose 0.2.alpha.10 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|