websocket-rails 0.4.9 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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