hkroger-websocket-rails 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +328 -0
  3. data/Gemfile +27 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +237 -0
  6. data/Rakefile +72 -0
  7. data/bin/thin-socketrails +45 -0
  8. data/lib/assets/javascripts/websocket_rails/abstract_connection.js.coffee +45 -0
  9. data/lib/assets/javascripts/websocket_rails/channel.js.coffee +70 -0
  10. data/lib/assets/javascripts/websocket_rails/event.js.coffee +42 -0
  11. data/lib/assets/javascripts/websocket_rails/http_connection.js.coffee +66 -0
  12. data/lib/assets/javascripts/websocket_rails/main.js +6 -0
  13. data/lib/assets/javascripts/websocket_rails/websocket_connection.js.coffee +29 -0
  14. data/lib/assets/javascripts/websocket_rails/websocket_rails.js.coffee +158 -0
  15. data/lib/config.ru +3 -0
  16. data/lib/generators/websocket_rails/install/install_generator.rb +33 -0
  17. data/lib/generators/websocket_rails/install/templates/events.rb +14 -0
  18. data/lib/generators/websocket_rails/install/templates/websocket_rails.rb +63 -0
  19. data/lib/hkroger-websocket-rails.rb +1 -0
  20. data/lib/rails/app/controllers/websocket_rails/delegation_controller.rb +13 -0
  21. data/lib/rails/config/routes.rb +7 -0
  22. data/lib/rails/tasks/websocket_rails.tasks +42 -0
  23. data/lib/spec_helpers/matchers/route_matchers.rb +65 -0
  24. data/lib/spec_helpers/matchers/trigger_matchers.rb +113 -0
  25. data/lib/spec_helpers/spec_helper_event.rb +34 -0
  26. data/lib/websocket-rails.rb +108 -0
  27. data/lib/websocket_rails/base_controller.rb +197 -0
  28. data/lib/websocket_rails/channel.rb +97 -0
  29. data/lib/websocket_rails/channel_manager.rb +55 -0
  30. data/lib/websocket_rails/configuration.rb +169 -0
  31. data/lib/websocket_rails/connection_adapters.rb +195 -0
  32. data/lib/websocket_rails/connection_adapters/http.rb +120 -0
  33. data/lib/websocket_rails/connection_adapters/web_socket.rb +36 -0
  34. data/lib/websocket_rails/connection_manager.rb +119 -0
  35. data/lib/websocket_rails/controller_factory.rb +80 -0
  36. data/lib/websocket_rails/data_store.rb +145 -0
  37. data/lib/websocket_rails/dispatcher.rb +129 -0
  38. data/lib/websocket_rails/engine.rb +26 -0
  39. data/lib/websocket_rails/event.rb +189 -0
  40. data/lib/websocket_rails/event_map.rb +184 -0
  41. data/lib/websocket_rails/event_queue.rb +33 -0
  42. data/lib/websocket_rails/internal_events.rb +37 -0
  43. data/lib/websocket_rails/logging.rb +133 -0
  44. data/lib/websocket_rails/spec_helpers.rb +3 -0
  45. data/lib/websocket_rails/synchronization.rb +182 -0
  46. data/lib/websocket_rails/user_manager.rb +276 -0
  47. data/lib/websocket_rails/version.rb +3 -0
  48. data/spec/dummy/Rakefile +7 -0
  49. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  50. data/spec/dummy/app/controllers/chat_controller.rb +53 -0
  51. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  52. data/spec/dummy/app/models/user.rb +2 -0
  53. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  54. data/spec/dummy/config.ru +4 -0
  55. data/spec/dummy/config/application.rb +45 -0
  56. data/spec/dummy/config/boot.rb +10 -0
  57. data/spec/dummy/config/database.yml +22 -0
  58. data/spec/dummy/config/environment.rb +5 -0
  59. data/spec/dummy/config/environments/development.rb +26 -0
  60. data/spec/dummy/config/environments/production.rb +49 -0
  61. data/spec/dummy/config/environments/test.rb +34 -0
  62. data/spec/dummy/config/events.rb +7 -0
  63. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  64. data/spec/dummy/config/initializers/inflections.rb +10 -0
  65. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  66. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  67. data/spec/dummy/config/initializers/session_store.rb +8 -0
  68. data/spec/dummy/config/locales/en.yml +5 -0
  69. data/spec/dummy/config/routes.rb +58 -0
  70. data/spec/dummy/db/development.sqlite3 +0 -0
  71. data/spec/dummy/db/migrate/20130902222552_create_users.rb +10 -0
  72. data/spec/dummy/db/schema.rb +23 -0
  73. data/spec/dummy/db/test.sqlite3 +0 -0
  74. data/spec/dummy/log/development.log +17 -0
  75. data/spec/dummy/log/production.log +0 -0
  76. data/spec/dummy/log/server.log +0 -0
  77. data/spec/dummy/public/404.html +26 -0
  78. data/spec/dummy/public/422.html +26 -0
  79. data/spec/dummy/public/500.html +26 -0
  80. data/spec/dummy/public/favicon.ico +0 -0
  81. data/spec/dummy/public/javascripts/application.js +2 -0
  82. data/spec/dummy/public/javascripts/controls.js +965 -0
  83. data/spec/dummy/public/javascripts/dragdrop.js +974 -0
  84. data/spec/dummy/public/javascripts/effects.js +1123 -0
  85. data/spec/dummy/public/javascripts/prototype.js +6001 -0
  86. data/spec/dummy/public/javascripts/rails.js +202 -0
  87. data/spec/dummy/script/rails +6 -0
  88. data/spec/integration/connection_manager_spec.rb +135 -0
  89. data/spec/javascripts/support/jasmine.yml +52 -0
  90. data/spec/javascripts/support/jasmine_helper.rb +38 -0
  91. data/spec/javascripts/support/vendor/sinon-1.7.1.js +4343 -0
  92. data/spec/javascripts/websocket_rails/channel_spec.coffee +112 -0
  93. data/spec/javascripts/websocket_rails/event_spec.coffee +69 -0
  94. data/spec/javascripts/websocket_rails/helpers.coffee +6 -0
  95. data/spec/javascripts/websocket_rails/websocket_connection_spec.coffee +158 -0
  96. data/spec/javascripts/websocket_rails/websocket_rails_spec.coffee +274 -0
  97. data/spec/spec_helper.rb +41 -0
  98. data/spec/spec_helpers/matchers/route_matchers_spec.rb +109 -0
  99. data/spec/spec_helpers/matchers/trigger_matchers_spec.rb +247 -0
  100. data/spec/spec_helpers/spec_helper_event_spec.rb +66 -0
  101. data/spec/support/helper_methods.rb +42 -0
  102. data/spec/support/mock_web_socket.rb +41 -0
  103. data/spec/unit/base_controller_spec.rb +74 -0
  104. data/spec/unit/channel_manager_spec.rb +58 -0
  105. data/spec/unit/channel_spec.rb +169 -0
  106. data/spec/unit/connection_adapters/http_spec.rb +88 -0
  107. data/spec/unit/connection_adapters/web_socket_spec.rb +30 -0
  108. data/spec/unit/connection_adapters_spec.rb +259 -0
  109. data/spec/unit/connection_manager_spec.rb +148 -0
  110. data/spec/unit/controller_factory_spec.rb +76 -0
  111. data/spec/unit/data_store_spec.rb +106 -0
  112. data/spec/unit/dispatcher_spec.rb +203 -0
  113. data/spec/unit/event_map_spec.rb +120 -0
  114. data/spec/unit/event_queue_spec.rb +36 -0
  115. data/spec/unit/event_spec.rb +181 -0
  116. data/spec/unit/logging_spec.rb +162 -0
  117. data/spec/unit/synchronization_spec.rb +150 -0
  118. data/spec/unit/target_validator_spec.rb +88 -0
  119. data/spec/unit/user_manager_spec.rb +165 -0
  120. metadata +320 -0
