webmate 0.1.0

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 (62) hide show
  1. data/.gitignore +19 -0
  2. data/GUIDE.md +115 -0
  3. data/Gemfile +10 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +47 -0
  6. data/Rakefile +1 -0
  7. data/bin/webmate +70 -0
  8. data/lib/webmate/application.rb +138 -0
  9. data/lib/webmate/config.rb +23 -0
  10. data/lib/webmate/decorators/base.rb +38 -0
  11. data/lib/webmate/env.rb +17 -0
  12. data/lib/webmate/logger.rb +20 -0
  13. data/lib/webmate/observers/base.rb +24 -0
  14. data/lib/webmate/presenters/base.rb +69 -0
  15. data/lib/webmate/presenters/base_presenter.rb +115 -0
  16. data/lib/webmate/presenters/scoped.rb +30 -0
  17. data/lib/webmate/responders/abstract.rb +127 -0
  18. data/lib/webmate/responders/base.rb +21 -0
  19. data/lib/webmate/responders/callbacks.rb +79 -0
  20. data/lib/webmate/responders/exceptions.rb +4 -0
  21. data/lib/webmate/responders/response.rb +36 -0
  22. data/lib/webmate/responders/templates.rb +65 -0
  23. data/lib/webmate/route_helpers/route.rb +91 -0
  24. data/lib/webmate/route_helpers/routes_collection.rb +273 -0
  25. data/lib/webmate/socket.io/actions/connection.rb +14 -0
  26. data/lib/webmate/socket.io/actions/handshake.rb +34 -0
  27. data/lib/webmate/socket.io/packets/ack.rb +5 -0
  28. data/lib/webmate/socket.io/packets/base.rb +156 -0
  29. data/lib/webmate/socket.io/packets/connect.rb +5 -0
  30. data/lib/webmate/socket.io/packets/disconnect.rb +5 -0
  31. data/lib/webmate/socket.io/packets/error.rb +5 -0
  32. data/lib/webmate/socket.io/packets/event.rb +5 -0
  33. data/lib/webmate/socket.io/packets/heartbeat.rb +5 -0
  34. data/lib/webmate/socket.io/packets/json.rb +5 -0
  35. data/lib/webmate/socket.io/packets/message.rb +5 -0
  36. data/lib/webmate/socket.io/packets/noop.rb +5 -0
  37. data/lib/webmate/support/em_mongoid.rb +53 -0
  38. data/lib/webmate/version.rb +3 -0
  39. data/lib/webmate/views/scope.rb +25 -0
  40. data/lib/webmate/websockets.rb +50 -0
  41. data/lib/webmate.rb +129 -0
  42. data/spec/lib/route_helpers/route_spec.rb +41 -0
  43. data/spec/spec_helper.rb +18 -0
  44. data/vendor/.DS_Store +0 -0
  45. data/vendor/assets/.DS_Store +0 -0
  46. data/vendor/assets/javascripts/.DS_Store +0 -0
  47. data/vendor/assets/javascripts/webmate/.DS_Store +0 -0
  48. data/vendor/assets/javascripts/webmate/auth.coffee +63 -0
  49. data/vendor/assets/javascripts/webmate/backbone_ext/.DS_Store +0 -0
  50. data/vendor/assets/javascripts/webmate/backbone_ext/resources.coffee +60 -0
  51. data/vendor/assets/javascripts/webmate/backbone_ext/sync.coffee +131 -0
  52. data/vendor/assets/javascripts/webmate/client.coffee +133 -0
  53. data/vendor/assets/javascripts/webmate/init.coffee +7 -0
  54. data/vendor/assets/javascripts/webmate/libs/.DS_Store +0 -0
  55. data/vendor/assets/javascripts/webmate/libs/backbone.js +1572 -0
  56. data/vendor/assets/javascripts/webmate/libs/benchmark.coffee +27 -0
  57. data/vendor/assets/javascripts/webmate/libs/icanhaz.js +542 -0
  58. data/vendor/assets/javascripts/webmate/libs/socket.io.js +3871 -0
  59. data/vendor/assets/javascripts/webmate/libs/underscore.js +1 -0
  60. data/vendor/assets/javascripts/webmate.js +10 -0
  61. data/webmate.gemspec +31 -0
  62. metadata +290 -0
