websocket-rails 0.1.3 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +11 -0
- data/Gemfile +7 -0
- data/README.md +15 -16
- data/lib/assets/javascripts/websocket_rails/channel.js +35 -0
- data/lib/assets/javascripts/websocket_rails/http_connection.js +68 -0
- data/lib/assets/javascripts/websocket_rails/main.js +4 -0
- data/lib/assets/javascripts/websocket_rails/websocket_connection.js +38 -0
- data/lib/assets/javascripts/websocket_rails/websocket_rails.js +100 -0
- data/lib/generators/websocket_rails/install/install_generator.rb +32 -0
- data/lib/generators/websocket_rails/install/templates/events.rb +14 -0
- data/lib/websocket-rails.rb +4 -0
- data/lib/websocket_rails/base_controller.rb +4 -2
- data/lib/websocket_rails/channel.rb +35 -0
- data/lib/websocket_rails/channel_manager.rb +28 -0
- data/lib/websocket_rails/connection_adapters.rb +35 -4
- data/lib/websocket_rails/connection_manager.rb +3 -1
- data/lib/websocket_rails/dispatcher.rb +22 -12
- data/lib/websocket_rails/event.rb +31 -17
- data/lib/websocket_rails/event_map.rb +14 -6
- data/lib/websocket_rails/event_queue.rb +27 -0
- data/lib/websocket_rails/internal_events.rb +19 -0
- data/lib/websocket_rails/version.rb +1 -1
- data/spec/dummy/config/initializers/events.rb +5 -5
- data/spec/dummy/log/test.log +180 -0
- data/spec/integration/connection_manager_spec.rb +21 -8
- data/spec/support/helper_methods.rb +2 -2
- data/spec/support/mock_web_socket.rb +4 -0
- data/spec/unit/channel_manager_spec.rb +29 -0
- data/spec/unit/channel_spec.rb +46 -0
- data/spec/unit/connection_adapters_spec.rb +20 -0
- data/spec/unit/dispatcher_spec.rb +13 -2
- data/spec/unit/event_map_spec.rb +2 -2
- data/spec/unit/event_queue_spec.rb +36 -0
- data/spec/unit/event_spec.rb +26 -3
- metadata +24 -28
- data/assets/javascripts/http_dispatcher.js +0 -78
- data/assets/javascripts/websocket_dispatcher.js +0 -66
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,16 @@
|
|
1
1
|
# WebsocketRails Change Log
|
2
2
|
|
3
|
+
## Version 0.1.4
|
4
|
+
|
5
|
+
June 30 2012
|
6
|
+
|
7
|
+
* Added channel support
|
8
|
+
* Fix the JavaScript client to work on the iPad - Thanks to @adamkittelson
|
9
|
+
* Add an event queue on the connection object to allow for queueing up
|
10
|
+
multiple events before flushing to the client.
|
11
|
+
* Add generator for creating the events.rb intializer and requiring the
|
12
|
+
client in the application.js sprockets manifest file.
|
13
|
+
|
3
14
|
## Version 0.1.3
|
4
15
|
|
5
16
|
June 22 2012
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -21,8 +21,8 @@ We are finally very close to the first production release. Any comments or sugge
|
|
21
21
|
Check out the [Example Application](https://github.com/DanKnox/websocket-rails-Example-Project) for additional information.
|
22
22
|
|
23
23
|
1. Add the gem to your Gemfile
|
24
|
-
3. Create a WebsocketRails controller - [See Documentation](http://rdoc.info/github/DanKnox/websocket-rails/master/WebsocketRails/BaseController)
|
25
|
-
4. Create an `events.rb` initializer file to map events to your controller - [See Documentation](http://rdoc.info/github/DanKnox/websocket-rails/master/WebsocketRails/
|
24
|
+
3. Create a WebsocketRails controller - [See Documentation](http://rdoc.info/github/DanKnox/websocket-rails/master/frames/WebsocketRails/BaseController)
|
25
|
+
4. Create an `events.rb` initializer file to map events to your controller - [See Documentation](http://rdoc.info/github/DanKnox/websocket-rails/master/frames/WebsocketRails/EventMap)
|
26
26
|
5. Launch the web server and connect a WebSocket client to `ws://yourserver:port/websocket`
|
27
27
|
|
28
28
|
*Important Note About Web Servers*
|
@@ -42,7 +42,7 @@ There are two built in events that are fired automatically by the dispatcher. Th
|
|
42
42
|
You can subscribe multiple controllers and actions to the same event to provide very clean event handling logic. The new message will be available in each controller using the `message` method discussed in the *Controllers* section below. The example event router below demonstrates subscribing to the `:new_message` event with one controller action to rebroadcast the message out to all connected clients and another controller action to log the message to a database.
|
43
43
|
|
44
44
|
The Event Map now supports namespacing events. Events triggered on the
|
45
|
-
client as `namespace.event_name` will now be dispatched
|
45
|
+
client as `namespace.event_name` will now be dispatched to the action
|
46
46
|
subscribed to the `event_name` event under the `namespace` namespace.
|
47
47
|
See the [EventMap
|
48
48
|
Documentation](http://rdoc.info/github/DanKnox/websocket-rails/master/frames/WebsocketRails/EventMap) for more details.
|
@@ -72,28 +72,27 @@ end
|
|
72
72
|
|
73
73
|
The `subscribe` method takes the event name as the first argument, then a hash where `:to` is the Controller class and `:with_method` is the action to execute.
|
74
74
|
|
75
|
-
## JavaScript
|
75
|
+
## WebSocketRails JavaScript Client
|
76
76
|
|
77
|
-
There
|
78
|
-
WebSockets and the other connects using streaming HTTP. These will eventually be merged into one dispatcher that selects the best transport at runtime based on what's available in the
|
79
|
-
browser. A pull request providing this functionality will definitely be accepted.
|
80
|
-
|
81
|
-
The current dispatchers are limited in functionality and meant mostly as a reference implementation. The two dispatchers are functionally equivalent and can be swapped out at will.
|
77
|
+
There is an accompanying JavaScript client [located here](https://github.com/DanKnox/websocket-rails/tree/master/lib/assets/javascripts/websocket_rails). The client detects support for WebSockets in the browser and falls back to HTTP streaming if it is unavailable. The client currently works in every major browser except for internet explorer. If you are using the current master branch of this repository, you can require the client in your application.js manifest directly: `//= require websocket_rails/main`. The client will be released to rubygems soon.
|
82
78
|
|
83
79
|
````javascript
|
84
|
-
//Setting up the
|
85
|
-
|
86
|
-
|
80
|
+
// Setting up the client and connecting to the server:
|
81
|
+
// Do not pass the prefix of the URL, 'ws://' will be
|
82
|
+
// added automatically when the client uses WebSockets
|
83
|
+
var dispatcher = new WebSocketRails("localhost:port/websocket");
|
84
|
+
|
85
|
+
dispatcher.on_open = function() {
|
87
86
|
// trigger a server event immediately after opening connection
|
88
|
-
dispatcher.trigger('new_user',{user_name: 'guest'})
|
89
|
-
}
|
87
|
+
dispatcher.trigger('new_user',{user_name: 'guest'});
|
88
|
+
}
|
90
89
|
|
91
90
|
//Triggering a new event on the server
|
92
|
-
dispatcher.trigger('event_name',object_to_be_serialized_to_json)
|
91
|
+
dispatcher.trigger('event_name',object_to_be_serialized_to_json);
|
93
92
|
|
94
93
|
//Listening for new events from the server
|
95
94
|
dispatcher.bind('event_name', function(data) {
|
96
|
-
|
95
|
+
console.log(data.user_name);
|
97
96
|
})
|
98
97
|
````
|
99
98
|
|
@@ -0,0 +1,35 @@
|
|
1
|
+
/*
|
2
|
+
* The channel object is returned when you subscribe to a channel.
|
3
|
+
*
|
4
|
+
* For instance:
|
5
|
+
* var dispatcher = new WebSocketRails('localhost:3000/websocket');
|
6
|
+
* var awesome_channel = dispatcher.subscribe('awesome_channel');
|
7
|
+
* awesome_channel.bind('event', function() { console.log('channel event!'); });
|
8
|
+
* awesome_channel.trigger('awesome_event', awesome_object);
|
9
|
+
*/
|
10
|
+
|
11
|
+
WebSocketRails.Channel = function(name,dispatcher) {
|
12
|
+
var that = this;
|
13
|
+
that.name = name;
|
14
|
+
|
15
|
+
dispatcher.trigger('websocket_rails.subscribe',{channel: name})
|
16
|
+
|
17
|
+
var callbacks = {};
|
18
|
+
|
19
|
+
that.bind = function(event_name, callback) {
|
20
|
+
callbacks[event_name] = callbacks[event_name] || [];
|
21
|
+
callbacks[event_name].push(callback);
|
22
|
+
}
|
23
|
+
|
24
|
+
that.trigger = function(event_name, message) {
|
25
|
+
dispatcher.trigger_channel(that.name,event_name,message);
|
26
|
+
}
|
27
|
+
|
28
|
+
that.dispatch = function(event_name, message) {
|
29
|
+
var chain = callbacks[event_name];
|
30
|
+
if (typeof chain == 'undefined') return;
|
31
|
+
for(var i = 0; i < chain.length; i++) {
|
32
|
+
chain[i]( message );
|
33
|
+
}
|
34
|
+
}
|
35
|
+
}
|
@@ -0,0 +1,68 @@
|
|
1
|
+
/*
|
2
|
+
* HTTP Interface for the WebSocketRails client.
|
3
|
+
*/
|
4
|
+
WebSocketRails.HttpConnection = function(url,dispatcher){
|
5
|
+
var that = this,
|
6
|
+
conn = WebSocketRails.createXMLHttpObject(),
|
7
|
+
lastPos = 0;
|
8
|
+
|
9
|
+
conn.onreadystatechange = function() {
|
10
|
+
if (conn.readyState == 3) {
|
11
|
+
var data = conn.responseText.substring(lastPos);
|
12
|
+
lastPos = conn.responseText.length;
|
13
|
+
|
14
|
+
console.log('raw data: '+data);
|
15
|
+
var json_data = JSON.parse(data);
|
16
|
+
|
17
|
+
console.log(json_data);
|
18
|
+
that.new_message(json_data);
|
19
|
+
}
|
20
|
+
}
|
21
|
+
conn.open("GET","/websocket",true);
|
22
|
+
conn.send();
|
23
|
+
|
24
|
+
|
25
|
+
that.trigger = function(event_name, data, client_id) {
|
26
|
+
var payload = JSON.stringify([event_name,data]);
|
27
|
+
$.ajax({
|
28
|
+
type: 'POST',
|
29
|
+
url: '/websocket',
|
30
|
+
data: {client_id: client_id, data: payload},
|
31
|
+
success: function(){console.log('success');}
|
32
|
+
});
|
33
|
+
return this;
|
34
|
+
}
|
35
|
+
|
36
|
+
that.trigger_channel = function(channel, event_name, data, client_id) {
|
37
|
+
var payload = JSON.stringify([channel,event_name,data]);
|
38
|
+
$.ajax({
|
39
|
+
type: 'POST',
|
40
|
+
url: '/websocket',
|
41
|
+
data: {client_id: client_id, data: payload},
|
42
|
+
success: function(){console.log('success');}
|
43
|
+
});
|
44
|
+
return this;
|
45
|
+
}
|
46
|
+
}
|
47
|
+
|
48
|
+
WebSocketRails.XMLHttpFactories = [
|
49
|
+
function () {return new XMLHttpRequest()},
|
50
|
+
function () {return new ActiveXObject("Msxml2.XMLHTTP")},
|
51
|
+
function () {return new ActiveXObject("Msxml3.XMLHTTP")},
|
52
|
+
function () {return new ActiveXObject("Microsoft.XMLHTTP")}
|
53
|
+
];
|
54
|
+
|
55
|
+
WebSocketRails.createXMLHttpObject = function() {
|
56
|
+
var xmlhttp = false,
|
57
|
+
factories = WebSocketRails.XMLHttpFactories;
|
58
|
+
for (var i=0;i<factories.length;i++) {
|
59
|
+
try {
|
60
|
+
xmlhttp = factories[i]();
|
61
|
+
}
|
62
|
+
catch (e) {
|
63
|
+
continue;
|
64
|
+
}
|
65
|
+
break;
|
66
|
+
}
|
67
|
+
return xmlhttp;
|
68
|
+
}
|
@@ -0,0 +1,38 @@
|
|
1
|
+
/*
|
2
|
+
* WebSocket Interface for the WebSocketRails client.
|
3
|
+
*/
|
4
|
+
WebSocketRails.WebSocketConnection = function(url,dispatcher){
|
5
|
+
var that = this,
|
6
|
+
conn = new WebSocket("ws://"+url);
|
7
|
+
|
8
|
+
that.trigger = function(event_name, data, client_id) {
|
9
|
+
var payload = JSON.stringify([event_name,data])
|
10
|
+
conn.send( payload )
|
11
|
+
return this;
|
12
|
+
}
|
13
|
+
|
14
|
+
that.trigger_channel = function(channel, event_name, data, client_id) {
|
15
|
+
var payload = JSON.stringify([channel,event_name,data])
|
16
|
+
conn.send( payload )
|
17
|
+
return this;
|
18
|
+
}
|
19
|
+
|
20
|
+
conn.onmessage = function(evt) {
|
21
|
+
var data = JSON.parse(evt.data);
|
22
|
+
|
23
|
+
console.log(data)
|
24
|
+
that.new_message(data);
|
25
|
+
}
|
26
|
+
|
27
|
+
conn.onclose = function(evt) {
|
28
|
+
dispatcher.dispatch('connection_closed', '')
|
29
|
+
}
|
30
|
+
|
31
|
+
var dispatch = function(event_name, message) {
|
32
|
+
var chain = callbacks[event_name]
|
33
|
+
if (typeof chain == 'undefined') return;
|
34
|
+
for(var i = 0; i < chain.length; i++) {
|
35
|
+
chain[i]( message )
|
36
|
+
}
|
37
|
+
}
|
38
|
+
}
|
@@ -0,0 +1,100 @@
|
|
1
|
+
/*
|
2
|
+
* WebsocketRails JavaScript Client
|
3
|
+
*
|
4
|
+
* Setting up the dispatcher:
|
5
|
+
* var dispatcher = new WebSocketRails('localhost:3000');
|
6
|
+
* dispatcher.on_open = function() {
|
7
|
+
* // trigger a server event immediately after opening connection
|
8
|
+
* dispatcher.trigger('new_user',{user_name: 'guest'});
|
9
|
+
* })
|
10
|
+
*
|
11
|
+
* Triggering a new event on the server
|
12
|
+
* dispatcher.trigger('event_name',object_to_be_serialized_to_json);
|
13
|
+
*
|
14
|
+
* Listening for new events from the server
|
15
|
+
* dispatcher.bind('event_name', function(data) {
|
16
|
+
* console.log(data.user_name);
|
17
|
+
* })
|
18
|
+
*/
|
19
|
+
var WebSocketRails = function(url) {
|
20
|
+
var that = this,
|
21
|
+
client_id = 0;
|
22
|
+
|
23
|
+
that.state = 'connecting';
|
24
|
+
|
25
|
+
if( typeof(WebSocket) != "function" && typeof(WebSocket) != "object" ) {
|
26
|
+
var conn = new WebSocketRails.HttpConnection(url);
|
27
|
+
} else {
|
28
|
+
var conn = new WebSocketRails.WebSocketConnection(url,that);
|
29
|
+
}
|
30
|
+
|
31
|
+
var on_open = function(data) {
|
32
|
+
that.state = 'connected';
|
33
|
+
that.connection_id = data.connection_id;
|
34
|
+
|
35
|
+
if (typeof that.on_open !== 'undefined') {
|
36
|
+
that.on_open(data);
|
37
|
+
}
|
38
|
+
}
|
39
|
+
|
40
|
+
conn.new_message = function(data) {
|
41
|
+
for(i = 0; i < data.length; i++) {
|
42
|
+
socket_message = data[i];
|
43
|
+
var is_channel = false;
|
44
|
+
|
45
|
+
if (data.length > 2) {
|
46
|
+
var channel_name = socket_message[0],
|
47
|
+
event_name = socket_message[1],
|
48
|
+
message = socket_message[2];
|
49
|
+
is_channel = true;
|
50
|
+
} else {
|
51
|
+
var event_name = socket_message[0],
|
52
|
+
message = socket_message[1];
|
53
|
+
}
|
54
|
+
if (that.state === 'connecting' && event_name === 'client_connected') {
|
55
|
+
on_open(message);
|
56
|
+
}
|
57
|
+
if (is_channel == true) {
|
58
|
+
that.dispatch_channel(channel_name, event_name, message);
|
59
|
+
} else {
|
60
|
+
that.dispatch(event_name, message);
|
61
|
+
}
|
62
|
+
}
|
63
|
+
}
|
64
|
+
|
65
|
+
var callbacks = {};
|
66
|
+
|
67
|
+
that.bind = function(event_name, callback) {
|
68
|
+
callbacks[event_name] = callbacks[event_name] || [];
|
69
|
+
callbacks[event_name].push(callback);
|
70
|
+
}
|
71
|
+
|
72
|
+
that.trigger = function(event_name, data) {
|
73
|
+
conn.trigger(event_name,data,that.connection_id);
|
74
|
+
}
|
75
|
+
|
76
|
+
that.trigger_channel = function(channel, event_name, data) {
|
77
|
+
conn.trigger_channel(channel,event_name,data,that.connection_id);
|
78
|
+
}
|
79
|
+
|
80
|
+
var channels = {};
|
81
|
+
that.subscribe = function(channel_name) {
|
82
|
+
var channel = new WebSocketRails.Channel(channel_name,this);
|
83
|
+
channels[channel_name] = channel;
|
84
|
+
return channel;
|
85
|
+
}
|
86
|
+
|
87
|
+
that.dispatch = function(event_name, message) {
|
88
|
+
var chain = callbacks[event_name];
|
89
|
+
if (typeof chain == 'undefined') return;
|
90
|
+
for(var i = 0; i < chain.length; i++) {
|
91
|
+
chain[i]( message );
|
92
|
+
}
|
93
|
+
}
|
94
|
+
|
95
|
+
that.dispatch_channel = function(channel, event_name, message) {
|
96
|
+
var channel = that.channels[channel];
|
97
|
+
if (typeof channel == 'undefined') return;
|
98
|
+
channel.dispatch(event_name, message);
|
99
|
+
}
|
100
|
+
}
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'rails'
|
2
|
+
|
3
|
+
module WebsocketRails
|
4
|
+
module Generators
|
5
|
+
class InstallGenerator < Rails::Generators::Base
|
6
|
+
source_root File.expand_path("../templates", __FILE__)
|
7
|
+
|
8
|
+
desc "Create the events.rb initializer and require the JS client in the application.js manifest."
|
9
|
+
|
10
|
+
class_option :manifest, :type => :string, :aliases => "-m", :default => 'application.js',
|
11
|
+
:desc => "Javascript manifest file to modify (or create)"
|
12
|
+
|
13
|
+
def create_events_initializer_file
|
14
|
+
template 'events.rb', File.join('config', 'initializers', 'events.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
def inject_websocket_rails_client
|
18
|
+
manifest = options[:manifest]
|
19
|
+
js_path = "app/assets/javascripts"
|
20
|
+
|
21
|
+
create_file("#{js_path}/#{manifest}") unless File.exists?("#{js_path}/#{manifest}")
|
22
|
+
|
23
|
+
append_to_file "#{js_path}/#{manifest}" do
|
24
|
+
out = ""
|
25
|
+
out << "//= require websocket_rails/main"
|
26
|
+
out << "\n"
|
27
|
+
out << "\n"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
WebsocketRails::EventMap.describe do
|
2
|
+
# You can use this file to map incoming events to controller actions.
|
3
|
+
# One event can be mapped to any number of controller actions. The
|
4
|
+
# actions will be executed in the order they were subscribed.
|
5
|
+
#
|
6
|
+
# Uncomment and edit the next line to handle the client connected event:
|
7
|
+
# subscribe :client_connected, :to => Controller, :with_method => :method_name
|
8
|
+
#
|
9
|
+
# Here is an example of mapping namespaced events:
|
10
|
+
# namespace :product do
|
11
|
+
# subscribe :new, :to => ProductController, :with_method => :new_product
|
12
|
+
# end
|
13
|
+
# The above will handle an event triggered on the client like `product.new`.
|
14
|
+
end
|
data/lib/websocket-rails.rb
CHANGED
@@ -22,7 +22,11 @@ require 'websocket_rails/connection_manager'
|
|
22
22
|
require 'websocket_rails/dispatcher'
|
23
23
|
require 'websocket_rails/event'
|
24
24
|
require 'websocket_rails/event_map'
|
25
|
+
require 'websocket_rails/event_queue'
|
26
|
+
require 'websocket_rails/channel'
|
27
|
+
require 'websocket_rails/channel_manager'
|
25
28
|
require 'websocket_rails/base_controller'
|
29
|
+
require 'websocket_rails/internal_events'
|
26
30
|
|
27
31
|
require 'websocket_rails/connection_adapters'
|
28
32
|
require 'websocket_rails/connection_adapters/http'
|
@@ -91,13 +91,15 @@ module WebsocketRails
|
|
91
91
|
#
|
92
92
|
# See the {EventMap} documentation for more on mapping namespaced actions.
|
93
93
|
def send_message(event_name, message, options={})
|
94
|
-
|
94
|
+
options.merge! :connection => connection
|
95
|
+
event = Event.new( event_name, message, options )
|
95
96
|
@_dispatcher.send_message event if @_dispatcher.respond_to?(:send_message)
|
96
97
|
end
|
97
98
|
|
98
99
|
# Broadcasts a message to all connected clients. See {#send_message} for message passing details.
|
99
100
|
def broadcast_message(event_name, message, options={})
|
100
|
-
|
101
|
+
options.merge! :connection => connection
|
102
|
+
event = Event.new( event_name, message, options )
|
101
103
|
@_dispatcher.broadcast_message event if @_dispatcher.respond_to?(:broadcast_message)
|
102
104
|
end
|
103
105
|
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module WebsocketRails
|
2
|
+
class Channel
|
3
|
+
|
4
|
+
attr_reader :name, :subscribers
|
5
|
+
|
6
|
+
def initialize(channel_name)
|
7
|
+
@subscribers = []
|
8
|
+
@name = channel_name
|
9
|
+
end
|
10
|
+
|
11
|
+
def subscribe(connection)
|
12
|
+
@subscribers << connection
|
13
|
+
end
|
14
|
+
|
15
|
+
def trigger(event_name,data,options={})
|
16
|
+
options.merge! :channel => name
|
17
|
+
event = Event.new event_name, data, options
|
18
|
+
send_data event
|
19
|
+
end
|
20
|
+
|
21
|
+
def trigger_event(event)
|
22
|
+
send_data event
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def send_data(event)
|
28
|
+
puts "sending channel event: #{event.serialize}"
|
29
|
+
subscribers.each do |subscriber|
|
30
|
+
subscriber.trigger event
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|