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

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/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: