hkroger-websocket-rails 0.7.1

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 (120) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +328 -0
  3. data/Gemfile +27 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +237 -0
  6. data/Rakefile +72 -0
  7. data/bin/thin-socketrails +45 -0
  8. data/lib/assets/javascripts/websocket_rails/abstract_connection.js.coffee +45 -0
  9. data/lib/assets/javascripts/websocket_rails/channel.js.coffee +70 -0
  10. data/lib/assets/javascripts/websocket_rails/event.js.coffee +42 -0
  11. data/lib/assets/javascripts/websocket_rails/http_connection.js.coffee +66 -0
  12. data/lib/assets/javascripts/websocket_rails/main.js +6 -0
  13. data/lib/assets/javascripts/websocket_rails/websocket_connection.js.coffee +29 -0
  14. data/lib/assets/javascripts/websocket_rails/websocket_rails.js.coffee +158 -0
  15. data/lib/config.ru +3 -0
  16. data/lib/generators/websocket_rails/install/install_generator.rb +33 -0
  17. data/lib/generators/websocket_rails/install/templates/events.rb +14 -0
  18. data/lib/generators/websocket_rails/install/templates/websocket_rails.rb +63 -0
  19. data/lib/hkroger-websocket-rails.rb +1 -0
  20. data/lib/rails/app/controllers/websocket_rails/delegation_controller.rb +13 -0
  21. data/lib/rails/config/routes.rb +7 -0
  22. data/lib/rails/tasks/websocket_rails.tasks +42 -0
  23. data/lib/spec_helpers/matchers/route_matchers.rb +65 -0
  24. data/lib/spec_helpers/matchers/trigger_matchers.rb +113 -0
  25. data/lib/spec_helpers/spec_helper_event.rb +34 -0
  26. data/lib/websocket-rails.rb +108 -0
  27. data/lib/websocket_rails/base_controller.rb +197 -0
  28. data/lib/websocket_rails/channel.rb +97 -0
  29. data/lib/websocket_rails/channel_manager.rb +55 -0
  30. data/lib/websocket_rails/configuration.rb +169 -0
  31. data/lib/websocket_rails/connection_adapters.rb +195 -0
  32. data/lib/websocket_rails/connection_adapters/http.rb +120 -0
  33. data/lib/websocket_rails/connection_adapters/web_socket.rb +36 -0
  34. data/lib/websocket_rails/connection_manager.rb +119 -0
  35. data/lib/websocket_rails/controller_factory.rb +80 -0
  36. data/lib/websocket_rails/data_store.rb +145 -0
  37. data/lib/websocket_rails/dispatcher.rb +129 -0
  38. data/lib/websocket_rails/engine.rb +26 -0
  39. data/lib/websocket_rails/event.rb +189 -0
  40. data/lib/websocket_rails/event_map.rb +184 -0
  41. data/lib/websocket_rails/event_queue.rb +33 -0
  42. data/lib/websocket_rails/internal_events.rb +37 -0
  43. data/lib/websocket_rails/logging.rb +133 -0
  44. data/lib/websocket_rails/spec_helpers.rb +3 -0
  45. data/lib/websocket_rails/synchronization.rb +182 -0
  46. data/lib/websocket_rails/user_manager.rb +276 -0
  47. data/lib/websocket_rails/version.rb +3 -0
  48. data/spec/dummy/Rakefile +7 -0
  49. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  50. data/spec/dummy/app/controllers/chat_controller.rb +53 -0
  51. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  52. data/spec/dummy/app/models/user.rb +2 -0
  53. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  54. data/spec/dummy/config.ru +4 -0
  55. data/spec/dummy/config/application.rb +45 -0
  56. data/spec/dummy/config/boot.rb +10 -0
  57. data/spec/dummy/config/database.yml +22 -0
  58. data/spec/dummy/config/environment.rb +5 -0
  59. data/spec/dummy/config/environments/development.rb +26 -0
  60. data/spec/dummy/config/environments/production.rb +49 -0
  61. data/spec/dummy/config/environments/test.rb +34 -0
  62. data/spec/dummy/config/events.rb +7 -0
  63. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  64. data/spec/dummy/config/initializers/inflections.rb +10 -0
  65. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  66. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  67. data/spec/dummy/config/initializers/session_store.rb +8 -0
  68. data/spec/dummy/config/locales/en.yml +5 -0
  69. data/spec/dummy/config/routes.rb +58 -0
  70. data/spec/dummy/db/development.sqlite3 +0 -0
  71. data/spec/dummy/db/migrate/20130902222552_create_users.rb +10 -0
  72. data/spec/dummy/db/schema.rb +23 -0
  73. data/spec/dummy/db/test.sqlite3 +0 -0
  74. data/spec/dummy/log/development.log +17 -0
  75. data/spec/dummy/log/production.log +0 -0
  76. data/spec/dummy/log/server.log +0 -0
  77. data/spec/dummy/public/404.html +26 -0
  78. data/spec/dummy/public/422.html +26 -0
  79. data/spec/dummy/public/500.html +26 -0
  80. data/spec/dummy/public/favicon.ico +0 -0
  81. data/spec/dummy/public/javascripts/application.js +2 -0
  82. data/spec/dummy/public/javascripts/controls.js +965 -0
  83. data/spec/dummy/public/javascripts/dragdrop.js +974 -0
  84. data/spec/dummy/public/javascripts/effects.js +1123 -0
  85. data/spec/dummy/public/javascripts/prototype.js +6001 -0
  86. data/spec/dummy/public/javascripts/rails.js +202 -0
  87. data/spec/dummy/script/rails +6 -0
  88. data/spec/integration/connection_manager_spec.rb +135 -0
  89. data/spec/javascripts/support/jasmine.yml +52 -0
  90. data/spec/javascripts/support/jasmine_helper.rb +38 -0
  91. data/spec/javascripts/support/vendor/sinon-1.7.1.js +4343 -0
  92. data/spec/javascripts/websocket_rails/channel_spec.coffee +112 -0
  93. data/spec/javascripts/websocket_rails/event_spec.coffee +69 -0
  94. data/spec/javascripts/websocket_rails/helpers.coffee +6 -0
  95. data/spec/javascripts/websocket_rails/websocket_connection_spec.coffee +158 -0
  96. data/spec/javascripts/websocket_rails/websocket_rails_spec.coffee +274 -0
  97. data/spec/spec_helper.rb +41 -0
  98. data/spec/spec_helpers/matchers/route_matchers_spec.rb +109 -0
  99. data/spec/spec_helpers/matchers/trigger_matchers_spec.rb +247 -0
  100. data/spec/spec_helpers/spec_helper_event_spec.rb +66 -0
  101. data/spec/support/helper_methods.rb +42 -0
  102. data/spec/support/mock_web_socket.rb +41 -0
  103. data/spec/unit/base_controller_spec.rb +74 -0
  104. data/spec/unit/channel_manager_spec.rb +58 -0
  105. data/spec/unit/channel_spec.rb +169 -0
  106. data/spec/unit/connection_adapters/http_spec.rb +88 -0
  107. data/spec/unit/connection_adapters/web_socket_spec.rb +30 -0
  108. data/spec/unit/connection_adapters_spec.rb +259 -0
  109. data/spec/unit/connection_manager_spec.rb +148 -0
  110. data/spec/unit/controller_factory_spec.rb +76 -0
  111. data/spec/unit/data_store_spec.rb +106 -0
  112. data/spec/unit/dispatcher_spec.rb +203 -0
  113. data/spec/unit/event_map_spec.rb +120 -0
  114. data/spec/unit/event_queue_spec.rb +36 -0
  115. data/spec/unit/event_spec.rb +181 -0
  116. data/spec/unit/logging_spec.rb +162 -0
  117. data/spec/unit/synchronization_spec.rb +150 -0
  118. data/spec/unit/target_validator_spec.rb +88 -0
  119. data/spec/unit/user_manager_spec.rb +165 -0
  120. metadata +320 -0
