firehose 1.2.20 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +36 -0
  3. data/{LICENSE.txt → LICENSE} +6 -6
  4. data/README.md +389 -139
  5. data/app/assets/javascripts/firehose.js +258 -0
  6. data/config/importmap.rb +1 -0
  7. data/db/migrate/20260214185244_create_firehose_events.rb +23 -0
  8. data/lib/commands/firehose_command.rb +119 -0
  9. data/lib/firehose/channel.rb +40 -0
  10. data/lib/firehose/cleanup_job.rb +21 -0
  11. data/lib/firehose/engine.rb +36 -0
  12. data/lib/firehose/helper.rb +40 -0
  13. data/lib/firehose/models/channel.rb +12 -0
  14. data/lib/firehose/models/message.rb +11 -0
  15. data/lib/firehose/queue.rb +44 -0
  16. data/lib/firehose/server.rb +931 -18
  17. data/lib/firehose/sse.rb +187 -0
  18. data/lib/firehose/stream.rb +32 -0
  19. data/lib/firehose/streamable.rb +25 -0
  20. data/lib/firehose/websocket.rb +242 -0
  21. data/lib/firehose.rb +67 -15
  22. data/lib/generators/firehose/install/install_generator.rb +43 -0
  23. data/lib/generators/firehose/install/templates/firehose_controller.rb +16 -0
  24. metadata +40 -428
  25. data/.env.sample +0 -17
  26. data/.gitignore +0 -12
  27. data/.rbenv-version +0 -1
  28. data/.rspec +0 -1
  29. data/.travis.yml +0 -16
  30. data/Gemfile +0 -4
  31. data/Guardfile +0 -31
  32. data/Procfile +0 -1
  33. data/Procfile.dev +0 -4
  34. data/Rakefile +0 -53
  35. data/bin/firehose +0 -9
  36. data/config/evergreen.rb +0 -9
  37. data/config/rainbows.rb +0 -17
  38. data/firehose.gemspec +0 -49
  39. data/lib/assets/javascripts/firehose/base.js.coffee +0 -4
  40. data/lib/assets/javascripts/firehose/consumer.js.coffee +0 -70
  41. data/lib/assets/javascripts/firehose/core.js.coffee +0 -5
  42. data/lib/assets/javascripts/firehose/long_poll.js.coffee +0 -149
  43. data/lib/assets/javascripts/firehose/transport.js.coffee +0 -42
  44. data/lib/assets/javascripts/firehose/version.js.coffee.erb +0 -2
  45. data/lib/assets/javascripts/firehose/web_socket.js.coffee +0 -102
  46. data/lib/assets/javascripts/firehose.js.coffee +0 -3
  47. data/lib/assets/javascripts/vendor/json2.js +0 -487
  48. data/lib/firehose/assets.rb +0 -56
  49. data/lib/firehose/cli.rb +0 -78
  50. data/lib/firehose/client/consumer.rb +0 -94
  51. data/lib/firehose/client/producer.rb +0 -108
  52. data/lib/firehose/client.rb +0 -10
  53. data/lib/firehose/logging.rb +0 -32
  54. data/lib/firehose/rack/app.rb +0 -49
  55. data/lib/firehose/rack/consumer/http_long_poll.rb +0 -86
  56. data/lib/firehose/rack/consumer/web_socket.rb +0 -95
  57. data/lib/firehose/rack/consumer.rb +0 -39
  58. data/lib/firehose/rack/ping.rb +0 -88
  59. data/lib/firehose/rack/publisher.rb +0 -53
  60. data/lib/firehose/rack.rb +0 -32
  61. data/lib/firehose/rails.rb +0 -7
  62. data/lib/firehose/server/app.rb +0 -52
  63. data/lib/firehose/server/channel.rb +0 -80
  64. data/lib/firehose/server/publisher.rb +0 -134
  65. data/lib/firehose/server/subscriber.rb +0 -51
  66. data/lib/firehose/version.rb +0 -4
  67. data/package.json +0 -5
  68. data/spec/integrations/integration_test_helper.rb +0 -22
  69. data/spec/integrations/rainbows_spec.rb +0 -7
  70. data/spec/integrations/shared_examples.rb +0 -120
  71. data/spec/integrations/thin_spec.rb +0 -7
  72. data/spec/javascripts/firehose/base_spec.coffee +0 -2
  73. data/spec/javascripts/firehose/consumer_spec.coffee +0 -45
  74. data/spec/javascripts/firehose/long_poll_spec.coffee +0 -44
  75. data/spec/javascripts/firehose/transport_spec.coffee +0 -32
  76. data/spec/javascripts/firehose/websocket_spec.coffee +0 -25
  77. data/spec/javascripts/helpers/spec_helper.js +0 -30
  78. data/spec/javascripts/support/sinon-1.7.3.js +0 -4290
  79. data/spec/lib/assets_spec.rb +0 -35
  80. data/spec/lib/client/consumer_spec.rb +0 -13
  81. data/spec/lib/client/producer_spec.rb +0 -71
  82. data/spec/lib/firehose_spec.rb +0 -7
  83. data/spec/lib/rack/consumer/http_long_poll_spec.rb +0 -12
  84. data/spec/lib/rack/consumer_spec.rb +0 -19
  85. data/spec/lib/rack/ping_spec.rb +0 -28
  86. data/spec/lib/rack/publisher_spec.rb +0 -34
  87. data/spec/lib/server/app_spec.rb +0 -15
  88. data/spec/lib/server/channel_spec.rb +0 -164
  89. data/spec/lib/server/publisher_spec.rb +0 -84
  90. data/spec/lib/server/subscriber_spec.rb +0 -69
  91. data/spec/spec_helper.rb +0 -77
