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.
- data/.gitignore +19 -0
- data/GUIDE.md +115 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +22 -0
- data/README.md +47 -0
- data/Rakefile +1 -0
- data/bin/webmate +70 -0
- data/lib/webmate/application.rb +138 -0
- data/lib/webmate/config.rb +23 -0
- data/lib/webmate/decorators/base.rb +38 -0
- data/lib/webmate/env.rb +17 -0
- data/lib/webmate/logger.rb +20 -0
- data/lib/webmate/observers/base.rb +24 -0
- data/lib/webmate/presenters/base.rb +69 -0
- data/lib/webmate/presenters/base_presenter.rb +115 -0
- data/lib/webmate/presenters/scoped.rb +30 -0
- data/lib/webmate/responders/abstract.rb +127 -0
- data/lib/webmate/responders/base.rb +21 -0
- data/lib/webmate/responders/callbacks.rb +79 -0
- data/lib/webmate/responders/exceptions.rb +4 -0
- data/lib/webmate/responders/response.rb +36 -0
- data/lib/webmate/responders/templates.rb +65 -0
- data/lib/webmate/route_helpers/route.rb +91 -0
- data/lib/webmate/route_helpers/routes_collection.rb +273 -0
- data/lib/webmate/socket.io/actions/connection.rb +14 -0
- data/lib/webmate/socket.io/actions/handshake.rb +34 -0
- data/lib/webmate/socket.io/packets/ack.rb +5 -0
- data/lib/webmate/socket.io/packets/base.rb +156 -0
- data/lib/webmate/socket.io/packets/connect.rb +5 -0
- data/lib/webmate/socket.io/packets/disconnect.rb +5 -0
- data/lib/webmate/socket.io/packets/error.rb +5 -0
- data/lib/webmate/socket.io/packets/event.rb +5 -0
- data/lib/webmate/socket.io/packets/heartbeat.rb +5 -0
- data/lib/webmate/socket.io/packets/json.rb +5 -0
- data/lib/webmate/socket.io/packets/message.rb +5 -0
- data/lib/webmate/socket.io/packets/noop.rb +5 -0
- data/lib/webmate/support/em_mongoid.rb +53 -0
- data/lib/webmate/version.rb +3 -0
- data/lib/webmate/views/scope.rb +25 -0
- data/lib/webmate/websockets.rb +50 -0
- data/lib/webmate.rb +129 -0
- data/spec/lib/route_helpers/route_spec.rb +41 -0
- data/spec/spec_helper.rb +18 -0
- data/vendor/.DS_Store +0 -0
- data/vendor/assets/.DS_Store +0 -0
- data/vendor/assets/javascripts/.DS_Store +0 -0
- data/vendor/assets/javascripts/webmate/.DS_Store +0 -0
- data/vendor/assets/javascripts/webmate/auth.coffee +63 -0
- data/vendor/assets/javascripts/webmate/backbone_ext/.DS_Store +0 -0
- data/vendor/assets/javascripts/webmate/backbone_ext/resources.coffee +60 -0
- data/vendor/assets/javascripts/webmate/backbone_ext/sync.coffee +131 -0
- data/vendor/assets/javascripts/webmate/client.coffee +133 -0
- data/vendor/assets/javascripts/webmate/init.coffee +7 -0
- data/vendor/assets/javascripts/webmate/libs/.DS_Store +0 -0
- data/vendor/assets/javascripts/webmate/libs/backbone.js +1572 -0
- data/vendor/assets/javascripts/webmate/libs/benchmark.coffee +27 -0
- data/vendor/assets/javascripts/webmate/libs/icanhaz.js +542 -0
- data/vendor/assets/javascripts/webmate/libs/socket.io.js +3871 -0
- data/vendor/assets/javascripts/webmate/libs/underscore.js +1 -0
- data/vendor/assets/javascripts/webmate.js +10 -0
- data/webmate.gemspec +31 -0
- 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,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,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,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,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
|