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.
Files changed (50) hide show
  1. data/CHANGELOG.md +22 -0
  2. data/Gemfile +4 -0
  3. data/README.md +66 -159
  4. data/Rakefile +31 -4
  5. data/bin/thin-socketrails +16 -1
  6. data/lib/assets/javascripts/websocket_rails/channel.js.coffee +23 -8
  7. data/lib/assets/javascripts/websocket_rails/event.js.coffee +40 -0
  8. data/lib/assets/javascripts/websocket_rails/http_connection.js.coffee +18 -10
  9. data/lib/assets/javascripts/websocket_rails/main.js +1 -0
  10. data/lib/assets/javascripts/websocket_rails/websocket_connection.js.coffee +15 -10
  11. data/lib/assets/javascripts/websocket_rails/websocket_rails.js.coffee +41 -23
  12. data/lib/websocket-rails.rb +4 -4
  13. data/lib/websocket_rails/base_controller.rb +61 -29
  14. data/lib/websocket_rails/channel.rb +14 -5
  15. data/lib/websocket_rails/channel_manager.rb +3 -1
  16. data/lib/websocket_rails/connection_adapters.rb +34 -12
  17. data/lib/websocket_rails/connection_manager.rb +4 -0
  18. data/lib/websocket_rails/dispatcher.rb +27 -3
  19. data/lib/websocket_rails/engine.rb +2 -5
  20. data/lib/websocket_rails/event.rb +87 -42
  21. data/lib/websocket_rails/event_map.rb +70 -20
  22. data/lib/websocket_rails/event_queue.rb +4 -0
  23. data/lib/websocket_rails/internal_events.rb +21 -3
  24. data/lib/websocket_rails/logging.rb +18 -0
  25. data/lib/websocket_rails/version.rb +1 -1
  26. data/spec/dummy/log/test.log +0 -429
  27. data/spec/integration/connection_manager_spec.rb +3 -5
  28. data/spec/javascripts/generated/assets/channel.js +98 -0
  29. data/spec/javascripts/generated/assets/event.js +78 -0
  30. data/spec/javascripts/generated/assets/http_connection.js +108 -0
  31. data/spec/javascripts/generated/assets/websocket_connection.js +66 -0
  32. data/spec/javascripts/generated/assets/websocket_rails.js +180 -0
  33. data/spec/javascripts/generated/specs/channel_spec.js +66 -0
  34. data/spec/javascripts/generated/specs/event_spec.js +107 -0
  35. data/spec/javascripts/generated/specs/websocket_connection_spec.js +117 -0
  36. data/spec/javascripts/generated/specs/websocket_rails_spec.js +232 -0
  37. data/spec/javascripts/support/jasmine.yml +44 -0
  38. data/spec/javascripts/support/jasmine_config.rb +63 -0
  39. data/spec/javascripts/support/vendor/sinon-1.3.4.js +3555 -0
  40. data/spec/javascripts/websocket_rails/channel_spec.coffee +51 -0
  41. data/spec/javascripts/websocket_rails/event_spec.coffee +69 -0
  42. data/spec/javascripts/websocket_rails/websocket_connection_spec.coffee +86 -0
  43. data/spec/javascripts/websocket_rails/websocket_rails_spec.coffee +166 -0
  44. data/spec/support/helper_methods.rb +10 -1
  45. data/spec/unit/channel_spec.rb +28 -4
  46. data/spec/unit/connection_adapters_spec.rb +17 -0
  47. data/spec/unit/connection_manager_spec.rb +1 -1
  48. data/spec/unit/dispatcher_spec.rb +1 -1
  49. data/spec/unit/event_spec.rb +15 -11
  50. metadata +22 -4
@@ -1,53 +1,112 @@
1
- require 'json'
2
-
3
1
  module WebsocketRails
4
- class Event
5
2
 
6
- def self.new_from_json(encoded_data,connection)
7
- event_name, data, namespace, channel = decode encoded_data
8
- Event.new event_name, data,
9
- :connection => connection,
10
- :namespace => namespace,
11
- :channel => channel
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 self.new_on_open(connection,data=nil)
15
- connection_id = { :connection_id => connection.id }
16
- on_open_data = data.is_a?(Hash) ? data.merge(connection_id) : connection_id
13
+ def new_on_close(connection,data=nil)
14
+ Event.new :client_disconnected, :data => data, :connection => connection
15
+ end
17
16
 
18
- Event.new :client_connected, on_open_data, :connection => connection
17
+ def new_on_error(connection,data=nil)
18
+ Event.new :client_error, :data => data, :connection => connection
19
19
  end
20
20
 
21
- def self.new_on_close(connection,data=nil)
22
- Event.new :client_disconnected, data, :connection => connection
21
+ def new_on_ping(connection)
22
+ Event.new :ping, :data => {}, :connection => connection, :namespace => :websocket_rails
23
23
  end
24
24
 
25
- def self.new_on_error(connection,data=nil)
26
- Event.new :client_error, data, :connection => connection
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
- attr_reader :name, :data, :connection, :namespace, :channel
66
+ include Logging
67
+ extend StaticEvents
68
+
69
+ attr_reader :id, :name, :connection, :namespace, :channel
30
70
 
31
- def initialize(event_name,data,options={})
32
- @name = event_name.to_sym
33
- @data = data.is_a?(Hash) ? data.with_indifferent_access : data
34
- @channel = options[:channel]
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
- if is_channel?
41
- [channel, encoded_name, data].to_json
42
- else
43
- [encoded_name, data].to_json
44
- end
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. A single event can be mapped to any
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
- # subscribe :new_user, :to => ChatController, :with_method => :new_user
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
- # namespace :product do
22
- # subscribe :new, :to => ProductController, :with_method => :new
23
- # end
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
- event_namespace = event.namespace.dup if event_namespace.nil?
102
- return if event_namespace.nil?
103
- namespace = event_namespace.shift
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
 
@@ -16,6 +16,10 @@ module WebsocketRails
16
16
  @queue.last
17
17
  end
18
18
 
19
+ def size
20
+ @queue.size
21
+ end
22
+
19
23
  def flush(&block)
20
24
  unless block.nil?
21
25
  @queue.each do |item|
@@ -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
- puts "subscribed to: #{data[:channel]}"
15
- channel_name = data[:channel]
16
- WebsocketRails[channel_name].subscribe connection
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
@@ -0,0 +1,18 @@
1
+ module WebsocketRails
2
+ # Need to replace this module with a real logger
3
+ module Logging
4
+
5
+ def log(msg)
6
+ puts msg if debug?
7
+ end
8
+
9
+ def warn(msg)
10
+ puts msg
11
+ end
12
+
13
+ def debug?
14
+ LOG_LEVEL == :debug
15
+ end
16
+
17
+ end
18
+ end
@@ -1,3 +1,3 @@
1
1
  module WebsocketRails
2
- VERSION = "0.1.5"
2
+ VERSION = "0.1.6"
3
3
  end