firehose 0.2.alpha.5 → 0.2.alpha.6

Sign up to get free protection for your applications and to get access to all the features.
data/firehose.gemspec CHANGED
@@ -5,8 +5,8 @@ require "firehose/version"
5
5
  Gem::Specification.new do |s|
6
6
  s.name = "firehose"
7
7
  s.version = Firehose::VERSION
8
- s.authors = ["Brad Gessler", "Steel Fu", "Paul Cortens"]
9
- s.email = ["brad@polleverywhere.com", "steel@polleverywhere.com", "paul@polleverywhere.com"]
8
+ s.authors = ["Brad Gessler", "Steel Fu", "Paul Cortens", "Zach Zolton"]
9
+ s.email = ["brad@polleverywhere.com", "steel@polleverywhere.com", "paul@polleverywhere.com", "zach@polleverywhere.com"]
10
10
  s.homepage = "http://firehose.io/"
11
11
  s.summary = %q{Build realtime Ruby web applications}
12
12
  s.description = %q{Firehose is a realtime web application toolkit for building realtime Ruby web applications.}
@@ -25,6 +25,7 @@ Gem::Specification.new do |s|
25
25
  s.add_runtime_dependency "faraday"
26
26
  s.add_runtime_dependency "faye-websocket"
27
27
  s.add_runtime_dependency "em-http-request", "~> 1.0.0"
28
+ s.add_runtime_dependency "json"
28
29
 
29
30
  s.add_development_dependency "rspec"
30
31
  s.add_development_dependency "webmock"
@@ -5,6 +5,10 @@ class Firehose.Consumer
5
5
  constructor: (config = {}) ->
6
6
  # List of transport stragies we have to use.
7
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'
8
12
  # Empty handler for messages.
9
13
  config.message ||= ->
10
14
  # Empty handler for error handling.
@@ -13,41 +17,38 @@ class Firehose.Consumer
13
17
  config.connected ||= ->
14
18
  # Empty handler for when we're disconnected.
15
19
  config.disconnected ||= ->
16
- # The initial connection failed. This is probably triggered when a transport, like WebSockets
17
- # is supported by the browser, but for whatever reason it can't connect (probably a firewall)
20
+ # The initial connection failed. This is probably triggered when a
21
+ # transport, like WebSockets is supported by the browser, but for whatever
22
+ # reason it can't connect (probably a firewall)
18
23
  config.failed ||= ->
19
24
  throw "Could not connect"
20
- # URL that we'll connect to.
21
- config.uri ||= undefined
22
25
  # Params that we'll tack on to the URL.
23
- config.params ||= { }
26
+ config.params ||= {}
24
27
  # Do stuff before we send the message into config.message. The sensible
25
28
  # default on the webs is to parse JSON.
26
- config.parse ||= (body) ->
27
- JSON.parse(body)
28
-
29
+ config.parse ||= JSON.parse
29
30
  # Hang on to these config for when we connect.
30
31
  @config = config
31
32
  # Make sure we return ourself out of the constructor so we can chain.
32
33
  this
33
34
 
34
- connect: =>
35
+ connect: (delay=0) =>
35
36
  # Get a list of transports that the browser supports
36
- supportedTransports = (transport for transport in @config.transports when transport.supported())
37
+ supportedTransports = (t for t in @config.transports when t.supported())
37
38
  # Mmmkay, we've got transports supported by the browser, now lets try connecting
38
39
  # to them and dealing with failed connections that might be caused by firewalls,
39
40
  # or other network connectivity issues.
40
41
  transports = for transport in supportedTransports
41
- # Copy the config so we can modify it with a failed callback.
42
- config = @config
42
+ originalFailFun = @config.failed
43
43
  # Map the next transport into the existing transports connectionError
44
44
  # If the connection fails, try the next transport supported by the browser.
45
- config.failed = =>
46
- # Map the next transport to connect insie of the current transport failures
47
- if nextTransport = supportedTransports.pop()
48
- new nextTransport(config).connect()
49
- else
50
- @config.failed() # Call the original fail method passed into the Firehose.Consumer
51
- new transport(config)
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
52
  # Fire off the first connection attempt.
