websocket-rails 0.4.9 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,23 @@
1
1
  # WebsocketRails Change Log
2
2
 
3
+ ## Version 0.5.0
4
+
5
+ September 2 2013
6
+
7
+ * Use window.location.protocol to choose between ws:// and wss://
8
+ shcheme. - Thanks to @depili
9
+ * Override ConnectionManager#inspect to clean up the output from `rake
10
+ routes`
11
+ * Add a basic Global UserManager for triggering events on specific users
12
+ from anywhere inside your app without creating a dedicated user channel.
13
+ * Deprecate the old controller observer system and implement full Rails
14
+ AbstractController::Callbacks support. - Thanks to @pitr
15
+ * Reload the events.rb event route file each time an event is fired. -
16
+ Thanks to @moaa
17
+ * Separate the event route file and WebsocketRails configuration files.
18
+ The events.rb now lives in `config/events.rb`. The configuration should
19
+ remain in an initializer located at `config/initializers/websocket_rails.rb`. - Thanks to @moaa
20
+
3
21
  ## Version 0.4.9
4
22
 
5
23
  July 9 2013
data/README.md CHANGED
@@ -6,7 +6,7 @@ If you haven't done so yet, check out the [Project Page](http://danknox.github.c
6
6
 
7
7
  **Find us on IRC #websocket-rails**
8
8
 
9
- Stop by #websocket-rails if you would like to chat or have any
9
+ Stop by #websocket-rails on freenode if you would like to chat or have any
10
10
  questions.
11
11
 
12
12
  ## Recent Updates
@@ -4,7 +4,13 @@ WebSocket Interface for the WebSocketRails client.
4
4
  class WebSocketRails.WebSocketConnection
5
5
 
6
6
  constructor: (@url,@dispatcher) ->
7
- @url = "ws://#{@url}" unless @url.match(/^wss?:\/\//)
7
+ if @url.match(/^wss?:\/\//)
8
+ console.log "WARNING: Using connection urls with protocol specified is depricated"
9
+ else if window.location.protocol == 'http:'
10
+ @url = "ws://#{@url}"
11
+ else
12
+ @url = "wss://#{@url}"
13
+
8
14
  @message_queue = []
9
15
  @_conn = new WebSocket(@url)
10
16
  @_conn.onmessage = @on_message
@@ -11,7 +11,8 @@ module WebsocketRails
11
11
  :desc => "Javascript manifest file to modify (or create)"
12
12
 
13
13
  def create_events_initializer_file
14
- template 'events.rb', File.join('config', 'initializers', 'events.rb')
14
+ template 'events.rb', File.join('config', 'events.rb')
15
+ template 'websocket_rails.rb', File.join('config', 'initializers', 'websocket_rails.rb')
15
16
  end
16
17
 
17
18
  def inject_websocket_rails_client
@@ -1,39 +1,3 @@
1
- WebsocketRails.setup do |config|
2
-
3
- # Uncomment to override the default log level. The log level can be
4
- # any of the standard Logger log levels. By default it will mirror the
5
- # current Rails environment log level.
6
- # config.log_level = :debug
7
-
8
- # Uncomment to change the default log file path.
9
- # config.log_path = "#{Rails.root}/log/websocket_rails.log"
10
-
11
- # Set to true if you wish to log the internal websocket_rails events
12
- # such as the keepalive `websocket_rails.ping` event.
13
- # config.log_internal_events = false
14
-
15
- # Change to true to enable standalone server mode
16
- # Start the standalone server with rake websocket_rails:start_server
17
- # * Requires Redis
18
- config.standalone = false
19
-
20
- # Change to true to enable channel synchronization between
21
- # multiple server instances.
22
- # * Requires Redis.
23
- config.synchronize = false
24
-
25
- # Uncomment and edit to point to a different redis instance.
26
- # Will not be used unless standalone or synchronization mode
27
- # is enabled.
28
- # config.redis_options = {:host => 'localhost', :port => '6379'}
29
-
30
- # By default, all subscribers in to a channel will be removed
31
- # when that channel is made private. If you don't wish active
32
- # subscribers to be removed from a previously public channel
33
- # when making it private, set the following to true.
34
- # config.keep_subscribers_when_private = false
35
- end
36
-
37
1
  WebsocketRails::EventMap.describe do
38
2
  # You can use this file to map incoming events to controller actions.
39
3
  # One event can be mapped to any number of controller actions. The
@@ -0,0 +1,41 @@
1
+ WebsocketRails.setup do |config|
2
+
3
+ # Uncomment to override the default log level. The log level can be
4
+ # any of the standard Logger log levels. By default it will mirror the
5
+ # current Rails environment log level.
6
+ # config.log_level = :debug
7
+
8
+ # Uncomment to change the default log file path.
9
+ # config.log_path = "#{Rails.root}/log/websocket_rails.log"
10
+
11
+ # Set to true if you wish to log the internal websocket_rails events
12
+ # such as the keepalive `websocket_rails.ping` event.
13
+ # config.log_internal_events = false
14
+
15
+ # Change to true to enable standalone server mode
16
+ # Start the standalone server with rake websocket_rails:start_server
17
+ # * Requires Redis
18
+ config.standalone = false
19
+
20
+ # Change to true to enable channel synchronization between
21
+ # multiple server instances.
22
+ # * Requires Redis.
23
+ config.synchronize = false
24
+
25
+ # Uncomment and edit to point to a different redis instance.
26
+ # Will not be used unless standalone or synchronization mode
27
+ # is enabled.
28
+ # config.redis_options = {:host => 'localhost', :port => '6379'}
29
+
30
+ # By default, all subscribers in to a channel will be removed
31
+ # when that channel is made private. If you don't wish active
32
+ # subscribers to be removed from a previously public channel
33
+ # when making it private, set the following to true.
34
+ # config.keep_subscribers_when_private = false
35
+
36
+ # Used as the key for the WebsocketRails.users Hash. This method
37
+ # will be called on the `current_user` object in your controller
38
+ # if one exists. If `current_user` does not exist or does not
39
+ # respond to the identifier, the key will default to `connection.id`
40
+ # config.user_identifier = :id
41
+ end
@@ -2,7 +2,8 @@ namespace :websocket_rails do
2
2
  desc 'Start the WebsocketRails standalone server.'
3
3
  task :start_server do
4
4
  require "thin"
5
- load "#{Rails.root}/config/initializers/events.rb"
5
+ load "#{Rails.root}/config/initializers/websocket_rails.rb"
6
+ load "#{Rails.root}/config/events.rb"
6
7
 
7
8
  options = WebsocketRails.config.thin_options
8
9
 
@@ -18,7 +19,8 @@ namespace :websocket_rails do
18
19
  desc 'Stop the WebsocketRails standalone server.'
19
20
  task :stop_server do
20
21
  require "thin"
21
- load "#{Rails.root}/config/initializers/events.rb"
22
+ load "#{Rails.root}/config/initializers/websocket_rails.rb"
23
+ load "#{Rails.root}/config/events.rb"
22
24
 
23
25
  options = WebsocketRails.config.thin_options
24
26
 
@@ -31,6 +33,6 @@ end
31
33
  def warn_if_standalone_not_enabled!
32
34
  return if WebsocketRails.standalone?
33
35
  puts "Fail!"
34
- puts "You must enable standalone mode in your events.rb initializer to use the standalone server."
36
+ puts "You must enable standalone mode in your websocket_rails.rb initializer to use the standalone server."
35
37
  exit 1
36
38
  end
@@ -41,6 +41,7 @@ require 'websocket_rails/event_map'
41
41
  require 'websocket_rails/event_queue'
42
42
  require 'websocket_rails/channel'
43
43
  require 'websocket_rails/channel_manager'
44
+ require 'websocket_rails/user_manager'
44
45
  require 'websocket_rails/base_controller'
45
46
  require 'websocket_rails/internal_events'
46
47
 
@@ -48,6 +49,7 @@ require 'websocket_rails/connection_adapters'
48
49
  require 'websocket_rails/connection_adapters/http'
49
50
  require 'websocket_rails/connection_adapters/web_socket'
50
51
 
52
+ load "#{Rails.root}/config/events.rb" if File.exists?("#{Rails.root}/config/events.rb")
51
53
 
52
54
  # Exceptions
53
55
  class WebsocketRails::InvalidConnectionError < StandardError
@@ -83,6 +85,18 @@ class WebsocketRails::EventRoutingError < StandardError
83
85
 
84
86
  end
85
87
 
88
+ class WebsocketRails::ConfigDeprecationError < StandardError
89
+ def to_s
90
+ out = "Deprecation Error:\n\n\t"
91
+ out << "config/initializers/events.rb has been moved to config/events.rb\n\t"
92
+ out << "Make sure events.rb is in the proper location and the old one has been removed.\n\t"
93
+ out << "More information can be found in the wiki.\n\n"
94
+ end
95
+ end
96
+
97
+ raise WebsocketRails::ConfigDeprecationError if File.exists?("config/initializers/events.rb")
98
+
99
+
86
100
  # Deprecation Notices
87
101
  class WebsocketRails::Dispatcher
88
102
  def self.describe_events(&block)
@@ -1,4 +1,5 @@
1
1
  require "websocket_rails/data_store"
2
+ require 'abstract_controller/callbacks'
2
3
 
3
4
  module WebsocketRails
4
5
  # Provides controller helper methods for developing a WebsocketRails controller. Action methods
@@ -18,6 +19,24 @@ module WebsocketRails
18
19
  #
19
20
  class BaseController
20
21
 
22
+ # We need process_action to be in a module loaded before AbstractController::Callbacks
23
+ # to get inheritance properly
24
+ module Metal
25
+ def process_action(method, event)
26
+ if respond_to?(method)
27
+ self.send(method)
28
+ else
29
+ raise EventRoutingError.new(event, self, method)
30
+ end
31
+ end
32
+ def response_body
33
+ false
34
+ end
35
+ end
36
+
37
+ include Metal
38
+ include AbstractController::Callbacks
39
+
21
40
  # Tell Rails that BaseController and children can be reloaded when in
22
41
  # the Development environment.
23
42
  def self.inherited(controller)
@@ -26,33 +45,6 @@ module WebsocketRails
26
45
  end
27
46
  end
28
47
 
29
- # Add observers to specific events or the controller in general. This functionality is similar
30
- # to the Rails before_filter methods. Observers are stored as Proc objects and have access
31
- # to the current controller environment.
32
- #
33
- # Observing all events sent to a controller:
34
- # class ChatController < WebsocketRails::BaseController
35
- # observe {
36
- # if data_store.each_user.count > 0
37
- # puts 'a user has joined'
38
- # end
39
- # }
40
- # end
41
- # Observing a single event that occurrs:
42
- # observe(:new_message) {
43
- # puts 'new_message has fired!'
44
- # }
45
- def self.observe(event = nil, &block)
46
- # Stores the observer Procs for the current controller. See {observe} for details.
47
- @observers ||= Hash.new {|h,k| h[k] = Array.new}
48
-
49
- if event
50
- @observers[event] << block
51
- else
52
- @observers[:general] << block
53
- end
54
- end
55
-
56
48
  # Provides direct access to the connection object for the client that
57
49
  # initiated the event that is currently being executed.
58
50
  def connection
@@ -136,7 +128,11 @@ module WebsocketRails
136
128
  end
137
129
 
138
130
  def request
139
- @_request
131
+ connection.request
132
+ end
133
+
134
+ def action_name
135
+ @_action_name
140
136
  end
141
137
 
142
138
  # Provides access to the {DataStore} for the current controller. The {DataStore} provides convenience
@@ -150,24 +146,16 @@ module WebsocketRails
150
146
  connection.data_store
151
147
  end
152
148
 
153
- private
154
-
155
- # Executes the observers that have been defined for this controller. General observers are executed
156
- # first and event specific observers are executed last. Each will be executed in the order that
157
- # they have been defined. This method is executed by the {Dispatcher}.
158
- def execute_observers(event)
159
- observers = self.class.instance_variable_get(:@observers)
160
-
161
- return unless observers
149
+ def self.controller_name
150
+ self.name.underscore.gsub(/_controller$/,'')
151
+ end
162
152
 
163
- observers[:general].each do |observer|
164
- instance_eval( &observer )
165
- end
166
- observers[event].each do |observer|
167
- instance_eval( &observer )
168
- end
153
+ def controller_name
154
+ self.class.controller_name
169
155
  end
170
156
 
157
+ private
158
+
171
159
  def delegate
172
160
  connection.controller_delegate
173
161
  end
@@ -1,5 +1,3 @@
1
- require 'active_support/hash_with_indifferent_access'
2
-
3
1
  module WebsocketRails
4
2
 
5
3
  class << self
@@ -19,7 +17,7 @@ module WebsocketRails
19
17
  attr_reader :channels
20
18
 
21
19
  def initialize
22
- @channels = HashWithIndifferentAccess.new
20
+ @channels = {}.with_indifferent_access
23
21
  end
24
22
 
25
23
  def [](channel)
@@ -1,6 +1,14 @@
1
1
  module WebsocketRails
2
2
  class Configuration
3
3
 
4
+ def user_identifier
5
+ @user_identifier ||= :id
6
+ end
7
+
8
+ def user_identifier=(identifier)
9
+ @user_identifier = identifier
10
+ end
11
+
4
12
  def keep_subscribers_when_private?
5
13
  @keep_subscribers_when_private ||= false
6
14
  end
@@ -9,7 +9,7 @@ module WebsocketRails
9
9
  @adapters.unshift adapter
10
10
  end
11
11
 
12
- def self.establish_connection(request,dispatcher)
12
+ def self.establish_connection(request, dispatcher)
13
13
  adapter = adapters.detect { |a| a.accepts?( request.env ) } || (raise InvalidConnectionError)
14
14
  adapter.new request, dispatcher
15
15
  end
@@ -36,8 +36,10 @@ module WebsocketRails
36
36
  @queue = EventQueue.new
37
37
  @data_store = DataStore::Connection.new(self)
38
38
  @delegate = WebsocketRails::DelegationController.new
39
- @delegate.instance_variable_set(:@_env,request.env)
40
- @delegate.instance_variable_set(:@_request,request)
39
+ @delegate.instance_variable_set(:@_env, request.env)
40
+ @delegate.instance_variable_set(:@_request, request)
41
+
42
+ WebsocketRails.users[user_identifier] = self
41
43
  start_ping_timer
42
44
  end
43
45
 
@@ -92,6 +94,14 @@ module WebsocketRails
92
94
  send message
93
95
  end
94
96
 
97
+ def send_message(event_name, data = {}, options = {})
98
+ options.merge! :user_id => user_identifier, :connection => self
99
+ options[:data] = data
100
+
101
+ event = Event.new(event_name, options)
102
+ event.trigger
103
+ end
104
+
95
105
  def send(message)
96
106
  raise NotImplementedError, "Override this method in the connection specific adapter class"
97
107
  end
@@ -118,14 +128,29 @@ module WebsocketRails
118
128
 
119
129
  private
120
130
 
131
+ def user_identifier
132
+ @user_identifier ||= begin
133
+ identifier = WebsocketRails.config.user_identifier
134
+
135
+ unless @delegate.respond_to?(:current_user) &&
136
+ @delegate.current_user &&
137
+ @delegate.current_user.respond_to?(identifier)
138
+ return id
139
+ end
140
+
141
+ controller_delegate.current_user.send(identifier)
142
+ end
143
+ end
144
+
121
145
  def dispatch(event)
122
- dispatcher.dispatch( event )
146
+ dispatcher.dispatch event
123
147
  end
124
148
 
125
149
  def close_connection
126
150
  @data_store.destroy!
127
151
  @ping_timer.try(:cancel)
128
152
  dispatcher.connection_manager.close_connection self
153
+ WebsocketRails.users.delete(user_identifier)
129
154
  end
130
155
 
131
156
  attr_accessor :pong
@@ -41,6 +41,10 @@ module WebsocketRails
41
41
  end
42
42
  end
43
43
 
44
+ def inspect
45
+ "websocket_rails"
46
+ end
47
+
44
48
  # Primary entry point for the Rack application
45
49
  def call(env)
46
50
  request = ActionDispatch::Request.new(env)
@@ -11,11 +11,11 @@ module WebsocketRails
11
11
 
12
12
  # TODO: Add deprecation notice for user defined
13
13
  # instance variables.
14
- def new_for_event(event, controller_class)
14
+ def new_for_event(event, controller_class, method)
15
15
  controller_class = reload!(controller_class)
16
16
  controller = controller_class.new
17
17
 
18
- prepare(controller, event)
18
+ prepare(controller, event, method)
19
19
 
20
20
  controller
21
21
  end
@@ -26,10 +26,11 @@ module WebsocketRails
26
26
  @controller_stores[controller.class] ||= DataStore::Controller.new(controller)
27
27
  end
28
28
 
29
- def prepare(controller, event)
29
+ def prepare(controller, event, method)
30
30
  set_event(controller, event)
31
31
  set_dispatcher(controller, dispatcher)
32
32
  set_controller_store(controller)
33
+ set_action_name(controller, method)
33
34
  initialize_controller(controller)
34
35
  end
35
36
 
@@ -45,6 +46,10 @@ module WebsocketRails
45
46
  set_ivar :@_controller_store, controller, store_for_controller(controller)
46
47
  end
47
48
 
49
+ def set_action_name(controller, method)
50
+ set_ivar :@_action_name, controller, method
51
+ end
52
+
48
53
  def set_ivar(ivar, object, value)
49
54
  object.instance_variable_set(ivar, value)
50
55
  end
@@ -67,7 +72,7 @@ module WebsocketRails
67
72
  load "#{filename}.rb"
68
73
  return class_name.constantize
69
74
  end
70
-
75
+
71
76
  return controller
72
77
  end
73
78
 
@@ -102,8 +102,8 @@ module WebsocketRails
102
102
  #
103
103
  #
104
104
  # class AccountController < WebsocketRails::BaseController
105
- # # We will use an Event Observer to set the initial value
106
- # observe { controller_store[:event_count] ||= 0 }
105
+ # # We will use a before filter to set the initial value
106
+ # before_action { controller_store[:event_count] ||= 0 }
107
107
  #
108
108
  # # Mapped as `accounts.important_event` in the Event Router
109
109
  # def important_event
@@ -114,11 +114,11 @@ module WebsocketRails
114
114
  # end
115
115
  #
116
116
  # class ProductController < WebsocketRails::BaseController
117
- # # We will use an Event Observer to set the initial value
118
- # observe { controller_store[:event_count] ||= 0 }
117
+ # # We will use a before filter to set the initial value
118
+ # before_action { controller_store[:event_count] ||= 0 }
119
119
  #
120
120
  # # Mapped as `products.boring_event` in the Event Router
121
- # def boring_event
121
+ # def boring_event
122
122
  # # This will be private for each controller
123
123
  # controller_store[:event_count] += 1
124
124
  # trigger_success controller_store[:event_count]
@@ -27,6 +27,7 @@ module WebsocketRails
27
27
  if event.is_channel?
28
28
  WebsocketRails[event.channel].trigger_event event
29
29
  else
30
+ reload_event_map! unless event.is_internal?
30
31
  route event
31
32
  end
32
33
  end
@@ -41,6 +42,16 @@ module WebsocketRails
41
42
  end
42
43
  end
43
44
 
45
+ def reload_event_map!
46
+ return unless defined?(Rails) and !Rails.configuration.cache_classes
47
+ begin
48
+ load "#{Rails.root}/config/events.rb"
49
+ @event_map = EventMap.new(self)
50
+ rescue Exception => ex
51
+ log(:warn, "EventMap reload failed: #{ex.message}")
52
+ end
53
+ end
54
+
44
55
  private
45
56
 
46
57
  def route(event)
@@ -49,15 +60,9 @@ module WebsocketRails
49
60
  actions << Fiber.new do
50
61
  begin
51
62
  log_event(event) do
52
- controller = controller_factory.new_for_event(event, controller_class)
53
-
54
- controller.send(:execute_observers, event.name)
63
+ controller = controller_factory.new_for_event(event, controller_class, method)
55
64
 
56
- if controller.respond_to?(method)
57
- controller.send(method)
58
- else
59
- raise EventRoutingError.new(event, controller, method)
60
- end
65
+ controller.process_action(method, event)
61
66
  end
62
67
  rescue Exception => ex
63
68
  event.success = false
@@ -93,11 +93,11 @@ module WebsocketRails
93
93
  include Logging
94
94
  extend StaticEvents
95
95
 
96
- attr_reader :id, :name, :connection, :namespace, :channel
96
+ attr_reader :id, :name, :connection, :namespace, :channel, :user_id
97
97
 
98
98
  attr_accessor :data, :result, :success, :server_token
99
99
 
100
- def initialize(event_name,options={})
100
+ def initialize(event_name, options={})
101
101
  case event_name
102
102
  when String
103
103
  namespace = event_name.split('.')
@@ -111,6 +111,7 @@ module WebsocketRails
111
111
  @channel = options[:channel].to_sym if options[:channel]
112
112
  @connection = options[:connection]
113
113
  @server_token = options[:server_token]
114
+ @user_id = options[:user_id]
114
115
  @namespace = validate_namespace( options[:namespace] || namespace )
115
116
  end
116
117
 
@@ -120,6 +121,7 @@ module WebsocketRails
120
121
  {
121
122
  :id => id,
122
123
  :channel => channel,
124
+ :user_id => user_id,
123
125
  :data => data,
124
126
  :success => success,
125
127
  :result => result,
@@ -132,6 +134,10 @@ module WebsocketRails
132
134
  !@channel.nil?
133
135
  end
134
136
 
137
+ def is_user?
138
+ !@user_id.nil? && !is_channel?
139
+ end
140
+
135
141
  def is_invalid?
136
142
  name == :invalid_event
137
143
  end
@@ -163,7 +169,5 @@ module WebsocketRails
163
169
  namespace.map(&:to_sym) rescue [:global]
164
170
  end
165
171
 
166
-
167
-
168
172
  end
169
173
  end
@@ -55,12 +55,18 @@ module WebsocketRails
55
55
  fiber_redis = Redis.connect(WebsocketRails.config.redis_options)
56
56
  fiber_redis.subscribe "websocket_rails.events" do |on|
57
57
 
58
- on.message do |channel, encoded_event|
58
+ on.message do |_, encoded_event|
59
59
  event = Event.new_from_json(encoded_event, nil)
60
+
61
+ # Do nothing if this is the server that sent this event.
60
62
  next if event.server_token == server_token
61
63
 
64
+ # Ensure an event never gets triggered twice. Events added to the
65
+ # redis queue from other processes may not have a server token
66
+ # attached.
62
67
  event.server_token = server_token if event.server_token.nil?
63
- WebsocketRails[event.channel].trigger_event(event)
68
+
69
+ trigger_incoming event
64
70
  end
65
71
  end
66
72
 
@@ -72,17 +78,28 @@ module WebsocketRails
72
78
  EM.next_tick { synchro.resume }
73
79
 
74
80
  trap('TERM') do
75
- shutdown!
81
+ Thread.new { shutdown! }
76
82
  end
77
83
  trap('INT') do
78
- shutdown!
84
+ Thread.new { shutdown! }
79
85
  end
80
86
  trap('QUIT') do
81
- shutdown!
87
+ Thread.new { shutdown! }
82
88
  end
83
89
  end
84
90
  end
85
91
 
92
+ def trigger_incoming(event)
93
+ case
94
+ when event.is_channel?
95
+ WebsocketRails[event.channel].trigger_event(event)
96
+ when event.is_user?
97
+ connection = WebsocketRails.users[event.user_id]
98
+ return if connection.nil?
99
+ connection.trigger event
100
+ end
101
+ end
102
+
86
103
  def shutdown!
87
104
  remove_server(server_token)
88
105
  end
@@ -103,11 +120,9 @@ module WebsocketRails
103
120
  end
104
121
 
105
122
  def remove_server(token)
106
- Fiber.new do
107
- redis.srem "websocket_rails.active_servers", token
108
- info "Server Removed: #{token}"
109
- EM.stop
110
- end.resume
123
+ ruby_redis.srem "websocket_rails.active_servers", token
124
+ info "Server Removed: #{token}"
125
+ EM.stop
111
126
  end
112
127
 
113
128
  end
@@ -0,0 +1,85 @@
1
+ module WebsocketRails
2
+
3
+ # Contains a Hash of all connected users. This
4
+ # can be used to trigger an event on a specific
5
+ # user from outside of a WebsocketRails controller.
6
+ #
7
+ # The key for a particular user is defined in the
8
+ # configuration as `config.user_identifier`.
9
+ #
10
+ # If there is a `current_user` method defined
11
+ # in ApplicationController and a user is signed
12
+ # in to your application when the connection is
13
+ # opened, WebsocketRails will call the method
14
+ # defined in `config.user_identifier` on the
15
+ # `current_user` object and use that value as
16
+ # the key.
17
+ #
18
+ # # In your events.rb file
19
+ # WebsocketRails.setup do |config|
20
+ # # Defaults to :id
21
+ # config.user_identifier = :name
22
+ # end
23
+ #
24
+ # # In a standard controller or background job
25
+ # name = current_user.name
26
+ # WebsocketRails.users[name].send_message :event_name, data
27
+ #
28
+ # If no `current_user` method is defined or the
29
+ # user is not signed in when the WebsocketRails
30
+ # connection is opened, the key will default to
31
+ # `connection.id`.
32
+ def self.users
33
+ @user_manager ||= UserManager.new
34
+ end
35
+
36
+ class UserManager
37
+
38
+ attr_reader :users
39
+
40
+ def initialize
41
+ @users = {}
42
+ end
43
+
44
+ def [](identifier)
45
+ unless user = @users[identifier]
46
+ user = MissingUser.new(identifier)
47
+ end
48
+ user
49
+ end
50
+
51
+ def []=(identifier, connection)
52
+ @users[identifier] = connection
53
+ end
54
+
55
+ def delete(identifier)
56
+ @users.delete(identifier)
57
+ end
58
+
59
+ class MissingUser
60
+
61
+ def initialize(identifier)
62
+ @identifier = identifier
63
+ end
64
+
65
+ def send_message(event_name, data = {}, options = {})
66
+ if WebsocketRails.synchronize?
67
+ options.merge! :user_id => @identifier
68
+ options[:data] = data
69
+
70
+ event = Event.new(event_name, options)
71
+ Synchronization.publish event
72
+ true
73
+ else
74
+ false
75
+ end
76
+ end
77
+
78
+ def nil?
79
+ true
80
+ end
81
+
82
+ end
83
+
84
+ end
85
+ end
@@ -1,3 +1,3 @@
1
1
  module WebsocketRails
2
- VERSION = "0.4.9"
2
+ VERSION = "0.5.0"
3
3
  end
@@ -1,53 +1,53 @@
1
1
  class ChatController < WebsocketRails::BaseController
2
2
 
3
- observe {
3
+ before_action do
4
4
  if message_counter > 10
5
5
  self.message_counter = 0
6
6
  end
7
- }
8
-
9
- observe(:new_message) {
7
+ end
8
+
9
+ before_action :only => :new_message do
10
10
  true
11
- }
12
-
11
+ end
12
+
13
13
  attr_accessor :message_counter
14
14
 
15
15
  def initialize
16
16
  # perform application setup here
17
17
  @message_counter = 0
18
18
  end
19
-
19
+
20
20
  def client_connected
21
21
  # do something when a client connects
22
22
  end
23
-
23
+
24
24
  def error_occurred
25
25
  # do something when an error occurs
26
26
  end
27
-
27
+
28
28
  def new_message
29
29
  @message_counter += 1
30
30
  broadcast_message :new_message, message
31
31
  end
32
-
32
+
33
33
  def new_user
34
34
  controller_store[:user] = message
35
35
  broadcast_user_list
36
36
  end
37
-
37
+
38
38
  def change_username
39
39
  controller_store[:user] = message
40
40
  broadcast_user_list
41
41
  end
42
-
42
+
43
43
  def delete_user
44
44
  controller_store[:user] = nil
45
45
  broadcast_user_list
46
46
  end
47
-
47
+
48
48
  def broadcast_user_list
49
49
  users = ['user']
50
50
  broadcast_message :user_list, users
51
51
  end
52
-
52
+
53
53
  end
@@ -15,8 +15,12 @@ WebSocket Interface for the WebSocketRails client.
15
15
  this.on_close = __bind(this.on_close, this);
16
16
  this.on_message = __bind(this.on_message, this);
17
17
  this.trigger = __bind(this.trigger, this);
18
- if (!this.url.match(/^wss?:\/\//)) {
18
+ if (this.url.match(/^wss?:\/\//)) {
19
+ console.log("WARNING: Using connection urls with protocol specified is depricated");
20
+ } else if (window.location.protocol === 'http:') {
19
21
  this.url = "ws://" + this.url;
22
+ } else {
23
+ this.url = "wss://" + this.url;
20
24
  }
21
25
  this.message_queue = [];
22
26
  this._conn = new WebSocket(this.url);
@@ -43,6 +43,18 @@ module WebsocketRails
43
43
  it "should create a new DataStore::Connection instance" do
44
44
  subject.data_store.should be_a DataStore::Connection
45
45
  end
46
+
47
+ before do
48
+ WebsocketRails.config.stub(:user_identifier).and_return(:name)
49
+ WebsocketRails::DelegationController.any_instance
50
+ .stub_chain(:current_user, :name)
51
+ .and_return('Frank')
52
+ subject
53
+ end
54
+
55
+ it "adds itself to the UserManager Hash" do
56
+ WebsocketRails.users['Frank'].should == subject
57
+ end
46
58
  end
47
59
 
48
60
  describe "#on_open" do
@@ -69,6 +81,11 @@ module WebsocketRails
69
81
  dispatcher.should_receive(:dispatch).with(on_close_event)
70
82
  subject.on_close("data")
71
83
  end
84
+
85
+ it "removes itself from the global UserMnaager" do
86
+ subject.on_close
87
+ WebsocketRails.users[subject.id].nil?.should == true
88
+ end
72
89
  end
73
90
 
74
91
  describe "#on_error" do
@@ -87,9 +104,36 @@ module WebsocketRails
87
104
  end
88
105
  end
89
106
 
107
+ describe "#send_message" do
108
+ before do
109
+ Event.any_instance.stub(:trigger)
110
+ end
111
+ after do
112
+ subject.send_message :message, "some_data"
113
+ end
114
+
115
+ it "creates and triggers a new event" do
116
+ Event.any_instance.should_receive(:trigger)
117
+ end
118
+
119
+ it "sets it's user identifier on the event" do
120
+ subject.stub(:user_identifier).and_return(:some_name_or_id)
121
+ Event.should_receive(:new) do |name, options|
122
+ options[:user_id].should == :some_name_or_id
123
+ end.and_call_original
124
+ end
125
+
126
+ it "sets the connection property of the event correctly" do
127
+ subject.stub(:user_identifier).and_return(:some_name_or_id)
128
+ Event.should_receive(:new) do |name, options|
129
+ options[:connection].should == subject
130
+ end.and_call_original
131
+ end
132
+ end
133
+
90
134
  describe "#send" do
91
135
  it "should raise a NotImplementedError exception" do
92
- expect { subject.send :message }.to raise_exception( NotImplementedError )
136
+ expect { subject.send :message }.to raise_exception(NotImplementedError)
93
137
  end
94
138
  end
95
139
 
@@ -38,37 +38,37 @@ module WebsocketRails
38
38
  rails_env = double(:rails_env)
39
39
  Rails.stub(:env).and_return rails_env
40
40
  rails_env.stub(:development?).and_return true
41
- controller = subject.new_for_event(event, InternalController)
41
+ controller = subject.new_for_event(event, InternalController, 'some_method')
42
42
  controller.class.should == InternalController
43
43
  end
44
44
 
45
45
  end
46
46
 
47
47
  it "creates and returns a new controller instance" do
48
- controller = subject.new_for_event(event, TestController)
48
+ controller = subject.new_for_event(event, TestController, 'some_method')
49
49
  controller.class.should == TestController
50
50
  end
51
51
 
52
52
  it "initializes the controller with the correct data_store" do
53
53
  store = double('data_store')
54
54
  subject.controller_stores[TestController] = store
55
- controller = subject.new_for_event(event, TestController)
55
+ controller = subject.new_for_event(event, TestController, 'some_method')
56
56
  controller.controller_store.should == store
57
57
  end
58
58
 
59
59
  it "initializes the controller with the correct event" do
60
- controller = subject.new_for_event(event, TestController)
60
+ controller = subject.new_for_event(event, TestController, 'some_method')
61
61
  controller.event.should == event
62
62
  end
63
63
 
64
64
  it "initializes the controller with the correct dispatcher" do
65
- controller = subject.new_for_event(event, TestController)
65
+ controller = subject.new_for_event(event, TestController, 'some_method')
66
66
  controller._dispatcher.should == dispatcher
67
67
  end
68
68
 
69
69
  it "calls #initialize_session on the controller only once" do
70
70
  TestController.any_instance.should_receive(:initialize_session).once
71
- 3.times { subject.new_for_event(event, TestController) }
71
+ 3.times { subject.new_for_event(event, TestController, 'some_method') }
72
72
  end
73
73
  end
74
74
 
@@ -20,21 +20,12 @@ def set_temp_module_const(mod, name, value, &block)
20
20
  mod.send(:remove_const, name)
21
21
  end
22
22
 
23
-
24
23
  module WebsocketRails
25
24
 
26
-
27
-
28
25
  class EventTarget
29
26
  attr_reader :_event, :_dispatcher, :test_method
30
-
31
- def execute_observers(event_name)
32
- true
33
- end
34
27
  end
35
28
 
36
-
37
-
38
29
  describe Dispatcher do
39
30
 
40
31
  let(:event) { double('Event').as_null_object }
@@ -59,6 +50,7 @@ module WebsocketRails
59
50
 
60
51
  describe "#receive" do
61
52
  before { Event.stub(:new).and_return( event ) }
53
+
62
54
  it "should dispatch a new event" do
63
55
  subject.stub(:dispatch) do |dispatch_event|
64
56
  dispatch_event.should == event
@@ -79,7 +71,7 @@ module WebsocketRails
79
71
  end
80
72
 
81
73
  it "should execute the correct method on the target class" do
82
- EventTarget.any_instance.should_receive(:test_method)
74
+ EventTarget.any_instance.should_receive(:process_action).with(:test_method, event)
83
75
  subject.dispatch(event)
84
76
  end
85
77
 
@@ -167,7 +159,6 @@ module WebsocketRails
167
159
 
168
160
  end
169
161
 
170
-
171
162
  context 'when ActiveRecord::RecordInvalid is not defined' do
172
163
 
173
164
  it 'should check that exception can be converted to JSON' do
@@ -181,6 +172,5 @@ module WebsocketRails
181
172
  end
182
173
 
183
174
  end
184
-
185
175
  end
186
176
  end
@@ -6,7 +6,7 @@ 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,"server_token":"1234"}]' }
9
+ let(:channel_encoded_message_string) { '["new_message",{"id":"1234","channel":"awesome_channel","user_id":null,"data":"this is a message","success":null,"result":null,"server_token":"1234"}]' }
10
10
  let(:synchronizable_encoded_message) { '["new_message",{"id":"1234","data":{"message":"this is a message"},"server_token":"1234"}]' }
11
11
  let(:connection) { double('connection') }
12
12
 
@@ -96,6 +96,13 @@ module WebsocketRails
96
96
  end
97
97
  end
98
98
 
99
+ describe "#is_user?" do
100
+ it "returns true if the event is meant for a specific user" do
101
+ event = Event.new "event", :data => "data", :user_id => :username
102
+ event.is_user?
103
+ end
104
+ end
105
+
99
106
  describe "#is_invalid?" do
100
107
  it "returns true if the event name is :invalid_event" do
101
108
  event = Event.new(:invalid_event)
@@ -2,12 +2,23 @@ require "spec_helper"
2
2
  require "eventmachine"
3
3
 
4
4
  module WebsocketRails
5
+ #class Synchronization
6
+ # def test_block(channel, &block)
7
+ # # do nothing beyatch
8
+ # block.call
9
+ # end
10
+
11
+ # def synchronize!
12
+ # test_block("something") { raise "FTW!" }
13
+ # end
14
+ #end
15
+
5
16
  describe Synchronization do
6
17
 
7
18
  around(:each) do |example|
8
19
  EM.run do
9
20
  Fiber.new do
10
- @redis = Redis.new
21
+ @redis = Redis.new(WebsocketRails.config.redis_options)
11
22
  @redis.del "websocket_rails.active_servers"
12
23
  example.run
13
24
  end.resume
@@ -15,6 +26,7 @@ module WebsocketRails
15
26
  end
16
27
 
17
28
  after(:each) do
29
+ @redis.del "websocket_rails.active_servers"
18
30
  EM.stop
19
31
  end
20
32
 
@@ -30,36 +42,43 @@ module WebsocketRails
30
42
  end
31
43
 
32
44
  describe "#synchronize!" do
33
- #before do
34
- # #@synchro = Synchronization.new
35
- #end
36
-
37
- #it "should receive remote channel events" do
38
- # event = Event.new(:channel_event, :channel => :channel_one, :data => 'hello channel one')
45
+ # need to add an integration test to cover this.
46
+ end
39
47
 
40
- # @redis.should_receive(:subscribe)
41
- # Redis.should_receive(:connect).with(WebsocketRails.redis_options).and_return(@redis)
48
+ describe "#trigger_incoming" do
49
+ context "when dispatching channel events" do
50
+ before do
51
+ @event = Event.new(:channel_event, :channel => :channel_one, :data => 'hello channel one')
52
+ end
42
53
 
43
- # Synchronization.new.synchronize!
54
+ it "triggers the event on the correct channel" do
55
+ WebsocketRails[:channel_one].should_receive(:trigger_event).with @event
56
+ subject.trigger_incoming @event
57
+ end
58
+ end
44
59
 
45
- # EM::Synchrony.sleep(0.5)
60
+ context "when dispatching user events" do
61
+ before do
62
+ @event = Event.new(:channel_event, :user_id => :username, :data => 'hello channel one')
63
+ end
46
64
 
47
- # redis = @redis
48
- # EM.next_tick { redis.publish "websocket_rails.events", event.serialize }
49
- #end
50
- it "should set server_token to stop circular publishing" do
51
- event = Event.new(:redis_event, :channel => 'synchrony', :data => 'hello from another process')
52
- if event.server_token.nil?
53
- event.server_token = subject.server_token
65
+ context "and the user is not connected to this server" do
66
+ it "does nothing" do
67
+ subject.trigger_incoming(@event).should == nil
68
+ end
54
69
  end
55
- event.server_token.should == subject.server_token
56
- end
57
- it "should not set server_token if it is present" do
58
- event = Event.new(:redis_event, :channel => 'synchrony', :data => 'hello from another process', :server_token => '1234')
59
- if event.server_token.nil?
60
- event.server_token = subject.server_token
70
+
71
+ context "and the user is connected to this server" do
72
+ before do
73
+ @connection = double('Connection')
74
+ WebsocketRails.users[:username] = @connection
75
+ end
76
+
77
+ it "triggers the event on the correct user" do
78
+ WebsocketRails.users[:username].should_receive(:trigger).with @event
79
+ subject.trigger_incoming @event
80
+ end
61
81
  end
62
- event.server_token.should == '1234'
63
82
  end
64
83
  end
65
84
 
@@ -0,0 +1,61 @@
1
+ require "spec_helper"
2
+
3
+ module WebsocketRails
4
+
5
+ describe ".users" do
6
+ it "returns the global instance of UserManager" do
7
+ WebsocketRails.users.should be_a UserManager
8
+ end
9
+
10
+ context "when synchronization is enabled" do
11
+ before do
12
+ WebsocketRails.stub(:synchronize?).and_return(true)
13
+ end
14
+
15
+ context "and the user is not connected to this worker" do
16
+ it "publishes the event to redis" do
17
+ Synchronization.should_receive(:publish) do |event|
18
+ event.user_id.should == :missing
19
+ end
20
+
21
+ WebsocketRails.users[:missing].send_message :test, :data
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ describe UserManager do
28
+
29
+ let(:connection) { double('Connection') }
30
+
31
+ describe "#[]=" do
32
+ it "store's a reference to a connection in the user's hash" do
33
+ subject[:username] = connection
34
+ subject.users[:username].should == connection
35
+ end
36
+ end
37
+
38
+ describe "#[]" do
39
+ before do
40
+ subject[:username] = connection
41
+ end
42
+
43
+ context "when passed a known user identifier" do
44
+ it "returns that user's connection" do
45
+ subject[:username].should == connection
46
+ end
47
+ end
48
+ end
49
+
50
+ describe "#delete" do
51
+ before do
52
+ subject[:username] = connection
53
+ end
54
+
55
+ it "deletes the connection from the users hash" do
56
+ subject.delete(:username)
57
+ subject[:username].should be_a UserManager::MissingUser
58
+ end
59
+ end
60
+ end
61
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: websocket-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.9
4
+ version: 0.5.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2013-07-11 00:00:00.000000000 Z
14
+ date: 2013-09-03 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: rails
@@ -190,6 +190,7 @@ files:
190
190
  - lib/config.ru
191
191
  - lib/generators/websocket_rails/install/install_generator.rb
192
192
  - lib/generators/websocket_rails/install/templates/events.rb
193
+ - lib/generators/websocket_rails/install/templates/websocket_rails.rb
193
194
  - lib/rails/app/controllers/websocket_rails/delegation_controller.rb
194
195
  - lib/rails/config/routes.rb
195
196
  - lib/rails/tasks/websocket_rails.tasks
@@ -216,6 +217,7 @@ files:
216
217
  - lib/websocket_rails/logging.rb
217
218
  - lib/websocket_rails/spec_helpers.rb
218
219
  - lib/websocket_rails/synchronization.rb
220
+ - lib/websocket_rails/user_manager.rb
219
221
  - lib/websocket_rails/version.rb
220
222
  - bin/thin-socketrails
221
223
  - spec/dummy/app/controllers/application_controller.rb
@@ -294,6 +296,7 @@ files:
294
296
  - spec/unit/logging_spec.rb
295
297
  - spec/unit/synchronization_spec.rb
296
298
  - spec/unit/target_validator_spec.rb
299
+ - spec/unit/user_manager_spec.rb
297
300
  - MIT-LICENSE
298
301
  - Rakefile
299
302
  - Gemfile
@@ -301,7 +304,7 @@ files:
301
304
  - CHANGELOG.md
302
305
  homepage: http://danknox.github.com/websocket-rails/
303
306
  licenses: []
304
- post_install_message: Welcome to WebsocketRails v0.4.9!
307
+ post_install_message: Welcome to WebsocketRails v0.5.0!
305
308
  rdoc_options: []
306
309
  require_paths:
307
310
  - lib
@@ -313,7 +316,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
313
316
  version: '0'
314
317
  segments:
315
318
  - 0
316
- hash: -2441396343706207267
319
+ hash: -3442295277156714841
317
320
  required_rubygems_version: !ruby/object:Gem::Requirement
318
321
  none: false
319
322
  requirements:
@@ -322,7 +325,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
322
325
  version: '0'
323
326
  segments:
324
327
  - 0
325
- hash: -2441396343706207267
328
+ hash: -3442295277156714841
326
329
  requirements: []
327
330
  rubyforge_project: websocket-rails
328
331
  rubygems_version: 1.8.25