data/lib/firehose/rack.rb DELETED
@@ -1,32 +0,0 @@
1
- module Firehose
2
- module Rack
3
- autoload :Consumer, 'firehose/rack/consumer'
4
- autoload :Publisher, 'firehose/rack/publisher'
5
- autoload :Ping, 'firehose/rack/ping'
6
- autoload :App, 'firehose/rack/app'
7
-
8
- # Evented web servers recognize the -1 HTTP code as a response deferral, which
9
- # is needed to stream responses via WebSockets or HTTP long polling.
10
- ASYNC_RESPONSE = [-1, {}, []].freeze
11
-
12
- # Normally we'd want to use a custom header to reduce the likelihood of some
13
- # HTTP middleware clobbering the value. But Safari seems to ignore our CORS
14
- # header instructions, so we are using 'pragma' because it is always allowed.
15
- LAST_MESSAGE_SEQUENCE_HEADER = 'Pragma'
16
-
17
- # Rack wants the header to start with HTTP, so we deal with that here.
18
- RACK_LAST_MESSAGE_SEQUENCE_HEADER = "HTTP_#{LAST_MESSAGE_SEQUENCE_HEADER.upcase.gsub('-', '_')}"
19
-
20
- # Disable CORS preflight caches for requests in development mode.
21
- CORS_OPTIONS_MAX_AGE = ENV['RACK_ENV'] == 'development' ? '1' : '1728000'
22
-
23
- module Helpers
24
- # Calculates the content of a message body for the response so that HTTP Keep-Alive
25
- # connections work.
26
- def response(status, body='', headers={})
27
- headers = {'Content-Length' => body.size.to_s}.merge(headers)
28
- [status, headers, [body]]
29
- end
30
- end
31
- end
32
- end
@@ -1,7 +0,0 @@
1
- module Firehose
2
- module Rails
3
- class Engine < ::Rails::Engine
4
- # enable rails to pick up lib/assets/* via sprockets.
5
- end
6
- end
7
- end
@@ -1,52 +0,0 @@
1
- module Firehose
2
- module Server
3
- # Configure servers that are booted with-out going through Rack. This is mostly used by
4
- # the `firehose server` CLI command or for testing. Production configurations are likely
5
- # to boot with custom rack configurations.
6
- class App
7
- def initialize(opts={})
8
- @port = opts[:port] || Firehose::URI.port
9
- @host = opts[:host] || Firehose::URI.host
10
- @server = opts[:server] || :rainbows
11
-
12
- Firehose.logger.info "Starting #{Firehose::VERSION} '#{Firehose::CODENAME}', in #{ENV['RACK_ENV']}"
13
- end
14
-
15
- def start
16
- self.send("start_#{@server}")
17
- end
18
-
19
- private
20
- # Boot the Firehose server with the Rainbows app server.
21
- def start_rainbows
22
- require 'rainbows'
23
- Faye::WebSocket.load_adapter('rainbows')
24
-
25
- rackup = Unicorn::Configurator::RACKUP
26
- rackup[:port] = @port if @port
27
- rackup[:host] = @host if @host
28
- rackup[:set_listener] = true
29
- opts = rackup[:options]
30
- opts[:config_file] = File.expand_path('../../../../config/rainbows.rb', __FILE__)
31
-
32
- server = Rainbows::HttpServer.new(Firehose::Rack::App.new, opts)
33
- server.start.join
34
- end
35
-
36
- # Boot the Firehose server with the Thin app server.
37
- def start_thin
38
- require 'thin'
39
-
40
- Faye::WebSocket.load_adapter('thin')
41
-
42
- # TODO: See if we can just set Thin to use Firehose.logger instead of
43
- # printing out messages by itself.
44
- Thin::Logging.silent = true if Firehose.logger.level == Logger::ERROR
45
-
46
- server = Thin::Server.new(@host, @port) do
47
- run Firehose::Rack::App.new
48
- end.start
49
- end
50
- end
51
- end
52
- end
@@ -1,80 +0,0 @@
1
- module Firehose
2
- module Server
3
- # Connects to a specific channel on Redis and listens for messages to notify subscribers.
4
- class Channel
5
- attr_reader :channel_key, :redis, :subscriber, :list_key, :sequence_key
6
-
7
- def self.redis
8
- @redis ||= EM::Hiredis.connect
9
- end
10
-
11
- def self.subscriber
12
- @subscriber ||= Server::Subscriber.new(EM::Hiredis.connect)
13
- end
14
-
15
- def initialize(channel_key, redis=self.class.redis, subscriber=self.class.subscriber)
16
- @channel_key, @redis, @subscriber = channel_key, redis, subscriber
17
- @list_key, @sequence_key = Server.key(channel_key, :list), Server.key(channel_key, :sequence)
18
- end
19
-
20
- def next_message(last_sequence=nil, options={})
21
- last_sequence = last_sequence.to_i
22
-
23
- deferrable = EM::DefaultDeferrable.new
24
- # TODO - Think this through a little harder... maybe some tests ol buddy!
25
- deferrable.errback {|e| EM.next_tick { raise e } unless [:timeout, :disconnect].include?(e) }
26
-
27
- # TODO: Use HSET so we don't have to pull 100 messages back every time.
28
- redis.multi
29
- redis.get(sequence_key).
30
- errback {|e| deferrable.fail e }
31
- redis.lrange(list_key, 0, Server::Publisher::MAX_MESSAGES).
32
- errback {|e| deferrable.fail e }
33
- redis.exec.callback do |(sequence, message_list)|
34
- Firehose.logger.debug "exec returned: `#{sequence}` and `#{message_list.inspect}`"
35
- sequence = sequence.to_i
36
-
37
- if sequence.nil? || (diff = sequence - last_sequence) <= 0
38
- Firehose.logger.debug "No message available yet, subscribing. sequence: `#{sequence}`"
39
- # Either this resource has never been seen before or we are all caught up.
40
- # Subscribe and hope something gets published to this end-point.
41
- subscribe(deferrable, options[:timeout])
42
- elsif last_sequence > 0 && diff < Server::Publisher::MAX_MESSAGES
43
- # The client is kinda-sorta running behind, but has a chance to catch
44
- # up. Catch them up FTW.
45
- # But we won't "catch them up" if last_sequence was zero/nil because
46
- # that implies the client is connecting for the 1st time.
47
- message = message_list[diff-1]
48
- Firehose.logger.debug "Sending old message `#{message}` and sequence `#{sequence}` to client directly. Client is `#{diff}` behind, at `#{last_sequence}`."
49
- deferrable.succeed message, last_sequence + 1
50
- else
51
- # The client is hopelessly behind and underwater. Just reset
52
- # their whole world with the lastest message.
53
- message = message_list[0]
54
- Firehose.logger.debug "Sending latest message `#{message}` and sequence `#{sequence}` to client directly."
55
- deferrable.succeed message, sequence
56
- end
57
- end.errback {|e| deferrable.fail e }
58
-
59
- deferrable
60
- end
61
-
62
- def unsubscribe(deferrable)
63
- subscriber.unsubscribe channel_key, deferrable
64
- end
65
-
66
- private
67
- def subscribe(deferrable, timeout=nil)
68
- subscriber.subscribe(channel_key, deferrable)
69
- if timeout
70
- timer = EventMachine::Timer.new(timeout) do
71
- deferrable.fail :timeout
72
- unsubscribe deferrable
73
- end
74
- # Cancel the timer if when the deferrable succeeds
75
- deferrable.callback { timer.cancel }
76
- end
77
- end
78
- end
79
- end
80
- end
@@ -1,134 +0,0 @@
1
- module Firehose
2
- module Server
3
- class Publisher
4
- # Number of messages that Redis buffers for the client if its
5
- # connection drops, then reconnects.
6
- MAX_MESSAGES = 100
7
-
8
- # Seconds that the message buffer should live before Redis expires it.
9
- TTL = 60*60*24
10
-
11
- # Delimited used to frame different parts of a message that's published
12
- # over Firehose.
13
- PAYLOAD_DELIMITER = "\n"
14
-
15
- # Publish a message to a Firehose channel via Redis.
16
- def publish(channel_key, message, opts={})
17
- # How long should we hang on to the resource once is published?
18
- ttl = (opts[:ttl] || TTL).to_i
19
-
20
- # TODO hi-redis isn't that awesome... we have to setup an errback per even for wrong
21
- # commands because of the lack of a method_missing whitelist. Perhaps implement a whitelist in
22
- # em-hiredis or us a diff lib?
23
- if (deferrable = opts[:deferrable]).nil?
24
- deferrable = EM::DefaultDeferrable.new
25
- deferrable.errback do |e|
26
- # Handle missing Lua publishing script in cache
27
- # (such as Redis restarting or someone executing SCRIPT FLUSH)
28
- if e.message =~ /NOSCRIPT/
29
- deferrable.succeed
30
- EM.next_tick do
31
- @publish_script_digest = nil
32
- combined_opts = opts.merge :deferrable => deferrable
33
- self.publish channel_key, message, combined_opts
34
- end
35
- else
36
- EM.next_tick { raise e }
37
- end
38
- end
39
- end
40
-
41
- if @publish_script_digest.nil?
42
- register_publish_script.errback do |e|
43
- deferrable.fail e
44
- end.callback do |digest|
45
- @publish_script_digest = digest
46
- Firehose.logger.debug "Registered Lua publishing script with Redis => #{digest}"
47
- eval_publish_script channel_key, message, ttl, deferrable
48
- end
49
- else
50
- eval_publish_script channel_key, message, ttl, deferrable
51
- end
52
-
53
- deferrable
54
- end
55
-
56
- private
57
- def redis
58
- @redis ||= EM::Hiredis.connect
59
- end
60
-
61
- # Serialize components of a message into something that can be dropped into Redis.
62
- def self.to_payload(channel_key, sequence, message)
63
- [channel_key, sequence, message].join(PAYLOAD_DELIMITER)
64
- end
65
-
66
- # Deserealize components of a message back into Ruby.
67
- def self.from_payload(payload)
68
- payload.split(PAYLOAD_DELIMITER, method(:to_payload).arity)
69
- end
70
-
71
- # TODO: Make this FAR more robust. Ideally we'd whitelist the permitted
72
- # characters and then escape or remove everything else.
73
- # See: http://en.wikibooks.org/wiki/Lua_Programming/How_to_Lua/escape_sequence
74
- def lua_escape(str)
75
- str.gsub(/\\/,'\\\\\\').gsub(/"/,'\"').gsub(/\n/,'\n').gsub(/\r/,'\r')
76
- end
77
-
78
- def register_publish_script
79
- redis.script 'LOAD', REDIS_PUBLISH_SCRIPT
80
- end
81
-
82
- def eval_publish_script(channel_key, message, ttl, deferrable)
83
- list_key = Server.key(channel_key, :list)
84
- script_args = [
85
- Server.key(channel_key, :sequence),
86
- list_key,
87
- Server.key(:channel_updates),
88
- ttl,
89
- message,
90
- MAX_MESSAGES,
91
- PAYLOAD_DELIMITER,
92
- channel_key
93
- ]
94
- redis.evalsha(
95
- @publish_script_digest, script_args.length, *script_args
96
- ).errback do |e|
97
- deferrable.fail e
98
- end.callback do |sequence|
99
- Firehose.logger.debug "Redis stored/published `#{message}` to list `#{list_key}` with sequence `#{sequence}`"
100
- deferrable.succeed
101
- end
102
- end
103
-
104
- REDIS_PUBLISH_SCRIPT = <<-LUA
105
- local sequence_key = KEYS[1]
106
- local list_key = KEYS[2]
107
- local channel_key = KEYS[3]
108
- local ttl = KEYS[4]
109
- local message = KEYS[5]
110
- local max_messages = KEYS[6]
111
- local payload_delimiter = KEYS[7]
112
- local firehose_resource = KEYS[8]
113
-
114
- local current_sequence = redis.call('get', sequence_key)
115
- if current_sequence == nil or current_sequence == false then
116
- current_sequence = 0
117
- end
118
-
119
- local sequence = current_sequence + 1
120
- local message_payload = firehose_resource .. payload_delimiter .. sequence .. payload_delimiter .. message
121
-
122
- redis.call('set', sequence_key, sequence)
123
- redis.call('expire', sequence_key, ttl)
124
- redis.call('lpush', list_key, message)
125
- redis.call('ltrim', list_key, 0, max_messages - 1)
126
- redis.call('expire', list_key, ttl)
127
- redis.call('publish', channel_key, message_payload)
128
-
129
- return sequence
130
- LUA
131
-
132
- end
133
- end
134
- end
@@ -1,51 +0,0 @@
1
- module Firehose
2
- module Server
3
- # Setups a connetion to Redis to listen for new resources...
4
- class Subscriber
5
- attr_reader :pubsub
6
-
7
- def initialize(redis)
8
- @pubsub = redis.pubsub
9
- # TODO: Instead of just raising an exception, it would probably be better
10
- # for the errback to set some sort of 'disconnected' state. Then
11
- # whenever a deferrable was 'subscribed' we could instantly fail
12
- # the deferrable with whatever connection error we had.
13
- # An alternative which would have a similar result would be to
14
- # subscribe lazily (i.e. not until we have a deferrable to subscribe).
15
- # Then, if connecting failed, it'd be super easy to fail the deferrable
16
- # with the same error.
17
- # The final goal is to allow the failed deferrable bubble back up
18
- # so we can send back a nice, clean 500 error to the client.
19
- channel_updates_key = Server.key('channel_updates')
20
- pubsub.subscribe(channel_updates_key).
21
- errback{|e| EM.next_tick { raise e } }.
22
- callback { Firehose.logger.debug "Redis subscribed to `#{channel_updates_key}`" }
23
- pubsub.on(:message) do |_, payload|
24
- channel_key, sequence, message = Server::Publisher.from_payload(payload)
25
-
26
- if deferrables = subscriptions.delete(channel_key)
27
- Firehose.logger.debug "Redis notifying #{deferrables.count} deferrable(s) at `#{channel_key}` with sequence `#{sequence}` and message `#{message}`"
28
- deferrables.each do |deferrable|
29
- Firehose.logger.debug "Sending message #{message} and sequence #{sequence} to client from subscriber"
30
- deferrable.succeed message, sequence.to_i
31
- end
32
- end
33
- end
34
- end
35
-
36
- def subscribe(channel_key, deferrable)
37
- subscriptions[channel_key].push deferrable
38
- end
39
-
40
- def unsubscribe(channel_key, deferrable)
41
- subscriptions[channel_key].delete deferrable
42
- subscriptions.delete(channel_key) if subscriptions[channel_key].empty?
43
- end
44
-
45
- private
46
- def subscriptions
47
- @subscriptions ||= Hash.new{|h,k| h[k] = []}
48
- end
49
- end
50
- end
51
- end
@@ -1,4 +0,0 @@
1
- module Firehose
2
- VERSION = "1.2.20"
3
- CODENAME = "Onprogress of Doom"
4
- end
data/package.json DELETED
@@ -1,5 +0,0 @@
1
- {
2
- "name": "firehose",
3
- "version": "1.2.20",
4
- "main": "lib/assets/javascripts/firehose.js.coffee"
5
- }
@@ -1,22 +0,0 @@
1
- require 'spec_helper'
2
-
3
- module IntegrationTestHelper
4
- def start_server
5
- @server_pid = fork do
6
- Firehose::Server::App.new(:server => server, :port => uri.port).start
7
- end
8
-
9
- # Need to give the server a chance to boot up.
10
- sleep 1
11
- end
12
-
13
- def stop_server
14
- Process.kill 'INT', @server_pid
15
- end
16
-
17
-
18
- # Let isn't allowed in before(:all)
19
- def uri
20
- Firehose::URI
21
- end
22
- end
@@ -1,7 +0,0 @@
1
- require 'spec_helper'
2
- require 'integrations/shared_examples'
3
-
4
- describe "rainbows" do
5
- def server; :rainbows; end # Let isn't allowed in before(:all)
6
- it_behaves_like 'Firehose::Rack::App'
7
- end
@@ -1,120 +0,0 @@
1
- require 'spec_helper'
2
- require 'integrations/integration_test_helper'
3
- require 'json'
4
-
5
- shared_examples_for 'Firehose::Rack::App' do
6
- include EM::TestHelper
7
- include IntegrationTestHelper
8
-
9
- before(:all) do
10
- Firehose::Client::Producer::Http.adapter = :em_http
11
- start_server
12
- end
13
-
14
- after(:all) do
15
- Firehose::Client::Producer::Http.adapter = nil
16
- stop_server
17
- end
18
-
19
- before(:each) { WebMock.disable! }
20
- after(:each) { WebMock.enable! }
21
-
22
- let(:app) { Firehose::Rack::App.new }
23
- let(:messages) { (1..200).map{|n| "msg-#{n}" } }
24
- let(:channel) { "/firehose/integration/#{Time.now.to_i}" }
25
- let(:http_url) { "http://#{uri.host}:#{uri.port}#{channel}" }
26
- let(:ws_url) { "ws://#{uri.host}:#{uri.port}#{channel}" }
27
-
28
- it "should pub-sub http and websockets" do
29
- # Setup variables that we'll use after we turn off EM to validate our
30
- # test assertions.
31
- outgoing, received = messages.dup, Hash.new{|h,k| h[k] = []}
32
-
33
- # Our WS and Http clients call this when they have received their messages to determine
34
- # when to turn off EM and make the test assertion at the very bottom.
35
- succeed = Proc.new do
36
- # TODO: For some weird reason the `add_timer` call causes up to 20 seconds of delay after
37
- # the test finishes running. However, without it the test will randomly fail with a
38
- # "Redis disconnected" error.
39
- em.add_timer(1) { em.stop } if received.values.all?{|arr| arr.size == messages.size }
40
- end
41
-
42
- # Setup a publisher
43
- publish = Proc.new do
44
- Firehose::Client::Producer::Http.new.publish(outgoing.shift).to(channel) do
45
- # The random timer ensures that sometimes the clients will be behind
46
- # and sometimes they will be caught up.
47
- EM::add_timer(rand*0.005) { publish.call } unless outgoing.empty?
48
- end
49
- end
50
-
51
- # Lets have an HTTP Long poll client
52
- http_long_poll = Proc.new do |cid, last_sequence|
53
- http = EM::HttpRequest.new(http_url).get(:query => {'last_message_sequence' => last_sequence})
54
- http.errback { em.stop }
55
- http.callback do
56
- frame = JSON.parse(http.response, :symbolize_names => true)
57
- received[cid] << frame[:message]
58
- if received[cid].size < messages.size
59
- # Add some jitter so the clients aren't syncronized
60
- EM::add_timer(rand*0.001) { http_long_poll.call cid, frame[:last_sequence] }
61
- else
62
- succeed.call cid
63
- end
64
- end
65
- end
66
-
67
- # And test a web socket client too, at the same time.
68
- websocket = Proc.new do |cid|
69
- ws = Faye::WebSocket::Client.new(ws_url)
70
-
71
- ws.onopen = lambda do |event|
72
- ws.send('{"message_sequence":0}')
73
- end
74
-
75
- ws.onmessage = lambda do |event|
76
- frame = JSON.parse(event.data, :symbolize_names => true)
77
- received[cid] << frame[:message]
78
- succeed.call cid unless received[cid].size < messages.size
79
- end
80
-
81
- ws.onclose = lambda do |event|
82
- ws = nil
83
- end
84
-
85
- ws.onerror = lambda do |event|
86
- raise 'ws failed' + "\n" + event.inspect
87
- end
88
- end
89
-
90
- # Great, we have all the pieces in order, lets run this thing in the reactor.
91
- em 180 do
92
- # Start the clients.
93
- websocket.call(1)
94
- websocket.call(2)
95
- http_long_poll.call(3)
96
- http_long_poll.call(4)
97
-
98
- # Wait a sec to let our clients set up.
99
- em.add_timer(1){ publish.call }
100
- end
101
-
102
- # When EM stops, these assertions will be made.
103
- received.size.should == 4
104
- received.values.each do |arr|
105
- arr.should == messages
106
- end
107
- end
108
-
109
-
110
- it "should return 400 error for long-polling when using http long polling and sequence header is < 0" do
111
- em 5 do
112
- http = EM::HttpRequest.new(http_url).get(:query => {'last_message_sequence' => -1})
113
- http.errback { |e| raise e.inspect }
114
- http.callback do
115
- http.response_header.status.should == 400
116
- em.stop
117
- end
118
- end
119
- end
120
- end
@@ -1,7 +0,0 @@
1
- require 'spec_helper'
2
- require 'integrations/shared_examples'
3
-
4
- describe "thin" do
5
- def server; :thin; end # Let isn't allowed in before(:all)
6
- it_behaves_like 'Firehose::Rack::App'
7
- end
@@ -1,2 +0,0 @@
1
- describe 'Firehose namespace', ->
2
- it 'exists', -> expect( window.Firehose ).toEqual jasmine.any Object
@@ -1,45 +0,0 @@
1
- describe 'Firehose.Consumer', ->
2
- beforeEach ->
3
- @klass = Firehose.Consumer
4
- @instance = new @klass uri: 'test_uri', okInterval: 9999 # Stop crazy ajax loops during tests
5
-
6
-
7
- #= Specs ===
8
-
9
- describe 'new instance', ->
10
- describe '@config', ->
11
- beforeEach -> @subj = @instance.config
12
-
13
- for callback in ['message','error','connected','disconnected','failed']
14
- it "has a .#{callback} function", ->
15
- expect( @subj[callback] ).toEqual jasmine.any Function
16
-
17
- it 'has a params object', ->
18
- expect( @subj.params ).toEqual jasmine.any Object
19
-
20
- it 'has a parse function', ->
21
- expect( @subj.parse ).toEqual jasmine.any Function
22
-
23
- describe '#connect', ->
24
- it 'runs without error', -> @instance.connect()
25
-
26
- describe 'after #connect called', ->
27
- beforeEach -> @instance.connect()
28
-
29
- it 'creates a @transport instance', ->
30
- expect( @instance.transport ).toEqual jasmine.any Firehose.Transport
31
-
32
- describe '#stop', ->
33
- describe 'when .connect called earlier', ->
34
- beforeEach ->
35
- @instance.connect()
36
- sinon.spy @instance.transport, 'stop'
37
-
38
- it 'calls stop once on @transport', ->
39
- @instance.stop()
40
- expect( @instance.transport.stop.calledOnce ).toBe true
41
-
42
- it 'stops the upgrade timeout', ->
43
- @instance.stop()
44
- expect( @instance.upgradeTimeout? ).toBe false
45
-
@@ -1,44 +0,0 @@
1
- describe 'Firehose.LongPoll', ->
2
- beforeEach ->
3
- @klass = Firehose.LongPoll
4
- @instance = new @klass()
5
-
6
- afterEach -> @instance.stop()
7
-
8
- #= Specs ===
9
-
10
- it '.ieSupported returns a Boolean', ->
11
- expect( typeof @klass.ieSupported() ).toBe 'boolean'
12
-
13
- it '.supported returns a Boolean', ->
14
- expect( typeof @klass.supported() ).toBe 'boolean'
15
-
16
- describe '#stop', ->
17
- beforeEach ->
18
- @instance._lastRequest = $.ajax()
19
- @instance._lastPingRequest = $.ajax()
20
-
21
- it 'aborts the last request', ->
22
- sinon.spy @instance._lastRequest, 'abort'
23
- Object.freeze @instance # Prevents the delete call from modifying the request object, so we can verify the call
24
- @instance.stop()
25
- expect( @instance._lastRequest.abort.called ).toBe true
26
-
27
- it 'deletes reference to the last request', ->
28
- @instance.stop()
29
- expect( @instance._lastRequest? ).toBe false
30
-
31
- it 'aborts the last ping request', ->
32
- sinon.spy @instance._lastPingRequest, 'abort'
33
- Object.freeze @instance # Prevents the delete call from modifying the request object, so we can verify the call
34
- @instance.stop()
35
- expect( @instance._lastPingRequest.abort.called ).toBe true
36
-
37
- it 'deletes reference to the last ping request', ->
38
- @instance.stop()
39
- expect( @instance._lastPingRequest? ).toBe false
40
-
41
- describe 'callbacks', ->
42
- for callback in ['_request','_success','_ping','_error']
43
- it "has a #{callback} callback function", ->
44
- expect( @instance[callback] ).toEqual jasmine.any Function
@@ -1,32 +0,0 @@
1
- describe 'Firehose.Transport', ->
2
- beforeEach ->
3
- @klass = Firehose.Transport
4
- @instance = new @klass()
5
-
6
- it '.supported returns false', -> expect( @klass.supported() ).toBe false
7
- it '.name throws error', -> expect( -> @instance.name ).toThrow()
8
- it '.stop throws error', -> expect( -> @instance.stop ).toThrow()
9
-
10
- describe 'constructor', ->
11
- it 'returns an instance', -> expect( @instance ).toEqual jasmine.any @klass
12
- it 'sets the @config object', -> expect( @instance.config ).toEqual jasmine.any Object
13
- it 'sets a retry delay', -> expect( @instance._retryDelay ).toEqual jasmine.any Number
14
-
15
- describe '#connect', ->
16
- it 'returns itself', -> expect( @instance.connect() ).toBe @instance
17
-
18
- describe 'when given a 500 delay', ->
19
- beforeEach ->
20
- @clock = sinon.useFakeTimers()
21
- sinon.stub @instance, '_request'
22
- @instance.connect 500
23
-
24
- afterEach -> @clock.restore()
25
-
26
- it 'does not call _request before the given delay', ->
27
- @clock.tick 499
28
- expect( @instance._request.called ).toBe false
29
-
30
- it 'calls _request after the given delay', ->
31
- @clock.tick 600
32
- expect( @instance._request.called ).toBe true