53
- transports[0].connect()
53
+ [firstTransport] = transports
54
+ firstTransport.connect delay
@@ -1,32 +1,33 @@
1
1
  class Firehose.LongPoll extends Firehose.Transport
2
- messageSequenceHeader: 'pragma'
2
+ messageSequenceHeader: 'Pragma'
3
+ name: -> 'LongPoll'
3
4
 
4
- # CORS is supported in IE 8+
5
- @ieSupported: =>
6
- $.browser.msie and parseInt($.browser.version) > 7 and window.XDomainRequest
5
+ # CORS is kinda supported in IE8+ except that its implementation cannot
6
+ # access "simple request" response headers. This means we don't yet have a
7
+ # plan to support IE<10 (when it gets a real XHR2 implementation). Sucks...
8
+ @ieSupported: ->
9
+ $.browser.msie and parseInt($.browser.version) >= 10
7
10
 
8
- @supported: =>
11
+ @supported: ->
9
12
  # IE 8+, FF 3.5+, Chrome 4+, Safari 4+, Opera 12+, iOS 3.2+, Android 2.1+
10
13
  if xhr = $.ajaxSettings.xhr()
11
14
  "withCredentials" of xhr || Firehose.LongPoll.ieSupported()
12
15
 
13
16
  constructor: (args) ->
14
17
  super args
15
-
16
18
  # Configrations specifically for web sockets
17
- @config.longPoll ||= {}
19
+ @config.longPoll ||= {}
18
20
  # Protocol schema we should use for talking to WS server.
19
- @config.longPoll.url ||= "http:#{@config.uri}"
21
+ @config.longPoll.url ||= "http:#{@config.uri}"
20
22
  # How many ms should we wait before timing out the AJAX connection?
21
23
  @config.longPoll.timeout ||= 25000
22
-
23
24
  # TODO - What is @_lagTime for? Can't we just use the @_timeout value?
24
25
  # We use the lag time to make the client live longer than the server.
25
- @_lagTime = 5000
26
- @_timeout = @config.longPoll.timeout + @_lagTime
27
- @_okInterval = 0
28
-
29
- @_isConnected = false
26
+ @_lagTime = 5000
27
+ @_timeout = @config.longPoll.timeout + @_lagTime
28
+ @_okInterval = 0
29
+ @_isConnected = false
30
+ @_stopRequestLoop = false
30
31
 
31
32
  connect: (delay = 0) =>
32
33
  unless @_isConnected
@@ -40,53 +41,35 @@ class Firehose.LongPoll extends Firehose.Transport
40
41
  # set any HTTP headers for CORS requests.
41
42
  data = @config.params
42
43
  data.last_message_sequence = @_lastMessageSequence
43
- # TODO: Some of these options will be deprecated in jQurey 1.8
44
+ # TODO: Some of these options will be deprecated in jQuery 1.8
44
45
  # See: http://api.jquery.com/jQuery.ajax/#jqXHR
46
+ opts =
47
+ crossDomain: true
48
+ data: data
49
+ timeout: @_timeout
50
+ success: @_success
51
+ error: @_error
52
+ complete: @_xhrComplete
53
+ opts.xhr = hackAroundFirefoxXhrHeadersBug if $.browser.mozilla
54
+ $.ajax @config.longPoll.url, opts
45
55
 
46
- $.ajax @config.longPoll.url,
47
- crossDomain: true
48
- data: data
49
- timeout: @_timeout
50
- success: @_success
51
- error: @_error
52
- xhr: ->
53
- # TODO - This while `xrh` attr is a stupid hack to deal with CORS short-comings in jQuery in Firefox.
54
- # This ticket can be viewed at http://bugs.jquery.com/ticket/10338. Once jQuery is
55
- # upgraded to this version, we can probably remove this, but be sure you test the
56
- # crap out of Firefox!
57
- #
58
- # Its also worth noting that I had to localize this monkey-patch to the Firehose.LongPoll
59
- # consumer because a previous global patch on jQuery.ajaxSettings.xhr was breaking regular IE7
60
- # loading. I figured its better to localize this anyway to solve that problem and loading order issues.
61
- xhr = jQuery.ajaxSettings.xhr()
62
- getAllResponseHeaders = xhr.getAllResponseHeaders
63
- xhr.getAllResponseHeaders = ->
64
- allHeaders = getAllResponseHeaders.call(xhr)
65
- return allHeaders if allHeaders
66
- allHeaders = ""
67
- for headerName in [ "Cache-Control", "Content-Language", "Content-Type", "Expires", "Last-Modified", "Pragma" ]
68
- do (headerName) ->
69
- allHeaders += headerName + ": " + xhr.getResponseHeader(headerName) + "\n" if xhr.getResponseHeader(headerName)
70
- allHeaders
71
- xhr
56
+ _xhrComplete: (jqXhr) =>
57
+ # Get the last sequence from the server if specified.
58
+ if jqXhr.status is 200
59
+ val = jqXhr.getResponseHeader @messageSequenceHeader
60
+ @_lastMessageSequence = val if val?
72
61
 
73
- complete: (jqXhr) =>
74
- # Get the last sequence from the server if specified.
75
- if jqXhr.status == 200
76
- @_lastMessageSequence = jqXhr.getResponseHeader(@messageSequenceHeader)
77
- if @_lastMessageSequence == null
78
- console.log 'ERROR: Unable to get last message sequnce from header'
62
+ stop: =>
63
+ @_stopRequestLoop = true
79
64
 
80
65
  _success: (data, status, jqXhr) =>
81
- # TODO we actually want to do this when the thing calls out... mmm right now it takes
82
- # up to 30s before we can call this thing.
83
- # Call the 'connected' callback if the connection succeeds.
84
- @_open(data) unless @_succeeded
66
+ @_open data unless @_succeeded
67
+ return if @_stopRequestLoop
85
68
  if jqXhr.status == 204
86
69
  # If we get a 204 back, that means the server timed-out and sent back a 204 with a
87
70
  # X-Http-Next-Request header
88
71
  #
89
- # Why did we use a 204 and not a 408? Because FireFox is really stupid about 400 level error
72
+ # Why did we use a 204 and not a 408? Because Firefox is really stupid about 400 level error
90
73
  # codes and would claims its a 0 error code, which we use for something else. Firefox is IE
91
74
  # in this case
92
75
  @connect(@_okInterval)
@@ -109,9 +92,65 @@ class Firehose.LongPoll extends Firehose.Transport
109
92
  _error: (jqXhr, status, error) =>
110
93
  @_isConnected = false
111
94
  @config.disconnected()
95
+ unless @_stopRequestLoop
96
+ # Ping the server to make sure this isn't a network connectivity error
97
+ setTimeout @_ping, @_retryDelay + @_lagTime
98
+ # Reconnect with delay
99
+ setTimeout @_request, @_retryDelay
100
+
101
+ # NB: This is a stupid hack to deal with CORS short-comings in jQuery in
102
+ # Firefox. There is a ticket for this: http://bugs.jquery.com/ticket/10338
103
+ # Once jQuery is upgraded to this version we can probably remove this, but be
104
+ # sure you test the crap out of Firefox!
105
+ # Its also worth noting that I had to localize this monkey-patch to the
106
+ # Firehose.LongPoll consumer because a previous global patch on
107
+ # jQuery.ajaxSettings.xhr was breaking regular IE7 loading. Better to localize
108
+ # this anyway to solve that problem and loading order issues.
109
+ hackAroundFirefoxXhrHeadersBug = ->
110
+ XHR_HEADERS = [
111
+ "Cache-Control", "Content-Language", "Content-Type"
112
+ "Expires", "Last-Modified", "Pragma"
113
+ ]
114
+ xhr = jQuery.ajaxSettings.xhr()
115
+ originalFun = xhr.getAllResponseHeaders
116
+ xhr.getAllResponseHeaders = ->
117
+ return allHeaders if allHeaders = originalFun.call xhr
118
+ lines = for name in XHR_HEADERS when xhr.getResponseHeader(name)?
119
+ "#{name}: #{xhr.getResponseHeader name}"
120
+ lines.join '\n'
121
+ xhr
112
122
 
