websocket-rails 0.1.5 → 0.1.6
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.
- 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
|