@@ -0,0 +1,273 @@
1
+ module Webmate
2
+ class RoutesCollection
3
+ TRANSPORTS = [:ws, :http]
4
+
5
+ attr_reader :routes
6
+
7
+ def initialize
8
+ @routes = {}
9
+ @resource_scope = []
10
+
11
+ enable_websockets_support
12
+ end
13
+
14
+ def define_routes(&block)
15
+ instance_eval(&block)
16
+ end
17
+
18
+ # get info about matched route
19
+ # method - GET/POST/PUT/PATCH/DELETE
20
+ # transport - HTTP / WS [ HTTPS / WSS ]
21
+ # path - /projects/123/tasks
22
+ #
23
+ def match(method, transport, path)
24
+ routes = get_routes(method, transport)
25
+ routes.each do |route|
26
+ if info = route.match(path)
27
+ return info
28
+ end
29
+ end
30
+ nil
31
+ end
32
+
33
+ def get_routes(method, transport)
34
+ @routes[method] ||= {}
35
+ @routes[method][transport] || []
36
+ end
37
+
38
+ private
39
+
40
+ # if websockets enabled, we should add specific http routes
41
+ # - for handshake [ get session id ]
42
+ # - for connection opening [ switch protocol from http to ws ]
43
+ def enable_websockets_support
44
+ namespace = configatron.websockets.namespace
45
+ namespace = 'api' if namespace.blank? # || not working with configatron
46
+
47
+ route_options = { method: 'GET', transport: ['HTTP'], action: 'websocket' }
48
+
49
+ # handshake
50
+ add_route(Webmate::Route.new(route_options.merge(
51
+ path: "/#{namespace}/:version_id",
52
+ responder: Webmate::SocketIO::Actions::Handshake,
53
+ )))
54
+
55
+ # transport connection
56
+ add_route(Webmate::Route.new(route_options.merge(
57
+ transport: ["WS"],
58
+ path: "/#{namespace}/:version_id/websocket/:session_id",
59
+ responder: Webmate::SocketIO::Actions::Connection,
60
+ action: 'open'
61
+ )))
62
+ end
63
+
64
+ # we store routes in following structure
65
+ # { method:
66
+ # transport: [ routes ]
67
+ # route - valid object of Webmate::Route class
68
+ def add_route(route)
69
+ # add route to specific node of routes hash
70
+ @routes[route.method.to_s.upcase] ||= {}
71
+ route.transport.each do |transport|
72
+ (@routes[route.method.to_s.upcase][transport.to_s.upcase] ||= []).push(route)
73
+ end
74
+ end
75
+
76
+ # define methods for separate routes
77
+ # get '/path', to: , transport: ,
78
+ # or
79
+ # resources :projects
80
+ # member do
81
+ # get 'read_formatted'
82
+ %w[get post put delete patch].each do |method_name|
83
+ define_method method_name.to_sym do |path, options = {}|
84
+ route_options = process_options(options)
85
+
86
+ # process case inside resources/collection or resources/member block
87
+ if is_member_scope? or is_collection_scope?
88
+ route_options[:responder] ||= get_responder_from_scope
89
+ route_options[:action] ||= path
90
+ route_options[:path] ||= "#{path_prefix}/#{path}"
91
+ else
92
+ route_options[:path] = path || '/'
93
+ end
94
+ route_options[:method] = method_name.to_sym
95
+
96
+ add_route(Webmate::Route.new(route_options))
97
+ end
98
+ end
99
+
100
+ # available options are
101
+ # transport: [:http, :ws] or any single transport
102
+ # responder can be specified
103
+ # action: some_action
104
+ # responder: SomeResponder or 'some/responder/api/v1'
105
+ # or with to: param
106
+ # to: 'responder_name#action_name'
107
+ def process_options(raw_options)
108
+ options = {}
109
+ options[:transport] = normalized_transport_option(raw_options[:transport])
110
+
111
+ if responder_with_action = raw_options[:to]
112
+ # extract action & responder from :to
113
+ responder_name, action = responder_with_action.split('#')
114
+ options[:responder] = "#{responder_name}_responder".classify.constantize
115
+ options[:action] = action
116
+ else
117
+ # use action & responder options
118
+ options[:responder] = raw_options[:responder]
119
+ options[:action] = raw_options[:action]
120
+ end
121
+
122
+ options
123
+ end
124
+
125
+ # resource :name, options, &block
126
+ # can register following methods
127
+ # get 'name' => read_all
128
+ # get 'name/:id' => read
129
+ # post 'name' => create
130
+ # put 'name/:id' => update
131
+ # delete 'name/:id' => destroy
132
+ #
133
+ # examples
134
+ # resources :projects, transport: :http, only: [:read, :read_all, :update, :delete, :create]
135
+ #
136
+ def resources(*resources, &block)
137
+ options = resources.last.is_a?(Hash) ? resources.pop : {}
138
+ actions = normalized_action_option(options.delete(:only))
139
+
140
+ resources.each do |resource_name|
141
+ responder = (options[:responder] || "#{resource_name}_responder").classify
142
+ route_args = { responder: responder, transport: options[:transport] }
143
+
144
+ [:read, :read_all, :update, :delete, :create].each do |action_name|
145
+ if actions.include?(action_name)
146
+ self.send "define_resource_#{action_name}_method", resource_name.to_s, route_args
147
+ end
148
+ end
149
+
150
+ nested_resources_eval(resource_name, &block) if block_given?
151
+ end
152
+ end
153
+
154
+ # should process blocks inside other resource
155
+ # and correctly set prefix of resources-parents
156
+ # resourcesprojects do
157
+ # resources :tasks
158
+ # end
159
+ def nested_resources_eval(resource_name, &block)
160
+ @resource_scope.push({
161
+ resource: resource_name,
162
+ resource_id: "#{resource_name.to_s.singularize}_id".to_sym
163
+ })
164
+ yield block
165
+ ensure
166
+ @resource_scope.pop
167
+ end
168
+
169
+ def path_prefix
170
+ prefix = ''
171
+ @resource_scope.each do |scope|
172
+ prefix << "/#{scope[:resource]}"
173
+ prefix << "/:#{scope[:resource_id]}" unless scope[:collection]
174
+ end
175
+ prefix
176
+ end
177
+
178
+ def get_responder_from_scope
179
+ responder_name = @resource_scope.last[:resource]
180
+ "#{responder_name}_responder".classify.constantize
181
+ end
182
+
183
+ # methods below designed to set actions on member/collection
184
+ # inside resource definition block
185
+ #
186
+ # "projects/do_on_collection"
187
+ # "projects/:project_id/do_on_member"
188
+ #
189
+ # can be designed as
190
+ # example
191
+ # resources :projects do
192
+ # collection do
193
+ # get 'do_on_collection'
194
+ # end
195
+ #
196
+ # member do
197
+ # get "do_on_member"
198
+ # end
199
+ # prefix /resource_name/resource_id
200
+ def member(&block)
201
+ return if @resource_scope.blank?
202
+ @resource_scope.last[:member] = true
203
+ yield block
204
+ ensure
205
+ @resource_scope.last[:member] = false
206
+ end
207
+
208
+ # prefix /resource_name
209
+ def collection(&block)
210
+ return if @resource_scope.blank?
211
+ @resource_scope.last[:collection] = true
212
+ yield block
213
+ ensure
214
+ @resource_scope.last[:collection] = false
215
+ end
216
+
217
+ # track definition inside collection block
218
+ # collection do
219
+ # path 'code', options
220
+ def is_collection_scope?
221
+ @resource_scope.present? && @resource_scope.last[:collection]
222
+ end
223
+
224
+ # track definition inside member block
225
+ # collection do
226
+ # path 'code', options
227
+ def is_member_scope?
228
+ @resource_scope.present? && @resource_scope.last[:member]
229
+ end
230
+
231
+
232
+ # helper methods
233
+ # normalize_transport_option
234
+ # returns array of requested transports, but available ones only
235
+ def normalized_transport_option(transport = nil)
236
+ return TRANSPORTS.dup if transport.blank?
237
+ transport = [transport] unless transport.is_a?(Array)
238
+
239
+ transport.map{|t| t.to_s.downcase.to_sym} & TRANSPORTS
240
+ end
241
+
242
+ # methods list
243
+ # combination from available
244
+ # [:read, :read_all, :update, :delete, :create]
245
+ def normalized_action_option(methods = nil)
246
+ default_methods = [:read, :read_all, :update, :delete, :create]
247
+ return default_methods if methods.blank?
248
+ methods = [methods] unless methods.is_a?(Array)
249
+
250
+ methods.map{|m| m.to_s.downcase.to_sym} & default_methods
251
+ end
252
+
253
+ def define_resource_read_all_method(resource_name, route_args)
254
+ get "#{path_prefix}/#{resource_name}", route_args.merge(action: :read_all)
255
+ end
256
+
257
+ def define_resource_read_method(resource_name, route_args)
258
+ get "#{path_prefix}/#{resource_name}/:#{resource_name.singularize}_id", route_args.merge(action: :read)
259
+ end
260
+
261
+ def define_resource_create_method(resource_name, route_args)
262
+ post "#{path_prefix}/#{resource_name}", route_args.merge(action: :create)
263
+ end
264
+
265
+ def define_resource_update_method(resource_name, route_args)
266
+ put "#{path_prefix}/#{resource_name}/:#{resource_name.singularize}_id", route_args.merge(action: :update)
267
+ end
268
+
269
+ def define_resource_delete_method(resource_name, route_args)
270
+ delete "#{path_prefix}/#{resource_name}/:#{resource_name.singularize}_id", route_args.merge(action: :delete)
271
+ end
272
+ end
273
+ end
@@ -0,0 +1,14 @@
1
+ module Webmate
2
+ module SocketIO
3
+ module Actions
4
+ class Connection
5
+ def initialize(*args)
6
+ end
7
+
8
+ def respond(base)
9
+ puts "this case should be process directly in Webmate::Application"
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,34 @@
1
+ module Webmate
2
+ module SocketIO
3
+ module Actions
4
+ class Handshake
5
+ def initialize(options = {})
6
+ @session_id = generate_session_id
7
+
8
+ default_options = {
9
+ transports: %w{websocket},
10
+ heartbeat_timeout: 300,
11
+ closing_timeout: 300
12
+ }
13
+ @settings = OpenStruct.new(default_options.merge(options))
14
+ end
15
+
16
+ def respond
17
+ body = [
18
+ @session_id,
19
+ @settings.heartbeat_timeout,
20
+ @settings.closing_timeout,
21
+ @settings.transports.join(',')
22
+ ]
23
+ Webmate::Responders::Response.new(body.join(':'))
24
+ end
25
+
26
+ private
27
+
28
+ def generate_session_id
29
+ SecureRandom.hex
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,5 @@
1
+ class Webmate::SocketIO::Packets::Ack < Webmate::SocketIO::Packets::Base
2
+ def packet_type
3
+ 'ack'
4
+ end
5
+ end
@@ -0,0 +1,156 @@
1
+ module Webmate
2
+ module SocketIO
3
+ module Packets
4
+ class Base
5
+ cattr_accessor :current_id
6
+
7
+ attr_writer :packet_id, :packet_endpoint
8
+
9
+ # the same order, as in socket io packet.packets
10
+ PACKETS_TYPES = %W{
11
+ disconnect
12
+ connect
13
+ heartbeat
14
+ message
15
+ json
16
+ event
17
+ ack
18
+ error
19
+ noop
20
+ }
21
+
22
+ def initialize(packet_data = {})
23
+ @packet_data = packet_data.with_indifferent_access
24
+ end
25
+
26
+ # packet should be created by socket.io spec
27
+ #[message type] ':' [message id ('+')] ':' [message endpoint] (':' [message data])
28
+ # and webmate spec
29
+ # message_data = {
30
+ # method: GET/POST/...
31
+ # path: '/projects'
32
+ # params: {}
33
+ # metadata: { data should be returned back with answer }
34
+ # }
35
+ def self.parse(packet)
36
+ # last element is encoded json array, so there can be many ':'
37
+ packet_type_id, packet_id, packet_endpoint, json_data = packet.split(':', 4)
38
+
39
+ packet_data = (Yajl::Parser.parse(json_data) || {}).with_indifferent_access
40
+
41
+ if packet_data[:params].is_a?(String)
42
+ packet_data[:params] = Yajl::Parser.parse(packet_data[:params])
43
+ end
44
+
45
+ if packet_data[:metadata].is_a?(String)
46
+ packet_data[:metadata] = Yajl::Parser.parse(packet_data[:metadata])
47
+ end
48
+
49
+ packet = OpenStruct.new(
50
+ path: packet_data[:path],
51
+ method: packet_data[:method],
52
+ params: packet_data[:params] || {},
53
+ metadata: packet_data[:metadata] || {},
54
+ packet_id: packet_id,
55
+ packet_endpoint: packet_endpoint
56
+ )
57
+
58
+ packet
59
+ end
60
+
61
+ # convert response from Responders::Base to socket io message
62
+ #
63
+ def self.build_response_packet(response)
64
+ new(self.prepare_packet_data(response))
65
+ end
66
+
67
+ def self.prepare_packet_data(response)
68
+ packet_data = {
69
+ action: response.action,
70
+ body: response.data,
71
+ path: response.path,
72
+ params: response.params,
73
+ metadata: response.metadata
74
+ }
75
+ end
76
+
77
+ # socket io spec
78
+ #[message type] ':' [message id ('+')] ':' [message endpoint] (':' [message data])
79
+ def to_packet
80
+ data = {
81
+ action: action,
82
+ request: {
83
+ path: path,
84
+ metadata: metadata
85
+ },
86
+ response: {
87
+ body: body,
88
+ status: status || 200
89
+ }
90
+ }
91
+ encoded_data = Yajl::Encoder.new.encode(data)
92
+ [
93
+ packet_type_id,
94
+ packet_id,
95
+ packet_endpoint,
96
+ encoded_data
97
+ ].join(':')
98
+ end
99
+
100
+ def packet_type_id
101
+ PACKETS_TYPES.index(self.packet_type)
102
+ end
103
+
104
+ def packet_endpoint
105
+ @packet_endpoint ||= ''
106
+ end
107
+
108
+ def packet_data
109
+ (@packet_data || {})
110
+ end
111
+
112
+ def metadata
113
+ packet_data[:metadata]
114
+ end
115
+
116
+ def path
117
+ packet_data[:path]
118
+ end
119
+
120
+ def action
121
+ packet_data[:action]
122
+ end
123
+
124
+ def params
125
+ packet_data[:params]
126
+ end
127
+
128
+ def body
129
+ packet_data[:body]
130
+ end
131
+
132
+ def status
133
+ packet_data[:status]
134
+ end
135
+
136
+ def packet_id
137
+ @id ||= generate_packet_id
138
+ end
139
+
140
+ # update counter
141
+ def packet_id=(new_packet_id)
142
+ self.class.current_id = new_packet_id
143
+ @id ||= generate_packet_id
144
+ end
145
+
146
+ # unique packet id
147
+ # didn't find any influence for now,
148
+ # uniqueness not matter
149
+ def generate_packet_id
150
+ self.class.current_id ||= 0
151
+ self.class.current_id += 1
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,5 @@
1
+ class Webmate::SocketIO::Packets::Connect < Webmate::SocketIO::Packets::Base
2
+ def packet_type
3
+ 'connect'
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Webmate::SocketIO::Packets::Disconnect < Webmate::SocketIO::Packets::Base
2
+ def packet_type
3
+ 'disconnect'
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Webmate::SocketIO::Packets::Error < Webmate::SocketIO::Packets::Base
2
+ def packet_type
3
+ 'error'
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Webmate::SocketIO::Packets::Event < Webmate::SocketIO::Packets::Base
2
+ def packet_type
3
+ 'event'
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Webmate::SocketIO::Packets::Heartbeat < Webmate::SocketIO::Packets::Base
2
+ def packet_type
3
+ 'heartbeat'
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Webmate::SocketIO::Packets::Json < Webmate::SocketIO::Packets::Base
2
+ def packet_type
3
+ 'json'
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Webmate::SocketIO::Packets::Message < Webmate::SocketIO::Packets::Base
2
+ def packet_type
3
+ 'message'
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Webmate::SocketIO::Packets::Noop < Webmate::SocketIO::Packets::Base
2
+ def packet_type
3
+ 'noop'
4
+ end
5
+ end
@@ -0,0 +1,53 @@
1
+ # TODO: this is needed to use latest mongoid with moped, but it doesn't work at this moment
2
+
3
+ begin
4
+ require "moped"
5
+ rescue LoadError => error
6
+ raise "Missing EM-Synchrony dependency: gem install moped"
7
+ end
8
+
9
+ module Moped
10
+ class TimeoutHandler
11
+ def self.timeout(op_timeout, &block)
12
+ f = Fiber.current
13
+ timer = EM::Timer.new(op_timeout) { f.resume(nil) }
14
+ res = block.call
15
+ timer.cancel
16
+ res
17
+ end
18
+ end
19
+ module Sockets
20
+ module Connectable
21
+ module ClassMethods
22
+ def connect(host, port, timeout)
23
+ TimeoutHandler.timeout(timeout) do
24
+ sock = new(host, port)
25
+ #sock.set_encoding('binary')
26
+ #sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
27
+ sock
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ class EM_TCP < ::EventMachine::Synchrony::TCPSocket
34
+ include Connectable
35
+
36
+ def connection_completed
37
+ @opening = false
38
+ @in_req.succeed(self) if @in_req
39
+ end
40
+ end
41
+ Mutex = ::EventMachine::Synchrony::Thread::Mutex
42
+ ConditionVariable = ::EventMachine::Synchrony::Thread::ConditionVariable
43
+ end
44
+ class Connection
45
+ def connect
46
+ @sock = if !!options[:ssl]
47
+ Sockets::SSL.connect(host, port, timeout)
48
+ else
49
+ Sockets::EM_TCP.connect(host, port, timeout)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,3 @@
1
+ module Webmate
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,25 @@
1
+ module Webmate::Views
2
+ class Scope
3
+ include Sinatra::Cookies
4
+ include Webmate::Sprockets::Helpers
5
+
6
+ def initialize(responder)
7
+ @responder = responder
8
+ end
9
+
10
+ def user_websocket_token
11
+ end
12
+
13
+ def javascript_client_configs
14
+ %Q{<script type="text/javascript">
15
+ if (!window.Webmate) {window.Webmate = {}};
16
+ window.Webmate.websocketsPort = #{configatron.websockets.port};
17
+ window.Webmate.websocketsEnabled = #{configatron.websockets.enabled ? 'true' : 'false'};
18
+ </script>}
19
+ end
20
+
21
+ def user_websocket_token_tag
22
+ %Q{<meta content="#{user_websocket_token}" name="websocket-token" />}
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,50 @@
1
+ module Webmate
2
+ class Websockets
3
+ class << self
4
+ def subscribe(session_id, request, &block)
5
+ user_id = request.env['warden'].user.id
6
+
7
+ request.websocket do |websocket|
8
+ # subscribe user to redis channel
9
+ subscribe_to_personal_channel(user_id, websocket)
10
+
11
+ websocket.onopen do
12
+ websocket.send(Webmate::SocketIO::Packets::Connect.new.to_packet)
13
+ warn("Socket connection opened for session_id: #{session_id}")
14
+ end
15
+
16
+ websocket.onmessage do |message|
17
+ response = block.call(Webmate::SocketIO::Packets::Base.parse(message))
18
+ if response
19
+ packet = Webmate::SocketIO::Packets::Message.build_response_packet(response)
20
+ websocket.send(packet.to_packet)
21
+ else
22
+ warn("empty response for #{message.inspect}")
23
+ end
24
+ end
25
+
26
+ websocket.onclose do
27
+ warn("Socket connection closed '#{session_id}'")
28
+ end
29
+ end
30
+ end
31
+
32
+ def subscribe_to_personal_channel(user_id, websocket)
33
+ channel_name = Webmate::Application.get_channel_name_for(user_id)
34
+
35
+ subscriber = EM::Hiredis.connect.pubsub
36
+ subscriber.subscribe(channel_name)
37
+ warn("user has been subscribed to channel '#{channel_name}'")
38
+
39
+ subscriber.on(:message) do |channel, message_data|
40
+ response_data = Webmate::Application.restore(message_data)
41
+ packet = Webmate::SocketIO::Packets::Message.new(response_data)
42
+
43
+ websocket.send(packet.to_packet)
44
+ end
45
+
46
+ subscriber
47
+ end
48
+ end
49
+ end
50
+ end