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.
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