wwl-websocket-rails 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +328 -0
- data/Gemfile +27 -0
- data/MIT-LICENSE +20 -0
- data/README.md +239 -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 +46 -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 +68 -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 +138 -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 +208 -0
- data/lib/websocket_rails/channel.rb +97 -0
- data/lib/websocket_rails/channel_manager.rb +55 -0
- data/lib/websocket_rails/configuration.rb +177 -0
- data/lib/websocket_rails/connection_adapters/http.rb +120 -0
- data/lib/websocket_rails/connection_adapters/web_socket.rb +35 -0
- data/lib/websocket_rails/connection_adapters.rb +195 -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 +193 -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 +178 -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/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/config.ru +4 -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/log/test.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 +81 -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 +273 -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 +358 -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,193 @@
|
|
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
|
+
SUCCEEDED = 0
|
67
|
+
FAILED = 1
|
68
|
+
FINISHED_WITHOUT_RESULT = 2
|
69
|
+
|
70
|
+
def self.log_header
|
71
|
+
"Event"
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.new_from_json(encoded_data, connection)
|
75
|
+
case encoded_data
|
76
|
+
when String
|
77
|
+
event_name, data = JSON.parse encoded_data
|
78
|
+
|
79
|
+
unless event_name.is_a?(String) && data.is_a?(Hash)
|
80
|
+
raise UnknownDataType
|
81
|
+
end
|
82
|
+
|
83
|
+
data = data.merge(:connection => connection).with_indifferent_access
|
84
|
+
Event.new event_name, data
|
85
|
+
# when Array
|
86
|
+
# TODO: Handle file
|
87
|
+
#File.open("/tmp/test#{rand(100)}.jpg", "wb") do |file|
|
88
|
+
# encoded_data.each do |byte|
|
89
|
+
# file << byte.chr
|
90
|
+
# end
|
91
|
+
#end
|
92
|
+
else
|
93
|
+
raise UnknownDataType
|
94
|
+
end
|
95
|
+
rescue JSON::ParserError, UnknownDataType => ex
|
96
|
+
warn "Invalid Event Received: #{ex}"
|
97
|
+
debug "Event Data: #{encoded_data}"
|
98
|
+
log_exception(ex)
|
99
|
+
Event.new_on_invalid_event_received(connection, nil)
|
100
|
+
end
|
101
|
+
|
102
|
+
include Logging
|
103
|
+
extend StaticEvents
|
104
|
+
|
105
|
+
attr_reader :id, :name, :connection, :namespace, :channel, :user_id, :token
|
106
|
+
|
107
|
+
attr_accessor :data, :result, :success, :server_token, :propagate
|
108
|
+
|
109
|
+
def initialize(event_name, options={})
|
110
|
+
case event_name
|
111
|
+
when String
|
112
|
+
namespace = event_name.split('.')
|
113
|
+
@name = namespace.pop.to_sym
|
114
|
+
when Symbol
|
115
|
+
@name = event_name
|
116
|
+
namespace = [:global]
|
117
|
+
end
|
118
|
+
@id = options[:id]
|
119
|
+
@data = options[:data].is_a?(Hash) ? options[:data].with_indifferent_access : options[:data]
|
120
|
+
@channel = options[:channel].to_sym rescue options[:channel].to_s.to_sym if options[:channel]
|
121
|
+
@token = options[:token] if options[:token]
|
122
|
+
@connection = options[:connection]
|
123
|
+
@server_token = options[:server_token]
|
124
|
+
@user_id = options[:user_id]
|
125
|
+
@propagate = options[:propagate].nil? ? true : options[:propagate]
|
126
|
+
@namespace = validate_namespace( options[:namespace] || namespace )
|
127
|
+
end
|
128
|
+
|
129
|
+
def as_json
|
130
|
+
[
|
131
|
+
encoded_name,
|
132
|
+
{
|
133
|
+
:id => id,
|
134
|
+
:channel => channel,
|
135
|
+
:user_id => user_id,
|
136
|
+
:data => data,
|
137
|
+
:success => success,
|
138
|
+
:result => result,
|
139
|
+
:token => token,
|
140
|
+
:server_token => server_token
|
141
|
+
}
|
142
|
+
]
|
143
|
+
end
|
144
|
+
|
145
|
+
def serialize
|
146
|
+
as_json.to_json
|
147
|
+
end
|
148
|
+
|
149
|
+
def is_channel?
|
150
|
+
!@channel.nil?
|
151
|
+
end
|
152
|
+
|
153
|
+
def is_user?
|
154
|
+
!@user_id.nil? && !is_channel?
|
155
|
+
end
|
156
|
+
|
157
|
+
def is_invalid?
|
158
|
+
name == :invalid_event
|
159
|
+
end
|
160
|
+
|
161
|
+
def is_internal?
|
162
|
+
namespace.include?(:websocket_rails)
|
163
|
+
end
|
164
|
+
|
165
|
+
def should_propagate?
|
166
|
+
@propagate
|
167
|
+
end
|
168
|
+
|
169
|
+
def trigger
|
170
|
+
connection.trigger self if connection
|
171
|
+
end
|
172
|
+
|
173
|
+
def encoded_name
|
174
|
+
if namespace.size > 1
|
175
|
+
child_namespace = namespace.dup[1..-1]
|
176
|
+
child_namespace << name
|
177
|
+
combined_name = child_namespace.join('.')
|
178
|
+
else
|
179
|
+
combined_name = name
|
180
|
+
end
|
181
|
+
combined_name
|
182
|
+
end
|
183
|
+
|
184
|
+
private
|
185
|
+
|
186
|
+
def validate_namespace(namespace)
|
187
|
+
namespace = [namespace] unless namespace.is_a?(Array)
|
188
|
+
namespace.unshift :global unless namespace.first == :global
|
189
|
+
namespace.map(&:to_sym) rescue [:global]
|
190
|
+
end
|
191
|
+
|
192
|
+
end
|
193
|
+
end
|