@@ -0,0 +1,184 @@
1
+ module WebsocketRails
2
+ # Provides a DSL for mapping client events to controller actions.
3
+ #
4
+ # == Example events.rb file
5
+ # # located in config/initializers/events.rb
6
+ # WebsocketRails::EventMap.describe do
7
+ # subscribe :client_connected, to: ChatController, with_method: :client_connected
8
+ # end
9
+ #
10
+ # A single event can be mapped to any number of controller actions.
11
+ #
12
+ # subscribe :new_message, :to => ChatController, :with_method => :rebroadcast_message
13
+ # subscribe :new_message, :to => LogController, :with_method => :log_message
14
+ #
15
+ # Events can be nested underneath namesapces.
16
+ #
17
+ # namespace :product do
18
+ # subscribe :new, :to => ProductController, :with_method => :new
19
+ # end
20
+ class EventMap
21
+
22
+ def self.describe(&block)
23
+ WebsocketRails.config.route_block = block
24
+ end
25
+
26
+ attr_reader :namespace
27
+
28
+ def initialize(dispatcher)
29
+ @dispatcher = dispatcher
30
+ @namespace = DSL.new(dispatcher).evaluate WebsocketRails.config.route_block
31
+ @namespace = DSL.new(dispatcher,@namespace).evaluate InternalEvents.events
32
+ end
33
+
34
+ def routes_for(event, &block)
35
+ @namespace.routes_for event, &block
36
+ end
37
+
38
+ # Proxy the reload_controllers! method to the global namespace.
39
+ def reload_controllers!
40
+ @namespace.reload_controllers!
41
+ end
42
+
43
+ # Provides the DSL methods available to the Event routes file
44
+ class DSL
45
+
46
+ def initialize(dispatcher,namespace=nil)
47
+ if namespace
48
+ @namespace = namespace
49
+ else
50
+ @namespace = Namespace.new :global, dispatcher
51
+ end
52
+ end
53
+
54
+ def evaluate(route_block)
55
+ instance_eval &route_block unless route_block.nil?
56
+ @namespace
57
+ end
58
+
59
+ def subscribe(event_name,options)
60
+ @namespace.store event_name, options
61
+ end
62
+
63
+ def namespace(new_namespace,&block)
64
+ @namespace = @namespace.find_or_create new_namespace
65
+ instance_eval &block if block.present?
66
+ @namespace = @namespace.parent
67
+ end
68
+
69
+ def private_channel(channel)
70
+ WebsocketRails[channel].make_private
71
+ end
72
+
73
+ end
74
+
75
+ # Stores route map for nested namespaces
76
+ class Namespace
77
+
78
+ include Logging
79
+
80
+ attr_reader :name, :controllers, :actions, :namespaces, :parent
81
+
82
+ def initialize(name,dispatcher,parent=nil)
83
+ @name = name
84
+ @parent = parent
85
+ @dispatcher = dispatcher
86
+ @actions = Hash.new {|h,k| h[k] = Array.new}
87
+ @controllers = Hash.new
88
+ @namespaces = Hash.new
89
+ end
90
+
91
+ def find_or_create(namespace)
92
+ unless child = namespaces[namespace]
93
+ child = Namespace.new namespace, @dispatcher, self
94
+ namespaces[namespace] = child
95
+ end
96
+ child
97
+ end
98
+
99
+ # Stores controller/action pairs for events subscribed under
100
+ # this namespace.
101
+ def store(event_name,options)
102
+ klass, action = TargetValidator.validate_target options
103
+ actions[event_name] << [klass,action]
104
+ end
105
+
106
+ # Iterates through the namespace tree and yields all
107
+ # controller/action pairs stored for the target event.
108
+ def routes_for(event, event_namespace=nil, &block)
109
+
110
+ # Grab the first level namespace from the namespace array
111
+ # and remove it from the copy.
112
+ event_namespace = copy_event_namespace( event, event_namespace ) || return
113
+ namespace = event_namespace.shift
114
+
115
+ # If the namespace matches the current namespace and we are
116
+ # at the last namespace level, yield any controller/action
117
+ # pairs for this event.
118
+ #
119
+ # If the namespace does not match, search the list of child
120
+ # namespaces stored at this level for a match and delegate
121
+ # to it's #routes_for method, passing along the current
122
+ # copy of the event's namespace array.
123
+ if namespace == @name and event_namespace.empty?
124
+ actions[event.name].each do |klass,action|
125
+ block.call klass, action
126
+ end
127
+ else
128
+ child_namespace = event_namespace.first
129
+ child = namespaces[child_namespace]
130
+ child.routes_for event, event_namespace, &block unless child.nil?
131
+ end
132
+ end
133
+
134
+ private
135
+
136
+ def copy_event_namespace(event, namespace=nil)
137
+ namespace = event.namespace.dup if namespace.nil?
138
+ namespace
139
+ end
140
+
141
+ end
142
+
143
+ end
144
+
145
+ class TargetValidator
146
+
147
+ # Parses the target and extracts controller/action pair or raises an error if target is invalid
148
+ def self.validate_target(target)
149
+ case target
150
+ when Hash
151
+ validate_hash_target target
152
+ when String
153
+ validate_string_target target
154
+ else
155
+ raise('Must specify the event target either as a string product#new_product or as a Hash to: ProductController, with_method: :new_product')
156
+ end
157
+ end
158
+
159
+ private
160
+
161
+ # Parses the target as a Hash, expecting keys to: and with_method:
162
+ def self.validate_hash_target(target)
163
+ klass = target[:to] || raise("Must specify a class for to: option in event route")
164
+ action = target[:with_method] || raise("Must specify a method for with_method: option in event route")
165
+ [klass, action]
166
+ end
167
+
168
+ # Parses the target as a String, expecting it to be in the format "product#new_product"
169
+ def self.validate_string_target(target)
170
+ strings = target.split('#')
171
+ raise('The string must be in a format like product#new_product') unless strings.count == 2
172
+ klass = constantize_controller strings[0]
173
+ action = strings[1].to_sym
174
+ [klass, action]
175
+ end
176
+
177
+ def self.constantize_controller(controller_string)
178
+ strings = (controller_string << '_controller').split('/')
179
+ strings.map{|string| string.camelize}.join('::').constantize
180
+ end
181
+
182
+ end
183
+
184
+ end
@@ -0,0 +1,33 @@
1
+ module WebsocketRails
2
+ class EventQueue
3
+
4
+ attr_reader :queue
5
+
6
+ def initialize
7
+ @queue = []
8
+ end
9
+
10
+ def enqueue(event)
11
+ @queue << event
12
+ end
13
+ alias :<< :enqueue
14
+
15
+ def last
16
+ @queue.last
17
+ end
18
+
19
+ def size
20
+ @queue.size
21
+ end
22
+
23
+ def flush(&block)
24
+ unless block.nil?
25
+ @queue.each do |item|
26
+ block.call item
27
+ end
28
+ end
29
+ @queue = []
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,37 @@
1
+ module WebsocketRails
2
+ class InternalEvents
3
+ def self.events
4
+ Proc.new do
5
+ namespace :websocket_rails do
6
+ subscribe :pong, :to => InternalController, :with_method => :do_pong
7
+ subscribe :subscribe, :to => InternalController, :with_method => :subscribe_to_channel
8
+ subscribe :unsubscribe, :to => InternalController, :with_method => :unsubscribe_to_channel
9
+ end
10
+ end
11
+ end
12
+ end
13
+
14
+ class InternalController < BaseController
15
+ include Logging
16
+
17
+ def subscribe_to_channel
18
+ channel_name = event.data[:channel]
19
+ unless WebsocketRails[channel_name].is_private?
20
+ WebsocketRails[channel_name].subscribe connection
21
+ trigger_success
22
+ else
23
+ trigger_failure( { :reason => "channel is private", :hint => "use subscribe_private instead." } )
24
+ end
25
+ end
26
+
27
+ def unsubscribe_to_channel
28
+ channel_name = event.data[:channel]
29
+ WebsocketRails[channel_name].unsubscribe connection
30
+ trigger_success
31
+ end
32
+
33
+ def do_pong
34
+ connection.pong = true
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,133 @@
1
+ require 'active_support/core_ext/module/delegation'
2
+ require 'active_support/core_ext/hash'
3
+ require 'bigdecimal'
4
+ require 'bigdecimal/util'
5
+
6
+ module WebsocketRails
7
+ module Logging
8
+ # Logging module heavily influenced by Travis-Support library
9
+
10
+ LOGGABLE_DATA = [
11
+ String,
12
+ Hash,
13
+ ActiveSupport::HashWithIndifferentAccess
14
+ ]
15
+
16
+ ANSI = {
17
+ :red => 31,
18
+ :green => 32,
19
+ :yellow => 33,
20
+ :cyan => 36
21
+ }
22
+
23
+ class << self
24
+ def configure(logger)
25
+ logger.tap do
26
+ logger.formatter = proc { |*args| Format.format(*args) }
27
+ logger.level = Logger.const_get(log_level.to_s.upcase)
28
+ end
29
+ end
30
+
31
+ def log_level
32
+ WebsocketRails.config.log_level || :debug
33
+ end
34
+ end
35
+
36
+ delegate :logger, :to => WebsocketRails
37
+
38
+ [:fatal, :error, :warn, :info, :debug].each do |level|
39
+ define_method(level) do |*args|
40
+ message, options = *args
41
+ log(level, message, options)
42
+ end
43
+ end
44
+
45
+ def log(level, message, options = {})
46
+ message.chomp.split("\n").each do |line|
47
+ logger.send(level, wrap(level, self, line, options || {}))
48
+ end
49
+ end
50
+
51
+ def log_event_start(event)
52
+ message = "Started Event: #{event.encoded_name}\n"
53
+ message << "#{colorize(:cyan, "Name:")} #{event.encoded_name}\n"
54
+ message << "#{colorize(:cyan, "Data:")} #{event.data.inspect}\n" if log_data?(event)
55
+ message << "#{colorize(:cyan, "Connection:")} #{event.connection}\n\n"
56
+ info message
57
+ end
58
+
59
+ def log_event_end(event, time)
60
+ info "Event #{event.encoded_name} Finished in #{time.to_f.to_d.to_s} seconds\n\n"
61
+ end
62
+
63
+ def log_event(event, &block)
64
+ log_event_start(event) if log_event?(event)
65
+ start_time = Time.now
66
+ block.call
67
+ total_time = Time.now - start_time
68
+ log_event_end(event, total_time) if log_event?(event)
69
+ rescue Exception => ex
70
+ log_exception(ex)
71
+ raise
72
+ end
73
+
74
+ def log_event?(event)
75
+ if event.is_internal?
76
+ WebsocketRails.config.log_internal_events?
77
+ else
78
+ true
79
+ end
80
+ end
81
+
82
+ def log_data?(event)
83
+ LOGGABLE_DATA.include?(event.data.class)
84
+ end
85
+
86
+ def log_exception(exception)
87
+ logger.error(wrap(:error, self, "#{exception.class.name}: #{exception.message}"))
88
+ exception.backtrace.each { |line| logger.error(wrap(:error, self, line)) } if exception.backtrace
89
+ logger << "\n"
90
+ rescue Exception => ex
91
+ puts '--- FATAL ---'
92
+ puts 'an exception occured while logging an exception'
93
+ puts ex.message, ex.backtrace
94
+ puts exception.message, exception.backtrace
95
+ end
96
+
97
+ def wrap(level, object, message, options = {})
98
+ header = options[:header] || object.log_header
99
+ color = color_for_level(level)
100
+ "[#{colorize(color, header)}] #{message.chomp}"
101
+ end
102
+
103
+ def colorize(color, text)
104
+ "\e[#{ANSI[color]}m#{text}\e[0m"
105
+ end
106
+
107
+ def color_for_level(level)
108
+ case level
109
+ when :info then :green
110
+ when :debug then :yellow
111
+ else
112
+ :red
113
+ end
114
+ end
115
+
116
+ def log_header
117
+ self.class.name.split('::').last
118
+ end
119
+
120
+ module Format
121
+ class << self
122
+ def format(severity, time, progname, msg)
123
+ "#{severity[0, 1]} [#{format_time(time)}] #{msg}\n"
124
+ end
125
+
126
+ def format_time(time)
127
+ time.strftime("%Y-%m-%d %H:%M:%S.") << time.usec.to_s[0, 3]
128
+ end
129
+ end
130
+ end
131
+
132
+ end
133
+ end
@@ -0,0 +1,3 @@
1
+ require 'spec_helpers/spec_helper_event'
2
+ require 'spec_helpers/matchers/route_matchers'
3
+ require 'spec_helpers/matchers/trigger_matchers'
@@ -0,0 +1,182 @@
1
+ require "redis/connection/synchrony"
2
+ require "redis"
3
+ require "redis/connection/ruby"
4
+
5
+ module WebsocketRails
6
+ class Synchronization
7
+
8
+ def self.all_users
9
+ singleton.all_users
10
+ end
11
+
12
+ def self.find_user(connection)
13
+ singleton.find_user connection
14
+ end
15
+
16
+ def self.register_user(connection)
17
+ singleton.register_user connection
18
+ end
19
+
20
+ def self.destroy_user(connection)
21
+ singleton.destroy_user connection
22
+ end
23
+
24
+ def self.publish(event)
25
+ singleton.publish event
26
+ end
27
+
28
+ def self.synchronize!
29
+ singleton.synchronize!
30
+ end
31
+
32
+ def self.shutdown!
33
+ singleton.shutdown!
34
+ end
35
+
36
+ def self.redis
37
+ singleton.redis
38
+ end
39
+
40
+ def self.singleton
41
+ @singleton ||= new
42
+ end
43
+
44
+ include Logging
45
+
46
+ def redis
47
+ @redis ||= begin
48
+ redis_options = WebsocketRails.config.redis_options
49
+ debug "Reactor is not running - engaging ruby redis" unless EM.reactor_running?
50
+ debug "Reactor is running - engaging standard redis new" if EM.reactor_running?
51
+ EM.reactor_running? ? Redis.new(redis_options) : ruby_redis
52
+ end
53
+ end
54
+
55
+ def ruby_redis
56
+ @ruby_redis ||= begin
57
+ WebsocketRails.config.redis_options.merge(:driver => :ruby) unless WebsocketRails.config.redis_options.has_key? :driver
58
+ redis_options = WebsocketRails.config.redis_options
59
+ Redis.new(redis_options)
60
+ end
61
+ end
62
+
63
+ def publish(event)
64
+ Fiber.new do
65
+ event.server_token = server_token
66
+ redis.publish "websocket_rails.events", event.serialize
67
+ end.resume
68
+ end
69
+
70
+ def server_token
71
+ @server_token
72
+ end
73
+
74
+ def synchronize!
75
+ unless @synchronizing
76
+ @server_token = generate_server_token
77
+ register_server(@server_token)
78
+
79
+ synchro = Fiber.new do
80
+ EM.defer do
81
+ fiber_redis = Redis.new(WebsocketRails.config.redis_options)
82
+ fiber_redis.subscribe "websocket_rails.events" do |on|
83
+ debug "Subscribed to websocket_rails events"
84
+
85
+ on.message do |_, encoded_event|
86
+ event = Event.new_from_json(encoded_event, nil)
87
+
88
+ # Do nothing if this is the server that sent this event.
89
+ next if event.server_token == server_token
90
+
91
+ # Ensure an event never gets triggered twice. Events added to the
92
+ # redis queue from other processes may not have a server token
93
+ # attached.
94
+ event.server_token = server_token if event.server_token.nil?
95
+
96
+ trigger_incoming event
97
+ end
98
+ end
99
+ end
100
+ info "Beginning Synchronization"
101
+ end
102
+ @synchronizing = true
103
+
104
+ EM.next_tick { synchro.resume }
105
+
106
+ trap('TERM') do
107
+ Thread.new { shutdown! }
108
+ end
109
+ trap('INT') do
110
+ Thread.new { shutdown! }
111
+ end
112
+ trap('QUIT') do
113
+ Thread.new { shutdown! }
114
+ end
115
+ end
116
+ end
117
+
118
+ def trigger_incoming(event)
119
+ case
120
+ when event.is_channel?
121
+ WebsocketRails[event.channel].trigger_event(event)
122
+ when event.is_user?
123
+ connection = WebsocketRails.users[event.user_id.to_s]
124
+ return if connection.nil?
125
+ connection.trigger event
126
+ end
127
+ end
128
+
129
+ def shutdown!
130
+ remove_server(server_token)
131
+ end
132
+
133
+ def generate_server_token
134
+ begin
135
+ token = SecureRandom.urlsafe_base64
136
+ end while redis.sismember("websocket_rails.active_servers", token)
137
+
138
+ token
139
+ end
140
+
141
+ def register_server(token)
142
+ Fiber.new do
143
+ redis.sadd "websocket_rails.active_servers", token
144
+ info "Server Registered: #{token}"
145
+ end.resume
146
+ end
147
+
148
+ def remove_server(token)
149
+ ruby_redis.srem "websocket_rails.active_servers", token
150
+ info "Server Removed: #{token}"
151
+ EM.stop
152
+ end
153
+
154
+ def register_user(connection)
155
+ Fiber.new do
156
+ id = connection.user_identifier
157
+ user = connection.user
158
+ redis.hset 'websocket_rails.users', id, user.as_json(root: false).to_json
159
+ end.resume
160
+ end
161
+
162
+ def destroy_user(identifier)
163
+ Fiber.new do
164
+ redis.hdel 'websocket_rails.users', identifier
165
+ end.resume
166
+ end
167
+
168
+ def find_user(identifier)
169
+ Fiber.new do
170
+ raw_user = redis.hget('websocket_rails.users', identifier)
171
+ raw_user ? JSON.parse(raw_user) : nil
172
+ end.resume
173
+ end
174
+
175
+ def all_users
176
+ Fiber.new do
177
+ redis.hgetall('websocket_rails.users')
178
+ end.resume
179
+ end
180
+
181
+ end
182
+ end