websocket-rails 0.1.5 → 0.1.6
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +22 -0
- data/Gemfile +4 -0
- data/README.md +66 -159
- data/Rakefile +31 -4
- data/bin/thin-socketrails +16 -1
- data/lib/assets/javascripts/websocket_rails/channel.js.coffee +23 -8
- data/lib/assets/javascripts/websocket_rails/event.js.coffee +40 -0
- data/lib/assets/javascripts/websocket_rails/http_connection.js.coffee +18 -10
- data/lib/assets/javascripts/websocket_rails/main.js +1 -0
- data/lib/assets/javascripts/websocket_rails/websocket_connection.js.coffee +15 -10
- data/lib/assets/javascripts/websocket_rails/websocket_rails.js.coffee +41 -23
- data/lib/websocket-rails.rb +4 -4
- data/lib/websocket_rails/base_controller.rb +61 -29
- data/lib/websocket_rails/channel.rb +14 -5
- data/lib/websocket_rails/channel_manager.rb +3 -1
- data/lib/websocket_rails/connection_adapters.rb +34 -12
- data/lib/websocket_rails/connection_manager.rb +4 -0
- data/lib/websocket_rails/dispatcher.rb +27 -3
- data/lib/websocket_rails/engine.rb +2 -5
- data/lib/websocket_rails/event.rb +87 -42
- data/lib/websocket_rails/event_map.rb +70 -20
- data/lib/websocket_rails/event_queue.rb +4 -0
- data/lib/websocket_rails/internal_events.rb +21 -3
- data/lib/websocket_rails/logging.rb +18 -0
- data/lib/websocket_rails/version.rb +1 -1
- data/spec/dummy/log/test.log +0 -429
- data/spec/integration/connection_manager_spec.rb +3 -5
- data/spec/javascripts/generated/assets/channel.js +98 -0
- data/spec/javascripts/generated/assets/event.js +78 -0
- data/spec/javascripts/generated/assets/http_connection.js +108 -0
- data/spec/javascripts/generated/assets/websocket_connection.js +66 -0
- data/spec/javascripts/generated/assets/websocket_rails.js +180 -0
- data/spec/javascripts/generated/specs/channel_spec.js +66 -0
- data/spec/javascripts/generated/specs/event_spec.js +107 -0
- data/spec/javascripts/generated/specs/websocket_connection_spec.js +117 -0
- data/spec/javascripts/generated/specs/websocket_rails_spec.js +232 -0
- data/spec/javascripts/support/jasmine.yml +44 -0
- data/spec/javascripts/support/jasmine_config.rb +63 -0
- data/spec/javascripts/support/vendor/sinon-1.3.4.js +3555 -0
- data/spec/javascripts/websocket_rails/channel_spec.coffee +51 -0
- data/spec/javascripts/websocket_rails/event_spec.coffee +69 -0
- data/spec/javascripts/websocket_rails/websocket_connection_spec.coffee +86 -0
- data/spec/javascripts/websocket_rails/websocket_rails_spec.coffee +166 -0
- data/spec/support/helper_methods.rb +10 -1
- data/spec/unit/channel_spec.rb +28 -4
- data/spec/unit/connection_adapters_spec.rb +17 -0
- data/spec/unit/connection_manager_spec.rb +1 -1
- data/spec/unit/dispatcher_spec.rb +1 -1
- data/spec/unit/event_spec.rb +15 -11
- metadata +22 -4
@@ -1,53 +1,112 @@
|
|
1
|
-
require 'json'
|
2
|
-
|
3
1
|
module WebsocketRails
|
4
|
-
class Event
|
5
2
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
:
|
11
|
-
|
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
|
12
11
|
end
|
13
12
|
|
14
|
-
def
|
15
|
-
|
16
|
-
|
13
|
+
def new_on_close(connection,data=nil)
|
14
|
+
Event.new :client_disconnected, :data => data, :connection => connection
|
15
|
+
end
|
17
16
|
|
18
|
-
|
17
|
+
def new_on_error(connection,data=nil)
|
18
|
+
Event.new :client_error, :data => data, :connection => connection
|
19
19
|
end
|
20
20
|
|
21
|
-
def
|
22
|
-
Event.new :
|
21
|
+
def new_on_ping(connection)
|
22
|
+
Event.new :ping, :data => {}, :connection => connection, :namespace => :websocket_rails
|
23
23
|
end
|
24
24
|
|
25
|
-
|
26
|
-
|
25
|
+
end
|
26
|
+
|
27
|
+
# Contains all of the relevant information for incoming and outgoing events.
|
28
|
+
# All events except for channel events will have a connection object associated.
|
29
|
+
#
|
30
|
+
# Events require an event name and hash of options:
|
31
|
+
#
|
32
|
+
# :data =>
|
33
|
+
# The data object will be passed to any callback functions bound on the
|
34
|
+
# client side.
|
35
|
+
#
|
36
|
+
# You can also pass a Hash of options to specify:
|
37
|
+
#
|
38
|
+
# :connection =>
|
39
|
+
# Connection that will be receiving or that sent this event.
|
40
|
+
#
|
41
|
+
# :namespace =>
|
42
|
+
# The namespace this event is under. Will default to :global
|
43
|
+
# If the namespace is nested under multiple levels pass them as an array.
|
44
|
+
# For instance, if the namespace route looks like the following:
|
45
|
+
#
|
46
|
+
# namespace :products do
|
47
|
+
# namespace :hats do
|
48
|
+
# # events
|
49
|
+
# end
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# Then you would pass the namespace argument as [:products,:hats]
|
53
|
+
#
|
54
|
+
# :channel =>
|
55
|
+
# The name of the channel that this event is destined for.
|
56
|
+
class Event
|
57
|
+
|
58
|
+
def self.new_from_json(encoded_data,connection)
|
59
|
+
event_name, data = JSON.parse encoded_data
|
60
|
+
data = data.merge(:connection => connection).with_indifferent_access
|
61
|
+
Event.new event_name, data
|
62
|
+
rescue JSON::ParserError => ex
|
63
|
+
warn "Invalid Event Received: #{ex}"
|
27
64
|
end
|
28
65
|
|
29
|
-
|
66
|
+
include Logging
|
67
|
+
extend StaticEvents
|
68
|
+
|
69
|
+
attr_reader :id, :name, :connection, :namespace, :channel
|
30
70
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
71
|
+
attr_accessor :data, :result, :success
|
72
|
+
|
73
|
+
def initialize(event_name,options={})
|
74
|
+
case event_name
|
75
|
+
when String
|
76
|
+
namespace = event_name.split('.')
|
77
|
+
@name = namespace.pop.to_sym
|
78
|
+
when Symbol
|
79
|
+
@name = event_name
|
80
|
+
namespace = [:global]
|
81
|
+
end
|
82
|
+
@id = options[:id]
|
83
|
+
@data = options[:data].is_a?(Hash) ? options[:data].with_indifferent_access : options[:data]
|
84
|
+
@channel = options[:channel].to_sym if options[:channel]
|
35
85
|
@connection = options[:connection]
|
36
|
-
@namespace = validate_namespace options[:namespace]
|
86
|
+
@namespace = validate_namespace( options[:namespace] || namespace )
|
37
87
|
end
|
38
88
|
|
39
89
|
def serialize
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
90
|
+
[
|
91
|
+
encoded_name,
|
92
|
+
{
|
93
|
+
:id => id,
|
94
|
+
:channel => channel,
|
95
|
+
:data => data,
|
96
|
+
:success => success,
|
97
|
+
:result => result
|
98
|
+
}
|
99
|
+
].to_json
|
45
100
|
end
|
46
101
|
|
47
102
|
def is_channel?
|
48
103
|
!@channel.nil?
|
49
104
|
end
|
50
105
|
|
106
|
+
def trigger
|
107
|
+
connection.trigger self if connection
|
108
|
+
end
|
109
|
+
|
51
110
|
private
|
52
111
|
|
53
112
|
def validate_namespace(namespace)
|
@@ -67,19 +126,5 @@ module WebsocketRails
|
|
67
126
|
combined_name
|
68
127
|
end
|
69
128
|
|
70
|
-
def self.decode(encoded_data)
|
71
|
-
message = JSON.parse( encoded_data )
|
72
|
-
|
73
|
-
channel_name = message.shift if message.size == 3
|
74
|
-
event_name = message[0]
|
75
|
-
data = message[1]
|
76
|
-
|
77
|
-
namespace = event_name.split('.')
|
78
|
-
event_name = namespace.pop
|
79
|
-
|
80
|
-
data['received'] = Time.now if data.is_a?(Hash)
|
81
|
-
[event_name, data, namespace, channel_name]
|
82
|
-
end
|
83
|
-
|
84
129
|
end
|
85
130
|
end
|
@@ -1,26 +1,21 @@
|
|
1
1
|
module WebsocketRails
|
2
|
-
# Provides a DSL for mapping client events to controller actions.
|
3
|
-
# number of controllers and actions. You can define your event routes by creating an +events.rb+ file in
|
4
|
-
# your application's +initializers+ directory. The DSL currently consists of two methods. The first is
|
5
|
-
# {#subscribe}, which takes a symbolized event name as the first argument, and a Hash with the controller
|
6
|
-
# and method name as the second argument. The second is {#namespace} which allows you to scope your
|
7
|
-
# actions within particular namespaces. The {#namespace} method takes a symbol representing the name
|
8
|
-
# of the namespace, and a block which contains the actions you wish to subscribe within that namespace.
|
9
|
-
# When an event is dispatched to the client, the namespace will be attached to the front of the event
|
10
|
-
# name separated by a period. The `new` event listed in the example below under the `product` namespace
|
11
|
-
# would arrive on the client as `product.new`. Similarly, incoming events in the `namespace.event_name`
|
12
|
-
# format will be properly dispatched to the `event_name` under the correct `namespace`. Namespaces can
|
13
|
-
# be nested.
|
2
|
+
# Provides a DSL for mapping client events to controller actions.
|
14
3
|
#
|
15
4
|
# == Example events.rb file
|
16
5
|
# # located in config/initializers/events.rb
|
17
6
|
# WebsocketRails::EventMap.describe do
|
18
7
|
# subscribe :client_connected, to: ChatController, with_method: :client_connected
|
19
|
-
#
|
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
|
20
14
|
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
15
|
+
# Events can be nested underneath namesapces.
|
16
|
+
#
|
17
|
+
# namespace :product do
|
18
|
+
# subscribe :new, :to => ProductController, :with_method => :new
|
24
19
|
# end
|
25
20
|
class EventMap
|
26
21
|
|
@@ -39,6 +34,11 @@ module WebsocketRails
|
|
39
34
|
def routes_for(event, &block)
|
40
35
|
@namespace.routes_for event, &block
|
41
36
|
end
|
37
|
+
|
38
|
+
# Proxy the reload_controllers! method to the global namespace.
|
39
|
+
def reload_controllers!
|
40
|
+
@namespace.reload_controllers!
|
41
|
+
end
|
42
42
|
|
43
43
|
# Provides the DSL methods available to the Event routes file
|
44
44
|
class DSL
|
@@ -66,11 +66,17 @@ module WebsocketRails
|
|
66
66
|
@namespace = @namespace.parent
|
67
67
|
end
|
68
68
|
|
69
|
+
def private_channel(channel)
|
70
|
+
WebsocketRails[channel].make_private
|
71
|
+
end
|
72
|
+
|
69
73
|
end
|
70
74
|
|
71
75
|
# Stores route map for nested namespaces
|
72
76
|
class Namespace
|
73
|
-
|
77
|
+
|
78
|
+
include Logging
|
79
|
+
|
74
80
|
attr_reader :name, :controllers, :actions, :namespaces, :parent
|
75
81
|
|
76
82
|
def initialize(name,dispatcher,parent=nil)
|
@@ -90,6 +96,8 @@ module WebsocketRails
|
|
90
96
|
child
|
91
97
|
end
|
92
98
|
|
99
|
+
# Stores controller/action pairs for events subscribed under
|
100
|
+
# this namespace.
|
93
101
|
def store(event_name,options)
|
94
102
|
klass = options[:to] || raise("Must specify a class for to: option in event route")
|
95
103
|
action = options[:with_method] || raise("Must specify a method for with_method: option in event route")
|
@@ -97,10 +105,47 @@ module WebsocketRails
|
|
97
105
|
actions[event_name] << [klass,action]
|
98
106
|
end
|
99
107
|
|
108
|
+
# Reloads the controller instances stored in the event map
|
109
|
+
# collection, picking up code changes in development.
|
110
|
+
def reload_controllers!
|
111
|
+
return unless defined?(Rails) and
|
112
|
+
Rails.env.development? or Rails.env.test?
|
113
|
+
|
114
|
+
controllers.each_key do |klass|
|
115
|
+
data_store = controllers[klass].data_store
|
116
|
+
class_name = klass.name
|
117
|
+
filename = class_name.underscore
|
118
|
+
load "#{filename}.rb"
|
119
|
+
new_class = class_name.safe_constantize
|
120
|
+
|
121
|
+
controller = new_class.new
|
122
|
+
controller.instance_variable_set(:@_dispatcher,@dispatcher)
|
123
|
+
controller.instance_variable_set(:@data_store,data_store)
|
124
|
+
controller.send :initialize_session if controller.respond_to?(:initialize_session)
|
125
|
+
controllers[klass] = controller
|
126
|
+
end
|
127
|
+
unless namespaces.empty?
|
128
|
+
namespaces.each_value { |ns| ns.reload_controllers! unless ns.name == :websocket_rails }
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Iterates through the namespace tree and yields all
|
133
|
+
# controller/action pairs stored for the target event.
|
100
134
|
def routes_for(event,event_namespace=nil,&block)
|
101
|
-
|
102
|
-
|
103
|
-
|
135
|
+
|
136
|
+
# Grab the first level namespace from the namespace array
|
137
|
+
# and remove it from the copy.
|
138
|
+
event_namespace = copy_event_namespace( event, event_namespace ) || return
|
139
|
+
namespace = event_namespace.shift
|
140
|
+
|
141
|
+
# If the namespace matches the current namespace and we are
|
142
|
+
# at the last namespace level, yield any controller/action
|
143
|
+
# pairs for this event.
|
144
|
+
#
|
145
|
+
# If the namespace does not match, search the list of child
|
146
|
+
# namespaces stored at this level for a match and delegate
|
147
|
+
# to it's #routes_for method, passing along the current
|
148
|
+
# copy of the event's namespace array.
|
104
149
|
if namespace == @name and event_namespace.empty?
|
105
150
|
actions[event.name].each do |klass,action|
|
106
151
|
controller = controllers[klass]
|
@@ -121,6 +166,11 @@ module WebsocketRails
|
|
121
166
|
controller.instance_variable_set(:@_dispatcher,@dispatcher)
|
122
167
|
controller.send :initialize_session if controller.respond_to?(:initialize_session)
|
123
168
|
end
|
169
|
+
|
170
|
+
def copy_event_namespace(event,namespace=nil)
|
171
|
+
namespace = event.namespace.dup if namespace.nil?
|
172
|
+
namespace
|
173
|
+
end
|
124
174
|
|
125
175
|
end
|
126
176
|
|
@@ -3,6 +3,8 @@ module WebsocketRails
|
|
3
3
|
def self.events
|
4
4
|
Proc.new do
|
5
5
|
namespace :websocket_rails do
|
6
|
+
subscribe :pong, :to => InternalController, :with_method => :do_pong
|
7
|
+
subscribe :reload!, :to => InternalController, :with_method => :reload_controllers!
|
6
8
|
subscribe :subscribe, :to => InternalController, :with_method => :subscribe_to_channel
|
7
9
|
end
|
8
10
|
end
|
@@ -10,10 +12,26 @@ module WebsocketRails
|
|
10
12
|
end
|
11
13
|
|
12
14
|
class InternalController < BaseController
|
15
|
+
include Logging
|
16
|
+
|
13
17
|
def subscribe_to_channel
|
14
|
-
|
15
|
-
|
16
|
-
|
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 subscibe_private instead." } )
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def reload_controllers!
|
28
|
+
return unless defined?(Rails) and Rails.env.development? || Rails.env.test?
|
29
|
+
log 'reloading controllers'
|
30
|
+
@_dispatcher.reload_controllers!
|
31
|
+
end
|
32
|
+
|
33
|
+
def do_pong
|
34
|
+
connection.pong = true
|
17
35
|
end
|
18
36
|
end
|
19
37
|
end
|