hkroger-websocket-rails 0.7.1
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +328 -0
- data/Gemfile +27 -0
- data/MIT-LICENSE +20 -0
- data/README.md +237 -0
- data/Rakefile +72 -0
- data/bin/thin-socketrails +45 -0
- data/lib/assets/javascripts/websocket_rails/abstract_connection.js.coffee +45 -0
- data/lib/assets/javascripts/websocket_rails/channel.js.coffee +70 -0
- data/lib/assets/javascripts/websocket_rails/event.js.coffee +42 -0
- data/lib/assets/javascripts/websocket_rails/http_connection.js.coffee +66 -0
- data/lib/assets/javascripts/websocket_rails/main.js +6 -0
- data/lib/assets/javascripts/websocket_rails/websocket_connection.js.coffee +29 -0
- data/lib/assets/javascripts/websocket_rails/websocket_rails.js.coffee +158 -0
- data/lib/config.ru +3 -0
- data/lib/generators/websocket_rails/install/install_generator.rb +33 -0
- data/lib/generators/websocket_rails/install/templates/events.rb +14 -0
- data/lib/generators/websocket_rails/install/templates/websocket_rails.rb +63 -0
- data/lib/hkroger-websocket-rails.rb +1 -0
- data/lib/rails/app/controllers/websocket_rails/delegation_controller.rb +13 -0
- data/lib/rails/config/routes.rb +7 -0
- data/lib/rails/tasks/websocket_rails.tasks +42 -0
- data/lib/spec_helpers/matchers/route_matchers.rb +65 -0
- data/lib/spec_helpers/matchers/trigger_matchers.rb +113 -0
- data/lib/spec_helpers/spec_helper_event.rb +34 -0
- data/lib/websocket-rails.rb +108 -0
- data/lib/websocket_rails/base_controller.rb +197 -0
- data/lib/websocket_rails/channel.rb +97 -0
- data/lib/websocket_rails/channel_manager.rb +55 -0
- data/lib/websocket_rails/configuration.rb +169 -0
- data/lib/websocket_rails/connection_adapters.rb +195 -0
- data/lib/websocket_rails/connection_adapters/http.rb +120 -0
- data/lib/websocket_rails/connection_adapters/web_socket.rb +36 -0
- data/lib/websocket_rails/connection_manager.rb +119 -0
- data/lib/websocket_rails/controller_factory.rb +80 -0
- data/lib/websocket_rails/data_store.rb +145 -0
- data/lib/websocket_rails/dispatcher.rb +129 -0
- data/lib/websocket_rails/engine.rb +26 -0
- data/lib/websocket_rails/event.rb +189 -0
- data/lib/websocket_rails/event_map.rb +184 -0
- data/lib/websocket_rails/event_queue.rb +33 -0
- data/lib/websocket_rails/internal_events.rb +37 -0
- data/lib/websocket_rails/logging.rb +133 -0
- data/lib/websocket_rails/spec_helpers.rb +3 -0
- data/lib/websocket_rails/synchronization.rb +182 -0
- data/lib/websocket_rails/user_manager.rb +276 -0
- data/lib/websocket_rails/version.rb +3 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/controllers/chat_controller.rb +53 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/models/user.rb +2 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +45 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +22 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +26 -0
- data/spec/dummy/config/environments/production.rb +49 -0
- data/spec/dummy/config/environments/test.rb +34 -0
- data/spec/dummy/config/events.rb +7 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +10 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +58 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/migrate/20130902222552_create_users.rb +10 -0
- data/spec/dummy/db/schema.rb +23 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +17 -0
- data/spec/dummy/log/production.log +0 -0
- data/spec/dummy/log/server.log +0 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +26 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/public/javascripts/application.js +2 -0
- data/spec/dummy/public/javascripts/controls.js +965 -0
- data/spec/dummy/public/javascripts/dragdrop.js +974 -0
- data/spec/dummy/public/javascripts/effects.js +1123 -0
- data/spec/dummy/public/javascripts/prototype.js +6001 -0
- data/spec/dummy/public/javascripts/rails.js +202 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/integration/connection_manager_spec.rb +135 -0
- data/spec/javascripts/support/jasmine.yml +52 -0
- data/spec/javascripts/support/jasmine_helper.rb +38 -0
- data/spec/javascripts/support/vendor/sinon-1.7.1.js +4343 -0
- data/spec/javascripts/websocket_rails/channel_spec.coffee +112 -0
- data/spec/javascripts/websocket_rails/event_spec.coffee +69 -0
- data/spec/javascripts/websocket_rails/helpers.coffee +6 -0
- data/spec/javascripts/websocket_rails/websocket_connection_spec.coffee +158 -0
- data/spec/javascripts/websocket_rails/websocket_rails_spec.coffee +274 -0
- data/spec/spec_helper.rb +41 -0
- data/spec/spec_helpers/matchers/route_matchers_spec.rb +109 -0
- data/spec/spec_helpers/matchers/trigger_matchers_spec.rb +247 -0
- data/spec/spec_helpers/spec_helper_event_spec.rb +66 -0
- data/spec/support/helper_methods.rb +42 -0
- data/spec/support/mock_web_socket.rb +41 -0
- data/spec/unit/base_controller_spec.rb +74 -0
- data/spec/unit/channel_manager_spec.rb +58 -0
- data/spec/unit/channel_spec.rb +169 -0
- data/spec/unit/connection_adapters/http_spec.rb +88 -0
- data/spec/unit/connection_adapters/web_socket_spec.rb +30 -0
- data/spec/unit/connection_adapters_spec.rb +259 -0
- data/spec/unit/connection_manager_spec.rb +148 -0
- data/spec/unit/controller_factory_spec.rb +76 -0
- data/spec/unit/data_store_spec.rb +106 -0
- data/spec/unit/dispatcher_spec.rb +203 -0
- data/spec/unit/event_map_spec.rb +120 -0
- data/spec/unit/event_queue_spec.rb +36 -0
- data/spec/unit/event_spec.rb +181 -0
- data/spec/unit/logging_spec.rb +162 -0
- data/spec/unit/synchronization_spec.rb +150 -0
- data/spec/unit/target_validator_spec.rb +88 -0
- data/spec/unit/user_manager_spec.rb +165 -0
- metadata +320 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
module WebsocketRails
|
|
2
|
+
class ControllerFactory
|
|
3
|
+
|
|
4
|
+
attr_reader :controller_stores, :dispatcher
|
|
5
|
+
|
|
6
|
+
def initialize(dispatcher)
|
|
7
|
+
@dispatcher = dispatcher
|
|
8
|
+
@controller_stores = {}
|
|
9
|
+
@initialized_controllers = {}
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# TODO: Add deprecation notice for user defined
|
|
13
|
+
# instance variables.
|
|
14
|
+
def new_for_event(event, controller_class, method)
|
|
15
|
+
controller_class = reload!(controller_class)
|
|
16
|
+
controller = controller_class.new
|
|
17
|
+
|
|
18
|
+
prepare(controller, event, method)
|
|
19
|
+
|
|
20
|
+
controller
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def store_for_controller(controller)
|
|
26
|
+
@controller_stores[controller.class] ||= DataStore::Controller.new(controller)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def prepare(controller, event, method)
|
|
30
|
+
set_event(controller, event)
|
|
31
|
+
set_dispatcher(controller, dispatcher)
|
|
32
|
+
set_controller_store(controller)
|
|
33
|
+
set_action_name(controller, method)
|
|
34
|
+
initialize_controller(controller)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def set_event(controller, event)
|
|
38
|
+
set_ivar :@_event, controller, event
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def set_dispatcher(controller, dispatcher)
|
|
42
|
+
set_ivar :@_dispatcher, controller, dispatcher
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def set_controller_store(controller)
|
|
46
|
+
set_ivar :@_controller_store, controller, store_for_controller(controller)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def set_action_name(controller, method)
|
|
50
|
+
set_ivar :@_action_name, controller, method.to_s
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def set_ivar(ivar, object, value)
|
|
54
|
+
object.instance_variable_set(ivar, value)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def initialize_controller(controller)
|
|
58
|
+
unless @initialized_controllers[controller.class] == true
|
|
59
|
+
controller.send(:initialize_session) if controller.respond_to?(:initialize_session)
|
|
60
|
+
@initialized_controllers[controller.class] = true
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Reloads the controller class to pick up code changes
|
|
65
|
+
# while in the development environment.
|
|
66
|
+
def reload!(controller)
|
|
67
|
+
return controller unless defined?(Rails) and !Rails.configuration.cache_classes
|
|
68
|
+
# we don't reload our own controller as we assume it provide as 'library'
|
|
69
|
+
unless controller.name == "WebsocketRails::InternalController"
|
|
70
|
+
class_name = controller.name
|
|
71
|
+
filename = class_name.underscore
|
|
72
|
+
load "#{filename}.rb"
|
|
73
|
+
return class_name.constantize
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
return controller
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
module WebsocketRails
|
|
2
|
+
# The {DataStore} provides a convenient way to persist information between
|
|
3
|
+
# execution of events. Since every event is executed within a new instance
|
|
4
|
+
# of the controller class, instance variables set while processing an
|
|
5
|
+
# action will be lost after the action finishes executing.
|
|
6
|
+
#
|
|
7
|
+
# There are two different {DataStore} classes that you can use:
|
|
8
|
+
#
|
|
9
|
+
# The {DataStore::Connection} class is unique for every active connection.
|
|
10
|
+
# You can use it similar to the Rails session store. The connection data
|
|
11
|
+
# store can be accessed within your controller using the `#connection_store`
|
|
12
|
+
# method.
|
|
13
|
+
#
|
|
14
|
+
# The {DataStore::Controller} class is unique for every controller. You
|
|
15
|
+
# can use it similar to how you would use instance variables within a
|
|
16
|
+
# plain ruby class. The values set within the controller store will be
|
|
17
|
+
# persisted between events. The controller store can be accessed within
|
|
18
|
+
# your controller using the `#controller_store` method.
|
|
19
|
+
module DataStore
|
|
20
|
+
class Base < ActiveSupport::HashWithIndifferentAccess
|
|
21
|
+
|
|
22
|
+
cattr_accessor :all_instances
|
|
23
|
+
@@all_instances = Hash.new { |h,k| h[k] = [] }
|
|
24
|
+
|
|
25
|
+
def self.clear_all_instances
|
|
26
|
+
@@all_instances = Hash.new { |h,k| h[k] = [] }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def initialize
|
|
30
|
+
instances << self
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def instances
|
|
34
|
+
all_instances[self.class]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def collect_all(key)
|
|
38
|
+
collection = instances.each_with_object([]) do |instance, array|
|
|
39
|
+
array << instance[key]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
if block_given?
|
|
43
|
+
collection.each do |item|
|
|
44
|
+
yield(item)
|
|
45
|
+
end
|
|
46
|
+
else
|
|
47
|
+
collection
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def destroy!
|
|
52
|
+
instances.delete_if {|store| store.object_id == self.object_id }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# The connection data store operates much like the {Controller} store. The
|
|
58
|
+
# biggest difference is that the data placed inside is private for
|
|
59
|
+
# individual users and accessible from any controller. Anything placed
|
|
60
|
+
# inside the connection data store will be deleted when a user disconnects.
|
|
61
|
+
#
|
|
62
|
+
# The connection data store is accessed through the `#connection_store`
|
|
63
|
+
# instance method inside your controller.
|
|
64
|
+
#
|
|
65
|
+
# If we were writing a basic chat system, we could use the connection data
|
|
66
|
+
# store to hold onto a user's current screen name.
|
|
67
|
+
#
|
|
68
|
+
#
|
|
69
|
+
# class UserController < WebsocketRails::BaseController
|
|
70
|
+
#
|
|
71
|
+
# def set_screen_name
|
|
72
|
+
# connection_store[:screen_name] = message[:screen_name]
|
|
73
|
+
# end
|
|
74
|
+
#
|
|
75
|
+
# end
|
|
76
|
+
#
|
|
77
|
+
# class ChatController < WebsocketRails::BaseController
|
|
78
|
+
#
|
|
79
|
+
# def say_hello
|
|
80
|
+
# screen_name = connection_store[:screen_name]
|
|
81
|
+
# send_message :new_message, "#{screen_name} says hello"
|
|
82
|
+
# end
|
|
83
|
+
#
|
|
84
|
+
# end
|
|
85
|
+
class Connection < Base
|
|
86
|
+
|
|
87
|
+
attr_accessor :connection
|
|
88
|
+
|
|
89
|
+
def initialize(connection)
|
|
90
|
+
super()
|
|
91
|
+
@connection = connection
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# The Controller DataStore acts as a stand-in for instance variables in your
|
|
97
|
+
# controller. At it's core, it is a Hash which is accessible inside your
|
|
98
|
+
# controller through the `#controller_store` instance method. Any values
|
|
99
|
+
# set in the controller store will be visible by all connected users which
|
|
100
|
+
# trigger events that use that controller. However, values set in one
|
|
101
|
+
# controller will not be visible by other controllers.
|
|
102
|
+
#
|
|
103
|
+
#
|
|
104
|
+
# class AccountController < WebsocketRails::BaseController
|
|
105
|
+
# # We will use a before filter to set the initial value
|
|
106
|
+
# before_action { controller_store[:event_count] ||= 0 }
|
|
107
|
+
#
|
|
108
|
+
# # Mapped as `accounts.important_event` in the Event Router
|
|
109
|
+
# def important_event
|
|
110
|
+
# # This will be private for each controller
|
|
111
|
+
# controller_store[:event_count] += 1
|
|
112
|
+
# trigger_success controller_store[:event_count]
|
|
113
|
+
# end
|
|
114
|
+
# end
|
|
115
|
+
#
|
|
116
|
+
# class ProductController < WebsocketRails::BaseController
|
|
117
|
+
# # We will use a before filter to set the initial value
|
|
118
|
+
# before_action { controller_store[:event_count] ||= 0 }
|
|
119
|
+
#
|
|
120
|
+
# # Mapped as `products.boring_event` in the Event Router
|
|
121
|
+
# def boring_event
|
|
122
|
+
# # This will be private for each controller
|
|
123
|
+
# controller_store[:event_count] += 1
|
|
124
|
+
# trigger_success controller_store[:event_count]
|
|
125
|
+
# end
|
|
126
|
+
# end
|
|
127
|
+
#
|
|
128
|
+
# # trigger `accounts.important_event`
|
|
129
|
+
# => 1
|
|
130
|
+
# # trigger `accounts.important_event`
|
|
131
|
+
# => 2
|
|
132
|
+
# # trigger `products.boring_event`
|
|
133
|
+
# => 1
|
|
134
|
+
class Controller < Base
|
|
135
|
+
|
|
136
|
+
attr_accessor :controller
|
|
137
|
+
|
|
138
|
+
def initialize(controller)
|
|
139
|
+
super()
|
|
140
|
+
@controller = controller
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
module WebsocketRails
|
|
2
|
+
class Dispatcher
|
|
3
|
+
|
|
4
|
+
include Logging
|
|
5
|
+
|
|
6
|
+
attr_reader :event_map, :connection_manager, :controller_factory
|
|
7
|
+
|
|
8
|
+
delegate :filtered_channels, to: WebsocketRails
|
|
9
|
+
|
|
10
|
+
def initialize(connection_manager)
|
|
11
|
+
@connection_manager = connection_manager
|
|
12
|
+
@controller_factory = ControllerFactory.new(self)
|
|
13
|
+
@event_map = EventMap.new(self)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def receive_encoded(encoded_data,connection)
|
|
17
|
+
event = Event.new_from_json( encoded_data, connection )
|
|
18
|
+
dispatch( event )
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def receive(event_name,data,connection)
|
|
22
|
+
event = Event.new event_name, data, connection
|
|
23
|
+
dispatch( event )
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def dispatch(event)
|
|
27
|
+
return if event.is_invalid?
|
|
28
|
+
|
|
29
|
+
if event.is_channel?
|
|
30
|
+
filter_channel(event)
|
|
31
|
+
else
|
|
32
|
+
reload_event_map! unless event.is_internal?
|
|
33
|
+
route event
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def send_message(event)
|
|
38
|
+
event.connection.trigger event
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def broadcast_message(event)
|
|
42
|
+
connection_manager.connections.map do |_, connection|
|
|
43
|
+
connection.trigger event
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def reload_event_map!
|
|
48
|
+
return unless defined?(Rails) and !Rails.configuration.cache_classes
|
|
49
|
+
begin
|
|
50
|
+
load "#{Rails.root}/config/events.rb"
|
|
51
|
+
@event_map = EventMap.new(self)
|
|
52
|
+
rescue Exception => ex
|
|
53
|
+
log(:warn, "EventMap reload failed: #{ex.message}")
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def route(event)
|
|
60
|
+
actions = []
|
|
61
|
+
event_map.routes_for event do |controller_class, method|
|
|
62
|
+
actions << Fiber.new do
|
|
63
|
+
begin
|
|
64
|
+
log_event(event) do
|
|
65
|
+
controller = controller_factory.new_for_event(event, controller_class, method)
|
|
66
|
+
|
|
67
|
+
controller.process_action(method, event)
|
|
68
|
+
end
|
|
69
|
+
rescue Exception => ex
|
|
70
|
+
event.success = false
|
|
71
|
+
event.data = extract_exception_data ex
|
|
72
|
+
event.trigger
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
execute actions
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def filter_channel(event)
|
|
80
|
+
actions = []
|
|
81
|
+
actions << Fiber.new do
|
|
82
|
+
begin
|
|
83
|
+
log_event(event) do
|
|
84
|
+
controller_class, catch_all = filtered_channels[event.channel]
|
|
85
|
+
|
|
86
|
+
controller = controller_factory.new_for_event(event, controller_class, event.name)
|
|
87
|
+
# send to the method of the event name
|
|
88
|
+
# silently skip routing to the controller on event.name if it doesnt respond
|
|
89
|
+
controller.process_action(event.name, event) if controller.respond_to?(event.name)
|
|
90
|
+
# send to our defined catch all method
|
|
91
|
+
controller.process_action(catch_all, event) if catch_all
|
|
92
|
+
|
|
93
|
+
end
|
|
94
|
+
rescue Exception => ex
|
|
95
|
+
event.success = false
|
|
96
|
+
event.data = extract_exception_data ex
|
|
97
|
+
event.trigger
|
|
98
|
+
end
|
|
99
|
+
end if filtered_channels[event.channel]
|
|
100
|
+
|
|
101
|
+
actions << Fiber.new{ WebsocketRails[event.channel].trigger_event(event) }
|
|
102
|
+
execute actions
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def execute(actions)
|
|
106
|
+
actions.map do |action|
|
|
107
|
+
EM.next_tick { action.resume }
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def extract_exception_data(ex)
|
|
112
|
+
if record_invalid_defined? and ex.is_a? ActiveRecord::RecordInvalid
|
|
113
|
+
{
|
|
114
|
+
:record => ex.record.attributes,
|
|
115
|
+
:errors => ex.record.errors,
|
|
116
|
+
:full_messages => ex.record.errors.full_messages
|
|
117
|
+
}
|
|
118
|
+
else
|
|
119
|
+
ex if ex.respond_to?(:to_json)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def record_invalid_defined?
|
|
124
|
+
Object.const_defined?('ActiveRecord') and ActiveRecord.const_defined?('RecordInvalid')
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
end
|
|
129
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module WebsocketRails
|
|
2
|
+
|
|
3
|
+
class Engine < Rails::Engine
|
|
4
|
+
|
|
5
|
+
config.autoload_paths += [File.expand_path("../../lib", __FILE__)]
|
|
6
|
+
|
|
7
|
+
paths["app"] << "lib/rails/app"
|
|
8
|
+
paths["app/controllers"] << "lib/rails/app/controllers"
|
|
9
|
+
|
|
10
|
+
if ::Rails.version >= '4.0.0'
|
|
11
|
+
paths["config/routes.rb"] << "lib/rails/config/routes.rb"
|
|
12
|
+
else
|
|
13
|
+
paths["config/routes"] << "lib/rails/config/routes.rb"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
initializer 'websocket_rails.load_event_routes', :before => :preload_frameworks do |app|
|
|
17
|
+
load "#{Rails.root}/config/events.rb" if File.exists?("#{Rails.root}/config/events.rb")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
rake_tasks do
|
|
21
|
+
require 'websocket-rails'
|
|
22
|
+
load 'rails/tasks/websocket_rails.tasks'
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
module WebsocketRails
|
|
2
|
+
|
|
3
|
+
module StaticEvents
|
|
4
|
+
|
|
5
|
+
def new_on_open(connection,data=nil)
|
|
6
|
+
connection_id = {
|
|
7
|
+
:connection_id => connection.id
|
|
8
|
+
}
|
|
9
|
+
data = data.is_a?(Hash) ? data.merge( connection_id ) : connection_id
|
|
10
|
+
Event.new :client_connected, :data => data, :connection => connection
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def new_on_close(connection,data=nil)
|
|
14
|
+
Event.new :client_disconnected, :data => data, :connection => connection
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def new_on_error(connection,data=nil)
|
|
18
|
+
Event.new :client_error, :data => data, :connection => connection
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def new_on_ping(connection)
|
|
22
|
+
Event.new :ping, :data => {}, :connection => connection, :namespace => :websocket_rails
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def new_on_invalid_event_received(connection,data=nil)
|
|
26
|
+
Event.new :invalid_event, :data => data, :connection => connection
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Contains all of the relevant information for incoming and outgoing events.
|
|
32
|
+
# All events except for channel events will have a connection object associated.
|
|
33
|
+
#
|
|
34
|
+
# Events require an event name and hash of options:
|
|
35
|
+
#
|
|
36
|
+
# :data =>
|
|
37
|
+
# The data object will be passed to any callback functions bound on the
|
|
38
|
+
# client side.
|
|
39
|
+
#
|
|
40
|
+
# You can also pass a Hash of options to specify:
|
|
41
|
+
#
|
|
42
|
+
# :connection =>
|
|
43
|
+
# Connection that will be receiving or that sent this event.
|
|
44
|
+
#
|
|
45
|
+
# :namespace =>
|
|
46
|
+
# The namespace this event is under. Will default to :global
|
|
47
|
+
# If the namespace is nested under multiple levels pass them as an array.
|
|
48
|
+
# For instance, if the namespace route looks like the following:
|
|
49
|
+
#
|
|
50
|
+
# namespace :products do
|
|
51
|
+
# namespace :hats do
|
|
52
|
+
# # events
|
|
53
|
+
# end
|
|
54
|
+
# end
|
|
55
|
+
#
|
|
56
|
+
# Then you would pass the namespace argument as [:products,:hats]
|
|
57
|
+
#
|
|
58
|
+
# :channel =>
|
|
59
|
+
# The name of the channel that this event is destined for.
|
|
60
|
+
class Event
|
|
61
|
+
|
|
62
|
+
class UnknownDataType < StandardError; end;
|
|
63
|
+
|
|
64
|
+
extend Logging
|
|
65
|
+
|
|
66
|
+
def self.log_header
|
|
67
|
+
"Event"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def self.new_from_json(encoded_data, connection)
|
|
71
|
+
case encoded_data
|
|
72
|
+
when String
|
|
73
|
+
event_name, data = JSON.parse encoded_data
|
|
74
|
+
|
|
75
|
+
unless event_name.is_a?(String) && data.is_a?(Hash)
|
|
76
|
+
raise UnknownDataType
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
data = data.merge(:connection => connection).with_indifferent_access
|
|
80
|
+
Event.new event_name, data
|
|
81
|
+
# when Array
|
|
82
|
+
# TODO: Handle file
|
|
83
|
+
#File.open("/tmp/test#{rand(100)}.jpg", "wb") do |file|
|
|
84
|
+
# encoded_data.each do |byte|
|
|
85
|
+
# file << byte.chr
|
|
86
|
+
# end
|
|
87
|
+
#end
|
|
88
|
+
else
|
|
89
|
+
raise UnknownDataType
|
|
90
|
+
end
|
|
91
|
+
rescue JSON::ParserError, UnknownDataType => ex
|
|
92
|
+
warn "Invalid Event Received: #{ex}"
|
|
93
|
+
debug "Event Data: #{encoded_data}"
|
|
94
|
+
log_exception(ex)
|
|
95
|
+
Event.new_on_invalid_event_received(connection, nil)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
include Logging
|
|
99
|
+
extend StaticEvents
|
|
100
|
+
|
|
101
|
+
attr_reader :id, :name, :connection, :namespace, :channel, :user_id, :token
|
|
102
|
+
|
|
103
|
+
attr_accessor :data, :result, :success, :server_token, :propagate
|
|
104
|
+
|
|
105
|
+
def initialize(event_name, options={})
|
|
106
|
+
case event_name
|
|
107
|
+
when String
|
|
108
|
+
namespace = event_name.split('.')
|
|
109
|
+
@name = namespace.pop.to_sym
|
|
110
|
+
when Symbol
|
|
111
|
+
@name = event_name
|
|
112
|
+
namespace = [:global]
|
|
113
|
+
end
|
|
114
|
+
@id = options[:id]
|
|
115
|
+
@data = options[:data].is_a?(Hash) ? options[:data].with_indifferent_access : options[:data]
|
|
116
|
+
@channel = options[:channel].to_sym rescue options[:channel].to_s.to_sym if options[:channel]
|
|
117
|
+
@token = options[:token] if options[:token]
|
|
118
|
+
@connection = options[:connection]
|
|
119
|
+
@server_token = options[:server_token]
|
|
120
|
+
@user_id = options[:user_id]
|
|
121
|
+
@propagate = options[:propagate].nil? ? true : options[:propagate]
|
|
122
|
+
@namespace = validate_namespace( options[:namespace] || namespace )
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def as_json
|
|
126
|
+
[
|
|
127
|
+
encoded_name,
|
|
128
|
+
{
|
|
129
|
+
:id => id,
|
|
130
|
+
:channel => channel,
|
|
131
|
+
:user_id => user_id,
|
|
132
|
+
:data => data,
|
|
133
|
+
:success => success,
|
|
134
|
+
:result => result,
|
|
135
|
+
:token => token,
|
|
136
|
+
:server_token => server_token
|
|
137
|
+
}
|
|
138
|
+
]
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def serialize
|
|
142
|
+
as_json.to_json
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def is_channel?
|
|
146
|
+
!@channel.nil?
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def is_user?
|
|
150
|
+
!@user_id.nil? && !is_channel?
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def is_invalid?
|
|
154
|
+
name == :invalid_event
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def is_internal?
|
|
158
|
+
namespace.include?(:websocket_rails)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def should_propagate?
|
|
162
|
+
@propagate
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def trigger
|
|
166
|
+
connection.trigger self if connection
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def encoded_name
|
|
170
|
+
if namespace.size > 1
|
|
171
|
+
child_namespace = namespace.dup[1..-1]
|
|
172
|
+
child_namespace << name
|
|
173
|
+
combined_name = child_namespace.join('.')
|
|
174
|
+
else
|
|
175
|
+
combined_name = name
|
|
176
|
+
end
|
|
177
|
+
combined_name
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
private
|
|
181
|
+
|
|
182
|
+
def validate_namespace(namespace)
|
|
183
|
+
namespace = [namespace] unless namespace.is_a?(Array)
|
|
184
|
+
namespace.unshift :global unless namespace.first == :global
|
|
185
|
+
namespace.map(&:to_sym) rescue [:global]
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
end
|
|
189
|
+
end
|