113
- # Ping the server to make sure this isn't a network connectivity error
114
- setTimeout @_ping, @_retryDelay + @_lagTime
123
+ # NB: Leaving this here for now. It does work, but won't help us since the XDR
124
+ # object cannot access any response headers, which Firehose relies on.
115
125
 
116
- # Reconnect with delay
117
- setTimeout @_request, @_retryDelay
126
+ # # Let's try to hack in support for IE8+ via the XDomainRequest object!
127
+ # # This was adapted from code shamelessly stolen from:
128
+ # # https://github.com/jaubourg/ajaxHooks/blob/master/src/ajax/xdr.js
129
+ # if $.browser.msie and parseInt($.browser.version, 10) in [8, 9]
130
+ # jQuery.ajaxTransport (s) ->
131
+ # if s.crossDomain and s.async
132
+ # if s.timeout
133
+ # s.xdrTimeout = s.timeout
134
+ # delete s.timeout
135
+ # xdr = undefined
136
+ # return {
137
+ # send: (_, complete) ->
138
+ # callback = (status, statusText, responses, responseHeaders) ->
139
+ # xdr.onload = xdr.onerror = xdr.ontimeout = jQuery.noop
140
+ # xdr = undefined
141
+ # complete status, statusText, responses, responseHeaders
142
+ # xdr = new XDomainRequest()
143
+ # xdr.open s.type, s.url
144
+ # xdr.onload = ->
145
+ # headers = "Content-Type: #{xdr.contentType}"
146
+ # callback 200, "OK", {text: xdr.responseText}, headers
147
+ # xdr.onerror = -> callback 404, "Not Found"
148
+ # if s.xdrTimeout?
149
+ # xdr.ontimeout = -> callback 0, "timeout"
150
+ # xdr.timeout = s.xdrTimeout
151
+ # xdr.send (s.hasContent and s.data) or null
152
+ # abort: ->
153
+ # if xdr?
154
+ # xdr.onerror = jQuery.noop()
155
+ # xdr.abort()
156
+ # }
@@ -11,23 +11,21 @@ class Firehose.Transport
11
11
 
12
12
  # Lets rock'n'roll! Connect to the server.
13
13
  connect: (delay = 0) =>
14
- setTimeout =>
15
- @_request()
16
- , delay
14
+ setTimeout @_request, delay
17
15
  this
18
16
 
19
- # Sub classes need to implement this method to handle requests
20
- _request: =>
17
+ # Hey subclasses:
18
+ name: -> throw 'not implemented in base Transport' # implement this to identify transport type
19
+ stop: -> throw 'not implemented in base Transport' # implement this to stop receiving messages
20
+ _request: -> throw 'not implemented in base Transport' # implement this to handle requests
21
21
 
22
22
  # Default error handler
23
23
  _error: (event) =>
24
- unless @_succeeded
25
- # Fail peremantly if the error happens on the first connection.
26
- @config.failed(this)
27
- else
24
+ if @_succeeded
28
25
  # Lets try to connect again with delay
29
26
  @config.disconnected()
30
27
  @connect(@_retryDelay)
28
+ else @config.failed @
31
29
 
32
30
  # Default connection established handler
33
31
  _open: (event) =>
@@ -3,6 +3,8 @@
3
3
  window.WEB_SOCKET_SWF_LOCATION = '/assets/firehose/WebSocketMain.swf' if !window.WEB_SOCKET_SWF_LOCATION
4
4
 
5
5
  class Firehose.WebSocket extends Firehose.Transport
6
+ name: -> 'WebSocket'
7
+
6
8
  @supported: =>
7
9
  # Compatibility reference: http://caniuse.com/websockets
