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 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
- RACK_ENV=development
3
+
4
+ # Configure the verbosity of the logger.
4
5
  LOG_LEVEL=debug
5
- # Which port to bind to
6
+
7
+ # Firehose port binding.
6
8
  PORT=7474
7
- # Which IP address to bind to
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
- Most configuration happens inside the `.env` file. Take a look at `.env.sample` for more info.
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) Add the firehose gem in your app's Gemfile.
125
+ 1. Add the firehose gem in your app's Gemfile.
128
126
 
129
- 2) Append the firehose gem's assets to the sprockets path. In a Rails app, this is usually done in an initializer.
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
- gem_path = Gem.loaded_specs['firehose'].full_gem_path
133
- sprockets.append_path File.join(gem_path, 'lib/assets/flash')
134
- sprockets.append_path File.join(gem_path, 'lib/assets/javascripts')
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
- NOTE: Some spockets setups will do this automatically. If that is your case, then you can skip this step.
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) Create a new project with a Gemfile such as
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) 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
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) Set up `config/rainbows.rb` (if you are using Rainbows!). The gem includes an example to get you started.
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
- # Transports that are available to Firehose.
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
- # Get a list of transports that the browser supports
37
- supportedTransports = (t for t in @config.transports when t.supported())
38
- # Mmmkay, we've got transports supported by the browser, now lets try connecting
39
- # to them and dealing with failed connections that might be caused by firewalls,
40
- # or other network connectivity issues.
41
- transports = for transport in supportedTransports
42
- originalFailFun = @config.failed
43
- # Map the next transport into the existing transports connectionError
44
- # If the connection fails, try the next transport supported by the browser.
45
- @config.failed = =>
46
- # Map the next transport to connect inside of the current transport failures
47
- if nextTransportType = supportedTransports.pop()
48
- nextTransport = new nextTransportType @config
49
- nextTransport.connect delay
50
- else originalFailFun?()
51
- new transport(@config)
52
- # Fire off the first connection attempt.
53
- [firstTransport] = transports
54
- firstTransport.connect delay
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: @config.longPoll.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
- !!(window.WebSocket)
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
- # Protocol schema we should use for talking to WS server.
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.webSocket.url
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 = @_waitForPong
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
- @cleanUp()
39
+ @_cleanUp()
31
40
 
32
41
  _open: =>
33
- # TODO: include JSON client-side script for less awesome browsers
34
- @socket.send JSON.stringify ping: 'PING'
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
- @config.message(@config.parse(event.data))
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
- @cleanUp()
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
- @cleanUp()
55
+ @_cleanUp()
62
56
  super
63
57
 
64
- cleanUp: ->
65
- if @socket
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(@socket)
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 = ::Rack::Request.new(env)
104
- @path = req.path
105
- ws = Faye::WebSocket.new(env)
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
- ws.onopen = lambda do |event|
108
- Firehose.logger.debug "WS subscribed to `#{@path}`"
109
-
110
- subscribe = Proc.new do |last_sequence|
111
- @channel = Channel.new(@path)
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
- subscribe.call nil
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
- ws.onmessage = lambda do |event|
123
- o = JSON.parse(event.data, :symbolize_names => true) rescue {}
124
- ws.send(JSON.generate(:pong => 'PONG')) if o[:ping] == 'PING'
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
@@ -1,4 +1,4 @@
1
1
  module Firehose
2
- VERSION = "0.2.alpha.10"
3
- CODENAME = "Surfin' Safari"
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.2.alpha.10
5
- prerelease: 4
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-10-09 00:00:00.000000000 Z
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: &70308234310380 !ruby/object:Gem::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: *70308234310380
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: &70308234309920 !ruby/object:Gem::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: *70308234309920
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: &70308234290880 !ruby/object:Gem::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: *70308234290880
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: &70308234290160 !ruby/object:Gem::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: *70308234290160
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: &70308234289460 !ruby/object:Gem::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: *70308234289460
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: &70308234288260 !ruby/object:Gem::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: *70308234288260
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: &70308234286820 !ruby/object:Gem::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: *70308234286820
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: &70308234283880 !ruby/object:Gem::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: *70308234283880
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: &70308234283200 !ruby/object:Gem::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: *70308234283200
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: &70308234282780 !ruby/object:Gem::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: *70308234282780
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: &70308234282360 !ruby/object:Gem::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: *70308234282360
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: &70308234281760 !ruby/object:Gem::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: *70308234281760
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: &70308234281120 !ruby/object:Gem::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: *70308234281120
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: &70308234280560 !ruby/object:Gem::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: *70308234280560
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: &70308234280140 !ruby/object:Gem::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: *70308234280140
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: &70308234279680 !ruby/object:Gem::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: *70308234279680
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: &70308234279180 !ruby/object:Gem::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: *70308234279180
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: &70308234278520 !ruby/object:Gem::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: *70308234278520
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: 1.3.1
394
+ version: '0'
305
395
  requirements: []
306
396
  rubyforge_project: firehose
307
- rubygems_version: 1.8.11
397
+ rubygems_version: 1.8.23
308
398
  signing_key:
309
399
  specification_version: 3
310
400
  summary: Build realtime Ruby web applications