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.
Files changed (37) hide show
  1. data/CHANGELOG.md +11 -0
  2. data/Gemfile +7 -0
  3. data/README.md +15 -16
  4. data/lib/assets/javascripts/websocket_rails/channel.js +35 -0
  5. data/lib/assets/javascripts/websocket_rails/http_connection.js +68 -0
  6. data/lib/assets/javascripts/websocket_rails/main.js +4 -0
  7. data/lib/assets/javascripts/websocket_rails/websocket_connection.js +38 -0
  8. data/lib/assets/javascripts/websocket_rails/websocket_rails.js +100 -0
  9. data/lib/generators/websocket_rails/install/install_generator.rb +32 -0
  10. data/lib/generators/websocket_rails/install/templates/events.rb +14 -0
  11. data/lib/websocket-rails.rb +4 -0
  12. data/lib/websocket_rails/base_controller.rb +4 -2
  13. data/lib/websocket_rails/channel.rb +35 -0
  14. data/lib/websocket_rails/channel_manager.rb +28 -0
  15. data/lib/websocket_rails/connection_adapters.rb +35 -4
  16. data/lib/websocket_rails/connection_manager.rb +3 -1
  17. data/lib/websocket_rails/dispatcher.rb +22 -12
  18. data/lib/websocket_rails/event.rb +31 -17
  19. data/lib/websocket_rails/event_map.rb +14 -6
  20. data/lib/websocket_rails/event_queue.rb +27 -0
  21. data/lib/websocket_rails/internal_events.rb +19 -0
  22. data/lib/websocket_rails/version.rb +1 -1
  23. data/spec/dummy/config/initializers/events.rb +5 -5
  24. data/spec/dummy/log/test.log +180 -0
  25. data/spec/integration/connection_manager_spec.rb +21 -8
  26. data/spec/support/helper_methods.rb +2 -2
  27. data/spec/support/mock_web_socket.rb +4 -0
  28. data/spec/unit/channel_manager_spec.rb +29 -0
  29. data/spec/unit/channel_spec.rb +46 -0
  30. data/spec/unit/connection_adapters_spec.rb +20 -0
  31. data/spec/unit/dispatcher_spec.rb +13 -2
  32. data/spec/unit/event_map_spec.rb +2 -2
  33. data/spec/unit/event_queue_spec.rb +36 -0
  34. data/spec/unit/event_spec.rb +26 -3
  35. metadata +24 -28
  36. data/assets/javascripts/http_dispatcher.js +0 -78
  37. 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
@@ -9,3 +9,10 @@ gem "simplecov"
9
9
  gem "ruby_gntp"
10
10
  gem "guard"
11
11
  gem "guard-rspec"
12
+
13
+ platforms :jruby do
14
+ gem 'activerecord-jdbcsqlite3-adapter', :require => 'jdbc-sqlite3', :require => 'arjdbc'
15
+ end
16
+ platforms :ruby do
17
+ gem 'sqlite3'
18
+ end
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/Events)
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 the the action
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 Dispatcher
75
+ ## WebSocketRails JavaScript Client
76
76
 
77
- There are two example dispatchers located in the [assets/javascripts](https://github.com/DanKnox/websocket-rails/tree/master/assets/javascripts) directory. One connects to the server using
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 dispatcher and connecting to the server:
85
- var dispatcher = new ServerEventsDispatcher()
86
- dispatcher.onopen(function() {
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
- alert(data.user_name)
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,4 @@
1
+ //= require ./websocket_rails
2
+ //= require ./http_connection
3
+ //= require ./websocket_connection
4
+ //= require ./channel
@@ -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
@@ -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
- event = Event.new( event_name, message, connection, options )
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
- event = Event.new( event_name, message, connection, options )
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