8
10
  # We don't need to explicitly check for Flash web socket or MozWebSocket
@@ -18,11 +20,31 @@ class Firehose.WebSocket extends Firehose.Transport
18
20
  @config.webSocket.url ||= "ws:#{@config.uri}?#{$.param(@config.params)}"
19
21
 
20
22
  _request: =>
21
- @socket = new window.WebSocket(@config.webSocket.url)
22
- @socket.onopen = @_open
23
- @socket.onclose = @_close
24
- @socket.onerror = @_error
25
- @socket.onmessage = @_message
23
+ @socket = new window.WebSocket @config.webSocket.url
24
+ @socket.onopen = @_open
25
+ @socket.onclose = @_close
26
+ @socket.onerror = @_error
27
+ @socket.onmessage = @_waitForPong
28
+
29
+ stop: =>
30
+ @cleanUp()
31
+
32
+ _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
26
48
 
27
49
  _message: (event) =>
28
50
  @config.message(@config.parse(event.data))
@@ -34,13 +56,14 @@ class Firehose.WebSocket extends Firehose.Transport
34
56
  @_error(event)
35
57
 
36
58
  _error: (event) =>
37
- # Cleanup the current connection
59
+ @cleanUp()
60
+ super
61
+
62
+ cleanUp: ->
38
63
  if @socket
39
- @socket.onopen = null
40
- @socket.onclose = null
41
- @socket.onerror = null
64
+ @socket.onopen = null
65
+ @socket.onclose = null
66
+ @socket.onerror = null
42
67
  @socket.onmessage = null
43
68
  @socket.close()
44
69
  delete(@socket)
45
-
46
- super
@@ -1,4 +1,5 @@
1
1
  require 'faye/websocket'
2
+ require 'json'
2
3
 
3
4
  module Firehose
4
5
  module Rack
@@ -47,7 +48,8 @@ module Firehose
47
48
  env['async.callback'].call response(400, "Header '#{LAST_MESSAGE_SEQUENCE_HEADER}' may not be less than zero", response_headers(env))
48
49
  else
49
50
  Channel.new(path).next_message(last_sequence, :timeout => TIMEOUT).callback do |message, sequence|
50
- env['async.callback'].call response(200, message, response_headers(env).merge(LAST_MESSAGE_SEQUENCE_HEADER => sequence.to_s))
51
+ combined_headers = response_headers(env).merge(LAST_MESSAGE_SEQUENCE_HEADER => sequence.to_s)
52
+ env['async.callback'].call response(200, message, combined_headers)
51
53
  end.errback do |e|
52
54
  if e == :timeout
53
55
  env['async.callback'].call response(204, '', response_headers(env))
@@ -84,8 +86,8 @@ module Firehose
84
86
  def cors_headers(env)
85
87
  # TODO seperate out CORS logic as an async middleware with a Goliath web server.
86
88
  {
87
- 'Access-Control-Allow-Origin' => cors_origin(env),
88
- 'Access-Control-Expose-Headers' => LAST_MESSAGE_SEQUENCE_HEADER
89
+ 'Access-Control-Allow-Origin' => cors_origin(env),
90
+ 'Access-Control-Expose-Headers' => LAST_MESSAGE_SEQUENCE_HEADER
89
91
  }
90
92
  end
91
93
  end
@@ -117,9 +119,10 @@ module Firehose
117
119
  subscribe.call nil
118
120
  end
119
121
 
120
- #ws.onmessage = lambda do |event|
121
- # event.data
122
- #end
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'
125
+ end
123
126
 
124
127
  ws.onclose = lambda do |event|
125
128
  if @deferrable
data/lib/firehose/rack.rb CHANGED
@@ -10,7 +10,7 @@ module Firehose
10
10
  # Normally we'd want to use a custom header to reduce the likelihood of some
11
11
  # HTTP middleware clobbering the value. But Safari seems to ignore our CORS
12
12
  # header instructions, so we are using 'pragma' because it is always allowed.