@@ -0,0 +1,42 @@
1
+ require 'json'
2
+
3
+ module WebsocketRails
4
+ module HelperMethods
5
+ def env
6
+ @_env ||= begin
7
+ env = Rack::MockRequest.env_for('/websocket')
8
+ env['async.callback'] = Proc.new { |response| true }
9
+ env
10
+ end
11
+ end
12
+
13
+ def mock_request
14
+ @_request ||= ActionDispatch::Request.new(env)
15
+ end
16
+
17
+ def encoded_message
18
+ ['test_event',{:data => {:user_name => 'Joe User'}}].to_json
19
+ end
20
+
21
+ def subscribe_encoded_message
22
+ ['websocket_rails.subscribe',:data => nil, :channel => :awesome_channel].to_json
23
+ end
24
+
25
+ def received_encoded_message(connection_id)
26
+ [connection_id,'test_event',{:user_name => 'Joe User'}].to_json
27
+ end
28
+
29
+ MockEvent = Struct.new(:name,:namespace)
30
+ end
31
+ end
32
+
33
+ module EM
34
+ def self.next_tick(&block)
35
+ block.call if block.respond_to?(:call)
36
+ end
37
+ class PeriodicTimer
38
+ def initialize(interval)
39
+ @interval = interval
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ module WebsocketRails
4
+
5
+ class MockWebSocket
6
+ attr_writer :onmessage, :onerror, :onclose
7
+
8
+ def env
9
+ env = Rack::MockRequest.env_for('/websocket')
10
+ end
11
+
12
+ def onmessage(event=nil)
13
+ @onmessage.call(event)
14
+ end
15
+
16
+ def onerror(event=nil)
17
+ @onerror.call(event)
18
+ end
19
+
20
+ def onclose(event=nil)
21
+ @onclose.call(event)
22
+ end
23
+
24
+ def rack_response
25
+ [ -1, {}, [] ]
26
+ end
27
+
28
+ def send(*args)
29
+ true
30
+ end
31
+
32
+ def trigger(event)
33
+ true
34
+ end
35
+
36
+ def id
37
+ object_id.to_i
38
+ end
39
+ end
40
+
41
+ end
@@ -0,0 +1,74 @@
1
+ require "spec_helper"
2
+
3
+ module WebsocketRails
4
+ describe BaseController do
5
+
6
+ class TestClass; end;
7
+
8
+ describe ".inherited" do
9
+ context "with Rails version 3" do
10
+ before do
11
+ Rails.stub(:version).and_return("3.2.13")
12
+ end
13
+
14
+ it "should call unloadable on the inherited class" do
15
+ Object.should_receive(:unloadable).with(TestClass)
16
+ BaseController.inherited(TestClass)
17
+ end
18
+ end
19
+
20
+ context "with Rails version 4" do
21
+ before do
22
+ Rails.stub(:version).and_return("4.0.0")
23
+ end
24
+
25
+ it "should call unloadable on the inherited class" do
26
+ Object.should_not_receive(:unloadable).with(TestClass)
27
+ BaseController.inherited(TestClass)
28
+ end
29
+ end
30
+ end
31
+
32
+ describe "before actions" do
33
+ class BeforeActionController < WebsocketRails::BaseController
34
+ before_action { self.before_array << :all }
35
+ before_action(:only => :only) { self.before_array << :only_1 }
36
+ before_action(:only => :except) { self.before_array << :only_2 }
37
+ before_action(:only => [:main, :only]) { self.before_array << :only_3 }
38
+ before_action(:only => [:except, :only]) { self.before_array << :only_4 }
39
+ before_action(:except => :except) { self.before_array << :except_1 }
40
+ before_action(:except => :only) { self.before_array << :except_2 }
41
+ before_action(:except => [:main, :except]){ self.before_array << :except_3 }
42
+ before_action(:except => [:only, :except]){ self.before_array << :except_4 }
43
+
44
+ attr_accessor :before_array
45
+
46
+ def initialize
47
+ @before_array = []
48
+ end
49
+ def main;end
50
+ def only;end
51
+ def except;end
52
+ end
53
+
54
+ let(:controller) { BeforeActionController.new }
55
+ it 'should handle before_action with no args' do
56
+ controller.instance_variable_set :@_action_name, 'main'
57
+ controller.process_action(:main, nil)
58
+ controller.before_array.should == [:all, :only_3, :except_1, :except_2, :except_4]
59
+ end
60
+
61
+ it 'should handle before_action with :only flag' do
62
+ controller.instance_variable_set :@_action_name, 'only'
63
+ controller.process_action(:only, nil)
64
+ controller.before_array.should == [:all, :only_1, :only_3, :only_4, :except_1, :except_3]
65
+ end
66
+
67
+ it 'should handle before_action with :except flag' do
68
+ controller.instance_variable_set :@_action_name, 'except'
69
+ controller.process_action(:except, nil)
70
+ controller.before_array.should == [:all, :only_2, :only_4, :except_2]
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+
3
+ module WebsocketRails
4
+
5
+ describe ".channel_manager" do
6
+ it "should load a new channel manager when first called" do
7
+ WebsocketRails.channel_manager.should be_a ChannelManager
8
+ end
9
+ end
10
+
11
+ describe ".[]" do
12
+ it "should delegate to channel manager" do
13
+ ChannelManager.any_instance.should_receive(:[]).with(:awesome_channel)
14
+ WebsocketRails[:awesome_channel]
15
+ end
16
+ end
17
+
18
+ describe ".channel_tokens" do
19
+ it "should delegate to channel manager" do
20
+ ChannelManager.any_instance.should_receive(:channel_tokens)
21
+ WebsocketRails.channel_tokens
22
+ end
23
+ end
24
+
25
+ describe ChannelManager do
26
+
27
+ describe "#channel_tokens" do
28
+ it "should return a Hash-like" do
29
+ subject.channel_tokens.respond_to? :[]
30
+ subject.channel_tokens.respond_to? :has_key?
31
+ end
32
+
33
+ it 'is used to store Channel\'s token' do
34
+ ChannelManager.any_instance.should_receive(:channel_tokens)
35
+ .at_least(:twice).and_call_original
36
+ token = Channel.new(:my_new_test_channel).token
37
+ WebsocketRails.channel_tokens[:my_new_test_channel].should == token
38
+ end
39
+ end
40
+
41
+ describe "#[]" do
42
+ context "accessing a channel" do
43
+ it "should create the channel if it does not exist" do
44
+ subject[:awesome_channel].class.should == Channel
45
+ end
46
+ end
47
+ end
48
+
49
+ describe "unsubscribe" do
50
+ it "should unsubscribe connection from all channels" do
51
+ subject[:awesome_channel].should_receive(:unsubscribe).with(:some_connection)
52
+ subject[:awesome_channel]
53
+ subject.unsubscribe(:some_connection)
54
+ end
55
+ end
56
+
57
+ end
58
+ end
@@ -0,0 +1,169 @@
1
+ require 'spec_helper'
2
+
3
+ module WebsocketRails
4
+ describe Channel do
5
+ subject { Channel.new :awesome_channel }
6
+
7
+ let(:connection) { double('connection') }
8
+
9
+ before do
10
+ connection.stub(:trigger)
11
+ end
12
+
13
+ it "should maintain a pool of subscribed connections" do
14
+ subject.subscribers.should == []
15
+ end
16
+
17
+ describe "#subscribe" do
18
+ before do
19
+ connection.stub(:user).and_return({})
20
+ WebsocketRails.config.stub(:broadcast_subscriber_events?).and_return(true)
21
+ end
22
+ it "should trigger an event when subscriber joins" do
23
+ subject.should_receive(:trigger).with("subscriber_join", connection.user)
24
+ subject.subscribe connection
25
+ end
26
+
27
+ it "should add the connection to the subscriber pool" do
28
+ subject.subscribe connection
29
+ subject.subscribers.include?(connection).should be_true
30
+ end
31
+ end
32
+
33
+ describe "#unsubscribe" do
34
+ before do
35
+ connection.stub(:user).and_return({})
36
+ WebsocketRails.config.stub(:broadcast_subscriber_events?).and_return(true)
37
+ end
38
+ it "should remove connection from subscriber pool" do
39
+ subject.subscribe connection
40
+ subject.unsubscribe connection
41
+ subject.subscribers.include?(connection).should be_false
42
+ end
43
+
44
+ it "should do nothing if connection is not subscribed to channel" do
45
+ subject.unsubscribe connection
46
+ subject.subscribers.include?(connection).should be_false
47
+ end
48
+
49
+ it "should trigger an event when subscriber parts" do
50
+ subject.subscribers << connection
51
+ subject.should_receive(:trigger).with('subscriber_part', connection.user)
52
+ subject.unsubscribe connection
53
+ end
54
+ end
55
+
56
+ describe "#trigger" do
57
+ it "should create a new event and trigger it on all subscribers" do
58
+ event = double('event').as_null_object
59
+ Event.should_receive(:new) do |name,options|
60
+ name.should == 'event'
61
+ options[:data].should == 'data'
62
+ event
63
+ end
64
+ connection.should_receive(:trigger).with(event)
65
+ subject.subscribers << connection
66
+ subject.trigger 'event', 'data'
67
+ end
68
+ end
69
+
70
+ describe "#trigger_event" do
71
+ it "should forward the event to subscribers if token matches" do
72
+ event = Event.new 'awesome_event', {:channel => 'awesome_channel', :token => subject.token}
73
+ subject.should_receive(:send_data).with(event)
74
+ subject.trigger_event event
75
+ end
76
+
77
+ it "should ignore the event if the token is invalid" do
78
+ event = Event.new 'invalid_event', {:channel => 'awesome_channel', :token => 'invalid_token'}
79
+ subject.should_not_receive(:send_data).with(event)
80
+ subject.trigger_event event
81
+ end
82
+
83
+ it "should not propagate if event.propagate is false" do
84
+ event = Event.new 'awesome_event', {:channel => 'awesome_channel', :token => subject.token, :propagate => false}
85
+ connection.should_not_receive(:trigger)
86
+ subject.subscribers << connection
87
+ subject.trigger_event event
88
+ end
89
+ end
90
+
91
+ describe "#filter_with" do
92
+ it "should add the controller to the filtered_channels hash" do
93
+ filter = double('BaseController')
94
+ subject.filter_with(filter)
95
+ subject.filtered_channels[subject.name].should eq(filter)
96
+ end
97
+
98
+ it "should allow setting the catch_all method" do
99
+ filter = double('BaseController')
100
+ subject.filter_with(filter, :some_method)
101
+ subject.filtered_channels[subject.name].should eq([filter, :some_method])
102
+ end
103
+ end
104
+
105
+ context "private channels" do
106
+ before do
107
+ subject.subscribers << connection
108
+ end
109
+
110
+ it "should be public by default" do
111
+ subject.instance_variable_get(:@private).should_not be_true
112
+ end
113
+
114
+ describe "#make_private" do
115
+ it "should set the @private instance variable to true" do
116
+ subject.make_private
117
+ subject.instance_variable_get(:@private).should be_true
118
+ end
119
+
120
+ context "when Configuration#keep_subscribers_when_private? is false" do
121
+ it "should clear any existing subscribers in the channel" do
122
+ subject.subscribers.count.should == 1
123
+ subject.make_private
124
+ subject.subscribers.count.should == 0
125
+ end
126
+ end
127
+
128
+ context "when Configuration#keep_subscribers_when_private? is true" do
129
+ before do
130
+ WebsocketRails.config.keep_subscribers_when_private = true
131
+ end
132
+
133
+ it "should leave the existing subscribers in the channel" do
134
+ subject.subscribers.count.should == 1
135
+ subject.make_private
136
+ subject.subscribers.count.should == 1
137
+ end
138
+ end
139
+ end
140
+
141
+ describe "#is_private?" do
142
+ it "should return true if the channel is private" do
143
+ subject.instance_variable_set(:@private,true)
144
+ subject.is_private?.should be_true
145
+ end
146
+
147
+ it "should return false if the channel is public" do
148
+ subject.instance_variable_set(:@private,false)
149
+ subject.is_private?.should_not be_true
150
+ end
151
+ end
152
+
153
+ describe "#token" do
154
+ it 'is long enough' do
155
+ subject.token.length.should > 10
156
+ end
157
+
158
+ it 'remains the same between two call' do
159
+ subject.token.should == subject.token
160
+ end
161
+
162
+ it 'is the same for two channels with the same name' do
163
+ subject.token.should == Channel.new(subject.name).token
164
+ end
165
+ end
166
+
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,88 @@
1
+ require 'spec_helper'
2
+
3
+ module WebsocketRails
4
+ module ConnectionAdapters
5
+ describe Http do
6
+
7
+ subject {
8
+ mr = mock_request
9
+ mr.stub(:protocol).and_return('http://')
10
+ mr.stub(:raw_host_with_port).and_return('localhost:3000')
11
+ Http.new(mr , double('Dispatcher').as_null_object )
12
+ }
13
+
14
+ it "should be a subclass of ConnectionAdapters::Base" do
15
+ subject.class.superclass.should == ConnectionAdapters::Base
16
+ end
17
+
18
+ it "should set the Content-Length header to text/json" do
19
+ subject.headers['Content-Type'].should == "text/json"
20
+ end
21
+
22
+ it "should set the Transfer-Encoding header to chunked" do
23
+ subject.headers['Transfer-Encoding'].should == "chunked"
24
+ end
25
+
26
+ it "should not set the Access-Control-Allow-Origin header" do
27
+ subject.headers['Access-Control-Allow-Origin'].should be_blank
28
+ end
29
+
30
+ context "with IE CORS hack enabled" do
31
+ it "should set the Access-Control-Allow-Origin when passed an array as configuration" do
32
+ WebsocketRails.config.allowed_origins = ['http://localhost:3000']
33
+ subject.headers['Access-Control-Allow-Origin'].should == 'http://localhost:3000'
34
+ end
35
+
36
+ it "should set the Access-Control-Allow-Origin when passed a string as configuration" do
37
+ WebsocketRails.config.allowed_origins = 'http://localhost:3000'
38
+ subject.headers['Access-Control-Allow-Origin'].should == 'http://localhost:3000'
39
+ end
40
+ end
41
+
42
+ context "#encode_chunk" do
43
+ it "should properly encode strings" do
44
+ subject.__send__(:encode_chunk,"test").should == "4\r\ntest\r\n"
45
+ end
46
+ end
47
+
48
+ context "adapter methods" do
49
+ before do
50
+ @body = double('DeferrableBody').as_null_object
51
+ Http::DeferrableBody.stub(:new).and_return(@body)
52
+ end
53
+
54
+ context "#define_deferrable_callbacks" do
55
+ it "should define a callback for :succeeded" do
56
+ @body.should_receive(:callback)
57
+ subject
58
+ end
59
+
60
+ it "should define a callback for :failed" do
61
+ @body.should_receive(:errback)
62
+ subject
63
+ end
64
+ end
65
+
66
+ context "#send" do
67
+ it "should encode the message before sending" do
68
+ subject.should_receive(:encode_chunk).with('test message')
69
+ subject.send 'test message'
70
+ end
71
+
72
+ it "should enqueue the message on DeferrableBody" do
73
+ encoded_message = subject.__send__(:encode_chunk,'test message')
74
+ @body.should_receive(:chunk).with(encoded_message)
75
+ subject.send 'test message'
76
+ end
77
+ end
78
+
79
+ describe "#close!" do
80
+ it "calls #close! on the DefferableBody instance" do
81
+ @body.should_receive(:close!)
82
+ subject.close!
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end