websocket-rails 0.1.8 → 0.1.9
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +15 -2
- data/Gemfile +3 -1
- data/lib/assets/javascripts/websocket_rails/websocket_connection.js.coffee +5 -0
- data/lib/websocket-rails.rb +31 -5
- data/lib/websocket_rails/channel.rb +17 -7
- data/lib/websocket_rails/channel_manager.rb +1 -1
- data/lib/websocket_rails/connection_adapters.rb +10 -9
- data/lib/websocket_rails/connection_adapters/http.rb +8 -8
- data/lib/websocket_rails/connection_adapters/web_socket.rb +4 -4
- data/lib/websocket_rails/connection_manager.rb +25 -12
- data/lib/websocket_rails/data_store.rb +10 -10
- data/lib/websocket_rails/dispatcher.rb +12 -9
- data/lib/websocket_rails/event.rb +23 -11
- data/lib/websocket_rails/event_map.rb +8 -8
- data/lib/websocket_rails/logging.rb +18 -2
- data/lib/websocket_rails/synchronization.rb +92 -0
- data/lib/websocket_rails/version.rb +1 -1
- data/spec/dummy/log/test.log +300 -0
- data/spec/integration/connection_manager_spec.rb +15 -15
- data/spec/javascripts/generated/assets/http_connection.js +1 -1
- data/spec/javascripts/generated/assets/websocket_connection.js +9 -0
- data/spec/unit/channel_spec.rb +3 -3
- data/spec/unit/connection_manager_spec.rb +25 -15
- data/spec/unit/dispatcher_spec.rb +21 -9
- data/spec/unit/event_spec.rb +11 -1
- data/spec/unit/logging_spec.rb +38 -0
- data/spec/unit/synchronization_spec.rb +68 -0
- metadata +56 -5
@@ -7,7 +7,7 @@ module WebsocketRails
|
|
7
7
|
class ProductController < BaseController
|
8
8
|
def update_list; true; end
|
9
9
|
end
|
10
|
-
|
10
|
+
|
11
11
|
def define_test_events
|
12
12
|
WebsocketRails.route_block = nil
|
13
13
|
WebsocketRails::EventMap.describe do
|
@@ -23,13 +23,13 @@ module WebsocketRails
|
|
23
23
|
end
|
24
24
|
end
|
25
25
|
end
|
26
|
-
|
27
|
-
before(:all)
|
26
|
+
|
27
|
+
before(:all) do
|
28
28
|
define_test_events
|
29
29
|
if defined?(ConnectionAdapters::Test)
|
30
30
|
ConnectionAdapters.adapters.delete( ConnectionAdapters::Test )
|
31
31
|
end
|
32
|
-
|
32
|
+
end
|
33
33
|
|
34
34
|
shared_examples "an evented rack server" do
|
35
35
|
context "new connections" do
|
@@ -38,12 +38,12 @@ module WebsocketRails
|
|
38
38
|
@server.call( env )
|
39
39
|
end
|
40
40
|
end
|
41
|
-
|
41
|
+
|
42
42
|
context "active connections" do
|
43
43
|
context "new message from client" do
|
44
44
|
let(:test_message) { ['change_username',{:user_name => 'Joe User'}] }
|
45
45
|
let(:encoded_message) { test_message.to_json }
|
46
|
-
|
46
|
+
|
47
47
|
it "should execute the controller action associated with the received event" do
|
48
48
|
ChatController.any_instance.should_receive(:change_username)
|
49
49
|
@server.call( env )
|
@@ -54,7 +54,7 @@ module WebsocketRails
|
|
54
54
|
context "new message from client under a namespace" do
|
55
55
|
let(:test_message) { ['products.update_list',{:product => 'x-ray-vision'}] }
|
56
56
|
let(:encoded_message) { test_message.to_json }
|
57
|
-
|
57
|
+
|
58
58
|
it "should execute the controller action under the correct namespace" do
|
59
59
|
ChatController.any_instance.should_not_receive(:update_user_list)
|
60
60
|
ProductController.any_instance.should_receive(:update_list)
|
@@ -64,17 +64,17 @@ module WebsocketRails
|
|
64
64
|
end
|
65
65
|
|
66
66
|
context "subscribing to a channel" do
|
67
|
-
let(:channel_message) { ['websocket_rails.subscribe',{:data => {
|
67
|
+
let(:channel_message) { ['websocket_rails.subscribe',{:data => {:channel => 'test_chan'}}] }
|
68
68
|
let(:encoded_channel_message) { channel_message.to_json }
|
69
69
|
|
70
70
|
it "should subscribe the connection to the correct channel" do
|
71
|
-
|
72
|
-
|
73
|
-
channel.should_receive(:subscribe).
|
74
|
-
socket.on_message encoded_channel_message
|
71
|
+
#channel = WebsocketRails[:test_chan]
|
72
|
+
#@server.call( env )
|
73
|
+
#channel.should_receive(:subscribe).with(socket)
|
74
|
+
#socket.on_message encoded_channel_message
|
75
75
|
end
|
76
76
|
end
|
77
|
-
|
77
|
+
|
78
78
|
context "client error" do
|
79
79
|
it "should execute the controller action associated with the 'client_error' event" do
|
80
80
|
ChatController.any_instance.should_receive(:error_occurred)
|
@@ -82,7 +82,7 @@ module WebsocketRails
|
|
82
82
|
socket.on_error
|
83
83
|
end
|
84
84
|
end
|
85
|
-
|
85
|
+
|
86
86
|
context "client disconnects" do
|
87
87
|
it "should execute the controller action associated with the 'client_disconnected' event" do
|
88
88
|
ChatController.any_instance.should_receive(:delete_user)
|
@@ -95,7 +95,7 @@ module WebsocketRails
|
|
95
95
|
|
96
96
|
context "WebSocket Adapter" do
|
97
97
|
let(:socket) { @server.connections.first }
|
98
|
-
|
98
|
+
|
99
99
|
before do
|
100
100
|
::Faye::WebSocket.stub(:websocket?).and_return(true)
|
101
101
|
@server = ConnectionManager.new
|
@@ -63,7 +63,7 @@
|
|
63
63
|
if (this._conn.readyState === 3) {
|
64
64
|
data = this._conn.responseText.substring(this.last_pos);
|
65
65
|
this.last_pos = this._conn.responseText.length;
|
66
|
-
data = data.replace(
|
66
|
+
data = data.replace(/\]\]\[\[/g, "],[");
|
67
67
|
decoded_data = JSON.parse(data);
|
68
68
|
return this.dispatcher.new_message(decoded_data);
|
69
69
|
}
|
@@ -14,6 +14,8 @@ WebSocket Interface for the WebSocketRails client.
|
|
14
14
|
this.dispatcher = dispatcher;
|
15
15
|
this.flush_queue = __bind(this.flush_queue, this);
|
16
16
|
|
17
|
+
this.on_error = __bind(this.on_error, this);
|
18
|
+
|
17
19
|
this.on_close = __bind(this.on_close, this);
|
18
20
|
|
19
21
|
this.on_message = __bind(this.on_message, this);
|
@@ -27,6 +29,7 @@ WebSocket Interface for the WebSocketRails client.
|
|
27
29
|
this._conn = new WebSocket(this.url);
|
28
30
|
this._conn.onmessage = this.on_message;
|
29
31
|
this._conn.onclose = this.on_close;
|
32
|
+
this._conn.onerror = this.on_error;
|
30
33
|
}
|
31
34
|
|
32
35
|
WebSocketConnection.prototype.trigger = function(event) {
|
@@ -49,6 +52,12 @@ WebSocket Interface for the WebSocketRails client.
|
|
49
52
|
return this.dispatcher.dispatch(close_event);
|
50
53
|
};
|
51
54
|
|
55
|
+
WebSocketConnection.prototype.on_error = function(event) {
|
56
|
+
var error_event;
|
57
|
+
error_event = new WebSocketRails.Event(['connection_error', event != null ? event.data : void 0]);
|
58
|
+
return this.dispatcher.dispatch(error_event);
|
59
|
+
};
|
60
|
+
|
52
61
|
WebSocketConnection.prototype.flush_queue = function() {
|
53
62
|
var event, _i, _len, _ref;
|
54
63
|
_ref = this.message_queue;
|
data/spec/unit/channel_spec.rb
CHANGED
@@ -24,14 +24,14 @@ module WebsocketRails
|
|
24
24
|
describe "#trigger" do
|
25
25
|
it "should create a new event and trigger it on all subscribers" do
|
26
26
|
event = double('event').as_null_object
|
27
|
-
Event.should_receive(:new) do |name,
|
27
|
+
Event.should_receive(:new) do |name,options|
|
28
28
|
name.should == 'event'
|
29
|
-
|
29
|
+
options[:data].should == 'data'
|
30
30
|
event
|
31
31
|
end
|
32
32
|
connection.should_receive(:trigger).with(event)
|
33
33
|
subject.subscribe connection
|
34
|
-
subject.trigger 'event',
|
34
|
+
subject.trigger 'event', 'data'
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
3
3
|
module WebsocketRails
|
4
4
|
describe ConnectionManager do
|
5
5
|
include Rack::Test::Methods
|
6
|
-
|
6
|
+
|
7
7
|
def app
|
8
8
|
@app ||= ConnectionManager.new
|
9
9
|
end
|
@@ -11,26 +11,36 @@ module WebsocketRails
|
|
11
11
|
def open_connection
|
12
12
|
subject.call(env)
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
let(:connections) { subject.connections }
|
16
16
|
let(:dispatcher) { subject.dispatcher }
|
17
|
-
|
17
|
+
|
18
18
|
before(:each) do
|
19
19
|
ConnectionAdapters::Base.any_instance.stub(:send)
|
20
20
|
@mock_socket = ConnectionAdapters::Base.new(mock_request,dispatcher)
|
21
21
|
ConnectionAdapters.stub(:establish_connection).and_return(@mock_socket)
|
22
22
|
end
|
23
|
-
|
23
|
+
|
24
|
+
describe "#initialize" do
|
25
|
+
it "should create an empty connections array" do
|
26
|
+
subject.connections.should be_a Array
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should create a new dispatcher instance" do
|
30
|
+
subject.dispatcher.should be_a Dispatcher
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
24
34
|
context "new connections" do
|
25
35
|
it "should add one to the total connection count" do
|
26
36
|
expect { open_connection }.to change { connections.count }.by(1)
|
27
37
|
end
|
28
|
-
|
38
|
+
|
29
39
|
it "should store the new connection in the @connections array" do
|
30
40
|
open_connection
|
31
41
|
connections.include?(@mock_socket).should be_true
|
32
42
|
end
|
33
|
-
|
43
|
+
|
34
44
|
it "should return an Async Rack response" do
|
35
45
|
open_connection.should == [ -1, {}, [] ]
|
36
46
|
end
|
@@ -41,19 +51,19 @@ module WebsocketRails
|
|
41
51
|
@mock_http = ConnectionAdapters::Http.new(mock_request,dispatcher)
|
42
52
|
app.connections << @mock_http
|
43
53
|
end
|
44
|
-
|
54
|
+
|
45
55
|
it "should receive the new event for the correct connection" do
|
46
56
|
@mock_http.should_receive(:on_message).with(encoded_message)
|
47
57
|
post '/websocket', {:client_id => @mock_http.id, :data => encoded_message}
|
48
58
|
end
|
49
59
|
end
|
50
|
-
|
60
|
+
|
51
61
|
context "open connections" do
|
52
62
|
before(:each) do
|
53
63
|
ConnectionAdapters.stub(:establish_connection).and_return(@mock_socket,ConnectionAdapters::Base.new(mock_request,dispatcher))
|
54
64
|
4.times { open_connection }
|
55
65
|
end
|
56
|
-
|
66
|
+
|
57
67
|
context "when receiving a new event" do
|
58
68
|
before(:each) { open_connection }
|
59
69
|
|
@@ -67,17 +77,17 @@ module WebsocketRails
|
|
67
77
|
@mock_socket.on_message(mock_event)
|
68
78
|
end
|
69
79
|
end
|
70
|
-
|
80
|
+
|
71
81
|
context "when closing" do
|
72
82
|
it "should remove the connection object from the @connections array" do
|
73
83
|
@mock_socket.on_close
|
74
84
|
connections.include?(@mock_socket).should be_false
|
75
85
|
end
|
76
|
-
|
86
|
+
|
77
87
|
it "should decrement the connection count by one" do
|
78
88
|
expect { @mock_socket.on_close }.to change { connections.count }.by(-1)
|
79
89
|
end
|
80
|
-
|
90
|
+
|
81
91
|
it "should dispatch the :client_disconnected event" do
|
82
92
|
dispatcher.should_receive(:dispatch) do |event|
|
83
93
|
event.name.should == :client_disconnected
|
@@ -86,14 +96,14 @@ module WebsocketRails
|
|
86
96
|
@mock_socket.on_close
|
87
97
|
end
|
88
98
|
end
|
89
|
-
|
99
|
+
|
90
100
|
end
|
91
|
-
|
101
|
+
|
92
102
|
context "invalid connections" do
|
93
103
|
before(:each) do
|
94
104
|
ConnectionAdapters.stub(:establish_connection).and_raise(InvalidConnectionError)
|
95
105
|
end
|
96
|
-
|
106
|
+
|
97
107
|
it "should return a 400 bad request error code" do
|
98
108
|
open_connection.first.should == 400
|
99
109
|
end
|
@@ -2,22 +2,22 @@ require 'spec_helper'
|
|
2
2
|
require 'support/mock_web_socket'
|
3
3
|
|
4
4
|
module WebsocketRails
|
5
|
-
|
5
|
+
|
6
6
|
class EventTarget
|
7
7
|
attr_reader :_event, :test_method
|
8
|
-
|
8
|
+
|
9
9
|
def execute_observers(event_name)
|
10
10
|
true
|
11
11
|
end
|
12
12
|
end
|
13
|
-
|
13
|
+
|
14
14
|
describe Dispatcher do
|
15
|
-
|
15
|
+
|
16
16
|
let(:event) { double('Event') }
|
17
17
|
let(:connection) { MockWebSocket.new }
|
18
18
|
let(:connection_manager) { double('connection_manager').as_null_object }
|
19
19
|
subject { Dispatcher.new(connection_manager) }
|
20
|
-
|
20
|
+
|
21
21
|
describe "#receive_encoded" do
|
22
22
|
context "receiving a new message" do
|
23
23
|
before do
|
@@ -42,7 +42,7 @@ module WebsocketRails
|
|
42
42
|
subject.receive(:test_event,{},connection)
|
43
43
|
end
|
44
44
|
end
|
45
|
-
|
45
|
+
|
46
46
|
context "dispatching a message for an event" do
|
47
47
|
before do
|
48
48
|
@target = EventTarget.new
|
@@ -51,13 +51,14 @@ module WebsocketRails
|
|
51
51
|
event.stub(:data).and_return(:some_message)
|
52
52
|
event.stub(:connection).and_return(connection)
|
53
53
|
event.stub(:is_channel?).and_return(false)
|
54
|
+
event.stub(:is_invalid?).and_return(false)
|
54
55
|
end
|
55
|
-
|
56
|
+
|
56
57
|
it "should execute the correct method on the target class" do
|
57
58
|
@target.should_receive(:test_method)
|
58
59
|
subject.dispatch(event)
|
59
60
|
end
|
60
|
-
|
61
|
+
|
61
62
|
it "should set the _event instance variable on the target object" do
|
62
63
|
subject.dispatch(event)
|
63
64
|
@target._event.should == event
|
@@ -72,8 +73,19 @@ module WebsocketRails
|
|
72
73
|
subject.dispatch event
|
73
74
|
end
|
74
75
|
end
|
76
|
+
|
77
|
+
context "invalid events" do
|
78
|
+
before do
|
79
|
+
event.stub(:is_invalid?).and_return(true)
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should not dispatch the event" do
|
83
|
+
subject.should_not_receive(:route)
|
84
|
+
subject.dispatch(event)
|
85
|
+
end
|
86
|
+
end
|
75
87
|
end
|
76
|
-
|
88
|
+
|
77
89
|
describe "#send_message" do
|
78
90
|
before do
|
79
91
|
@event = Event.new_from_json( encoded_message, connection )
|
data/spec/unit/event_spec.rb
CHANGED
@@ -6,7 +6,8 @@ module WebsocketRails
|
|
6
6
|
let(:encoded_message_string) { '["new_message",{"id":"1234","data":"this is a message"}]' }
|
7
7
|
let(:namespace_encoded_message_string) { '["product.new_message",{"id":"1234","data":"this is a message"}]' }
|
8
8
|
let(:namespace_encoded_message) { '["product.new_message",{"id":"1234","data":{"message":"this is a message"}}]' }
|
9
|
-
let(:channel_encoded_message_string) { '["new_message",{"id":"1234","channel":"awesome_channel","data":"this is a message","success":null,"result":null}]' }
|
9
|
+
let(:channel_encoded_message_string) { '["new_message",{"id":"1234","channel":"awesome_channel","data":"this is a message","success":null,"result":null,"server_token":"1234"}]' }
|
10
|
+
let(:synchronizable_encoded_message) { '["new_message",{"id":"1234","data":{"message":"this is a message"},"server_token":"1234"}]' }
|
10
11
|
let(:connection) { double('connection') }
|
11
12
|
|
12
13
|
before { connection.stub!(:id).and_return(1) }
|
@@ -120,6 +121,15 @@ module WebsocketRails
|
|
120
121
|
event.serialize.should == channel_encoded_message_string
|
121
122
|
end
|
122
123
|
end
|
124
|
+
|
125
|
+
context "messages for synchronization" do
|
126
|
+
it "should include the unique server token" do
|
127
|
+
event = Event.new_from_json synchronizable_encoded_message, connection
|
128
|
+
raw_data = event.serialize
|
129
|
+
data = JSON.parse raw_data
|
130
|
+
data[1]['server_token'].should == '1234'
|
131
|
+
end
|
132
|
+
end
|
123
133
|
end
|
124
134
|
|
125
135
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module WebsocketRails
|
4
|
+
class ClassWithLogging
|
5
|
+
include Logging
|
6
|
+
end
|
7
|
+
|
8
|
+
describe ClassWithLogging do
|
9
|
+
|
10
|
+
describe "#log" do
|
11
|
+
context "when log_level = :warn" do
|
12
|
+
before do
|
13
|
+
WebsocketRails.setup do |config|
|
14
|
+
config.log_level = :warn
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should not print to the console" do
|
19
|
+
subject.should_not_receive(:puts).with("test message")
|
20
|
+
subject.log "test message"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context "log_level = :debug" do
|
25
|
+
before do
|
26
|
+
WebsocketRails.setup do |config|
|
27
|
+
config.log_level = :debug
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should print to the console if log_level is :debug" do
|
32
|
+
subject.should_receive(:puts).with("test message")
|
33
|
+
subject.log "test message"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "eventmachine"
|
3
|
+
|
4
|
+
module WebsocketRails
|
5
|
+
describe Synchronization do
|
6
|
+
|
7
|
+
around(:each) do |example|
|
8
|
+
EM.run do
|
9
|
+
Fiber.new do
|
10
|
+
@redis = Redis.new
|
11
|
+
@redis.del "websocket_rails.active_servers"
|
12
|
+
example.run
|
13
|
+
end.resume
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
after(:each) do
|
18
|
+
EM.stop
|
19
|
+
end
|
20
|
+
|
21
|
+
let(:subject) { Synchronization }
|
22
|
+
|
23
|
+
describe "#publish" do
|
24
|
+
it "should add the serialized event to the websocket_rails.events channel" do
|
25
|
+
event = Event.new(:test_event, :channel => 'synchrony', :data => 'hello channel')
|
26
|
+
Redis.any_instance.should_receive(:publish).with("websocket_rails.events", event.serialize)
|
27
|
+
|
28
|
+
subject.publish(event)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "#generate_unique_token" do
|
33
|
+
before do
|
34
|
+
SecureRandom.stub(:urlsafe_base64).and_return(1, 2, 3)
|
35
|
+
end
|
36
|
+
|
37
|
+
after do
|
38
|
+
@redis.del "websocket_rails.active_servers"
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should generate a unique token" do
|
42
|
+
SecureRandom.should_receive(:urlsafe_base64).at_least(1).times
|
43
|
+
subject.generate_unique_token
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should generate another id if the current id is already registered" do
|
47
|
+
@redis.sadd "websocket_rails.active_servers", 1
|
48
|
+
token = subject.generate_unique_token
|
49
|
+
token.should == 2
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "#register_server" do
|
54
|
+
it "should add the unique token to the active_servers key in redis" do
|
55
|
+
Redis.any_instance.should_receive(:sadd).with("websocket_rails.active_servers", "token")
|
56
|
+
subject.register_server "token"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "#remove_server" do
|
61
|
+
it "should add the unique token to the active_servers key in redis" do
|
62
|
+
Redis.any_instance.should_receive(:srem).with("websocket_rails.active_servers", "token")
|
63
|
+
subject.remove_server "token"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|