13
- LAST_MESSAGE_SEQUENCE_HEADER = 'pragma'
13
+ LAST_MESSAGE_SEQUENCE_HEADER = 'Pragma'
14
14
  RACK_LAST_MESSAGE_SEQUENCE_HEADER = "HTTP_#{LAST_MESSAGE_SEQUENCE_HEADER.upcase.gsub('-', '_')}"
15
15
  # Don't cache in development mode
16
16
  CORS_OPTIONS_MAX_AGE = ENV['RACK_ENV'] == 'development' ? '1' : '1728000'
@@ -1,4 +1,4 @@
1
1
  module Firehose
2
- VERSION = "0.2.alpha.5"
3
- CODENAME = "Time To Live (it up?)"
2
+ VERSION = "0.2.alpha.6"
3
+ CODENAME = "Web Sockets, Schmeb Schmockets"
4
4
  end
@@ -79,7 +79,7 @@ shared_examples_for 'Firehose::Rack::App' do
79
79
  end
80
80
 
81
81
  # Great, we have all the pieces in order, lets run this thing in the reactor.
82
- em 60 do
82
+ em 180 do
83
83
  # Start the clients.
84
84
  websocket.call(1)
85
85
  websocket.call(2)
metadata CHANGED
@@ -1,21 +1,22 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: firehose
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.alpha.5
4
+ version: 0.2.alpha.6
5
5
  prerelease: 4
6
6
  platform: ruby
7
7
  authors:
8
8
  - Brad Gessler
9
9
  - Steel Fu
10
10
  - Paul Cortens
11
+ - Zach Zolton
11
12
  autorequire:
12
13
  bindir: bin
13
14
  cert_chain: []
14
- date: 2012-08-12 00:00:00.000000000 Z
15
+ date: 2012-08-28 00:00:00.000000000 Z
15
16
  dependencies:
16
17
  - !ruby/object:Gem::Dependency
17
18
  name: eventmachine
18
- requirement: &70227705301260 !ruby/object:Gem::Requirement
19
+ requirement: &70256291491020 !ruby/object:Gem::Requirement
19
20
  none: false
20
21
  requirements:
21
22
  - - ! '>='
@@ -23,10 +24,10 @@ dependencies:
23
24
  version: 1.0.0.rc
24
25
  type: :runtime
25
26
  prerelease: false
26
- version_requirements: *70227705301260
27
+ version_requirements: *70256291491020
27
28
  - !ruby/object:Gem::Dependency
28
29
  name: em-hiredis
29
- requirement: &70227705300800 !ruby/object:Gem::Requirement
30
+ requirement: &70256291490580 !ruby/object:Gem::Requirement
30
31
  none: false
31
32
  requirements:
32
33
  - - ! '>='
@@ -34,10 +35,10 @@ dependencies:
34
35
  version: '0'
35
36
  type: :runtime
36
37
  prerelease: false
37
- version_requirements: *70227705300800
38
+ version_requirements: *70256291490580
38
39
  - !ruby/object:Gem::Dependency
39
40
  name: thor
40
- requirement: &70227705300260 !ruby/object:Gem::Requirement
41
+ requirement: &70256291490080 !ruby/object:Gem::Requirement
41
42
  none: false
42
43
  requirements:
43
44
  - - ! '>='
@@ -45,10 +46,10 @@ dependencies:
45
46
  version: '0'
46
47
  type: :runtime
47
48
  prerelease: false
48
- version_requirements: *70227705300260
49
+ version_requirements: *70256291490080
49
50
  - !ruby/object:Gem::Dependency
50
51
  name: faraday
51
- requirement: &70227705299800 !ruby/object:Gem::Requirement
52
+ requirement: &70256291489660 !ruby/object:Gem::Requirement
52
53
  none: false
53
54
  requirements:
54
55
  - - ! '>='
@@ -56,10 +57,10 @@ dependencies:
56
57
  version: '0'
57
58
  type: :runtime
58
59
  prerelease: false
59
- version_requirements: *70227705299800
60
+ version_requirements: *70256291489660
60
61
  - !ruby/object:Gem::Dependency
61
62
  name: faye-websocket
62
- requirement: &70227705299260 !ruby/object:Gem::Requirement
63
+ requirement: &70256291489240 !ruby/object:Gem::Requirement
63
64
  none: false
64
65
  requirements:
65
66
  - - ! '>='
@@ -67,10 +68,10 @@ dependencies:
67
68
  version: '0'
68
69
  type: :runtime
69
70
  prerelease: false
70
- version_requirements: *70227705299260
71
+ version_requirements: *70256291489240
71
72
  - !ruby/object:Gem::Dependency
72
73
  name: em-http-request
73
- requirement: &70227705298620 !ruby/object:Gem::Requirement
74
+ requirement: &70256291488740 !ruby/object:Gem::Requirement
74
75
  none: false
75
76
  requirements:
76
77
  - - ~>
@@ -78,10 +79,21 @@ dependencies:
78
79
  version: 1.0.0
79
80
  type: :runtime
80
81
  prerelease: false
81
- version_requirements: *70227705298620
82
+ version_requirements: *70256291488740
83
+ - !ruby/object:Gem::Dependency
84
+ name: json
85
+ requirement: &70256291488320 !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ! '>='
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ type: :runtime
92
+ prerelease: false
93
+ version_requirements: *70256291488320
82
94
  - !ruby/object:Gem::Dependency
83
95
  name: rspec
84
- requirement: &70227705297680 !ruby/object:Gem::Requirement
96
+ requirement: &70256291487860 !ruby/object:Gem::Requirement
85
97
  none: false
86
98
  requirements:
87
99
  - - ! '>='
@@ -89,10 +101,10 @@ dependencies:
89
101
  version: '0'
90
102
  type: :development
91
103
  prerelease: false
92
- version_requirements: *70227705297680
104
+ version_requirements: *70256291487860
93
105
  - !ruby/object:Gem::Dependency
94
106
  name: webmock
95
- requirement: &70227705340680 !ruby/object:Gem::Requirement
107
+ requirement: &70256291459520 !ruby/object:Gem::Requirement
96
108
  none: false
97
109
  requirements:
98
110
  - - ! '>='
@@ -100,10 +112,10 @@ dependencies:
100
112
  version: '0'
101
113
  type: :development
102
114
  prerelease: false
103
- version_requirements: *70227705340680
115
+ version_requirements: *70256291459520
104
116
  - !ruby/object:Gem::Dependency
105
117
  name: guard-rspec
106
- requirement: &70227705340080 !ruby/object:Gem::Requirement
118
+ requirement: &70256291459080 !ruby/object:Gem::Requirement
107
119
  none: false
108
120
  requirements:
109
121
  - - ! '>='
@@ -111,10 +123,10 @@ dependencies:
111
123
  version: '0'
112
124
  type: :development
113
125
  prerelease: false
114
- version_requirements: *70227705340080
126
+ version_requirements: *70256291459080
115
127
  - !ruby/object:Gem::Dependency
116
128
  name: guard-bundler
117
- requirement: &70227705339180 !ruby/object:Gem::Requirement
129
+ requirement: &70256291458660 !ruby/object:Gem::Requirement
118
130
  none: false
119
131
  requirements:
120
132
  - - ! '>='
@@ -122,10 +134,10 @@ dependencies:
122
134
  version: '0'
123
135
  type: :development
124
136
  prerelease: false
125
- version_requirements: *70227705339180
137
+ version_requirements: *70256291458660
126
138
  - !ruby/object:Gem::Dependency
127
139
  name: guard-coffeescript
128
- requirement: &70227705338480 !ruby/object:Gem::Requirement
140
+ requirement: &70256291458220 !ruby/object:Gem::Requirement
129
141
  none: false
130
142
  requirements:
131
143
  - - ! '>='
@@ -133,10 +145,10 @@ dependencies:
133
145
  version: '0'
134
146
  type: :development
135
147
  prerelease: false
136
- version_requirements: *70227705338480
148
+ version_requirements: *70256291458220
137
149
  - !ruby/object:Gem::Dependency
138
150
  name: rainbows
139
- requirement: &70227705337780 !ruby/object:Gem::Requirement
151
+ requirement: &70256291457800 !ruby/object:Gem::Requirement
140
152
  none: false
141
153
  requirements:
142
154
  - - ! '>='
@@ -144,10 +156,10 @@ dependencies:
144
156
  version: '0'
145
157
  type: :development
146
158
  prerelease: false
147
- version_requirements: *70227705337780
159
+ version_requirements: *70256291457800
148
160
  - !ruby/object:Gem::Dependency
149
161
  name: thin
150
- requirement: &70227705337360 !ruby/object:Gem::Requirement
162
+ requirement: &70256291457380 !ruby/object:Gem::Requirement
151
163
  none: false
152
164
  requirements:
153
165
  - - ! '>='
@@ -155,10 +167,10 @@ dependencies:
155
167
  version: '0'
156
168
  type: :development
157
169
  prerelease: false
158
- version_requirements: *70227705337360
170
+ version_requirements: *70256291457380
159
171
  - !ruby/object:Gem::Dependency
160
172
  name: rack-test
161
- requirement: &70227705336940 !ruby/object:Gem::Requirement
173
+ requirement: &70256291456960 !ruby/object:Gem::Requirement
162
174
  none: false
163
175
  requirements:
164
176
  - - ! '>='
@@ -166,10 +178,10 @@ dependencies:
166
178
  version: '0'
167
179
  type: :development
168
180
  prerelease: false
169
- version_requirements: *70227705336940
181
+ version_requirements: *70256291456960
170
182
  - !ruby/object:Gem::Dependency
171
183
  name: async_rack_test
172
- requirement: &70227705336480 !ruby/object:Gem::Requirement
184
+ requirement: &70256291456520 !ruby/object:Gem::Requirement
173
185
  none: false
174
186
  requirements:
175
187
  - - ! '>='
@@ -177,10 +189,10 @@ dependencies:
177
189
  version: '0'
178
190
  type: :development
179
191
  prerelease: false
180
- version_requirements: *70227705336480
192
+ version_requirements: *70256291456520
181
193
  - !ruby/object:Gem::Dependency
182
194
  name: foreman
183
- requirement: &70227705336020 !ruby/object:Gem::Requirement
195
+ requirement: &70256291456040 !ruby/object:Gem::Requirement
184
196
  none: false
185
197
  requirements:
186
198
  - - ! '>='
@@ -188,10 +200,10 @@ dependencies:
188
200
  version: '0'
189
201
  type: :development
190
202
  prerelease: false
191
- version_requirements: *70227705336020
203
+ version_requirements: *70256291456040
192
204
  - !ruby/object:Gem::Dependency
193
205
  name: sprockets
194
- requirement: &70227705335600 !ruby/object:Gem::Requirement
206
+ requirement: &70256291455540 !ruby/object:Gem::Requirement
195
207
  none: false
196
208
  requirements:
197
209
  - - ! '>='
@@ -199,13 +211,14 @@ dependencies:
199
211
  version: '0'
200
212
  type: :development
201
213
  prerelease: false
202
- version_requirements: *70227705335600
214
+ version_requirements: *70256291455540
203
215
  description: Firehose is a realtime web application toolkit for building realtime
204
216
  Ruby web applications.
205
217
  email:
206
218
  - brad@polleverywhere.com
207
219
  - steel@polleverywhere.com
208
220
  - paul@polleverywhere.com
221
+ - zach@polleverywhere.com
209
222
  executables:
210
223
  - firehose
211
224
  extensions: []
@@ -282,6 +295,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
282
295
  - - ! '>='
283
296
  - !ruby/object:Gem::Version
284
297
  version: '0'
298
+ segments:
299
+ - 0
300
+ hash: 1005777279219714638
285
301
  required_rubygems_version: !ruby/object:Gem::Requirement
286
302
  none: false
287
303
  requirements: