_bushido-faye 0.8.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +247 -0
- data/README.rdoc +92 -0
- data/lib/faye-browser-min.js +1 -0
- data/lib/faye.rb +121 -0
- data/lib/faye/adapters/rack_adapter.rb +210 -0
- data/lib/faye/engines/connection.rb +60 -0
- data/lib/faye/engines/memory.rb +112 -0
- data/lib/faye/engines/proxy.rb +111 -0
- data/lib/faye/error.rb +49 -0
- data/lib/faye/mixins/logging.rb +47 -0
- data/lib/faye/mixins/publisher.rb +30 -0
- data/lib/faye/mixins/timeouts.rb +22 -0
- data/lib/faye/protocol/channel.rb +124 -0
- data/lib/faye/protocol/client.rb +378 -0
- data/lib/faye/protocol/extensible.rb +43 -0
- data/lib/faye/protocol/grammar.rb +58 -0
- data/lib/faye/protocol/publication.rb +5 -0
- data/lib/faye/protocol/server.rb +282 -0
- data/lib/faye/protocol/subscription.rb +24 -0
- data/lib/faye/transport/http.rb +76 -0
- data/lib/faye/transport/local.rb +22 -0
- data/lib/faye/transport/transport.rb +115 -0
- data/lib/faye/transport/web_socket.rb +99 -0
- data/lib/faye/util/namespace.rb +20 -0
- data/spec/browser.html +45 -0
- data/spec/encoding_helper.rb +7 -0
- data/spec/install.sh +78 -0
- data/spec/javascript/channel_spec.js +15 -0
- data/spec/javascript/client_spec.js +714 -0
- data/spec/javascript/engine/memory_spec.js +7 -0
- data/spec/javascript/engine_spec.js +417 -0
- data/spec/javascript/faye_spec.js +15 -0
- data/spec/javascript/grammar_spec.js +66 -0
- data/spec/javascript/node_adapter_spec.js +307 -0
- data/spec/javascript/publisher_spec.js +27 -0
- data/spec/javascript/server/connect_spec.js +168 -0
- data/spec/javascript/server/disconnect_spec.js +121 -0
- data/spec/javascript/server/extensions_spec.js +60 -0
- data/spec/javascript/server/handshake_spec.js +145 -0
- data/spec/javascript/server/integration_spec.js +124 -0
- data/spec/javascript/server/publish_spec.js +85 -0
- data/spec/javascript/server/subscribe_spec.js +247 -0
- data/spec/javascript/server/unsubscribe_spec.js +245 -0
- data/spec/javascript/server_spec.js +110 -0
- data/spec/javascript/transport_spec.js +130 -0
- data/spec/node.js +55 -0
- data/spec/phantom.js +17 -0
- data/spec/ruby/channel_spec.rb +17 -0
- data/spec/ruby/client_spec.rb +724 -0
- data/spec/ruby/engine/memory_spec.rb +7 -0
- data/spec/ruby/engine_examples.rb +427 -0
- data/spec/ruby/faye_spec.rb +14 -0
- data/spec/ruby/grammar_spec.rb +68 -0
- data/spec/ruby/publisher_spec.rb +27 -0
- data/spec/ruby/rack_adapter_spec.rb +236 -0
- data/spec/ruby/server/connect_spec.rb +170 -0
- data/spec/ruby/server/disconnect_spec.rb +120 -0
- data/spec/ruby/server/extensions_spec.rb +68 -0
- data/spec/ruby/server/handshake_spec.rb +143 -0
- data/spec/ruby/server/integration_spec.rb +126 -0
- data/spec/ruby/server/publish_spec.rb +81 -0
- data/spec/ruby/server/subscribe_spec.rb +247 -0
- data/spec/ruby/server/unsubscribe_spec.rb +247 -0
- data/spec/ruby/server_spec.rb +110 -0
- data/spec/ruby/transport_spec.rb +134 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/testswarm +29 -0
- data/spec/thin_proxy.rb +37 -0
- metadata +302 -0
@@ -0,0 +1,210 @@
|
|
1
|
+
module Faye
|
2
|
+
class RackAdapter
|
3
|
+
|
4
|
+
include Logging
|
5
|
+
|
6
|
+
extend Forwardable
|
7
|
+
def_delegators "@server.engine", :bind, :unbind
|
8
|
+
|
9
|
+
ASYNC_RESPONSE = [-1, {}, []].freeze
|
10
|
+
|
11
|
+
DEFAULT_ENDPOINT = '/bayeux'
|
12
|
+
SCRIPT_PATH = File.join(ROOT, 'faye-browser-min.js')
|
13
|
+
|
14
|
+
TYPE_JSON = {'Content-Type' => 'application/json'}
|
15
|
+
TYPE_SCRIPT = {'Content-Type' => 'text/javascript'}
|
16
|
+
TYPE_TEXT = {'Content-Type' => 'text/plain'}
|
17
|
+
|
18
|
+
# This header is passed by Rack::Proxy during testing. Rack::Proxy seems to
|
19
|
+
# set content-length for you, and setting it in here really slows the tests
|
20
|
+
# down. Better suggestions welcome.
|
21
|
+
HTTP_X_NO_CONTENT_LENGTH = 'HTTP_X_NO_CONTENT_LENGTH'
|
22
|
+
|
23
|
+
def initialize(app = nil, options = nil)
|
24
|
+
@app = app if app.respond_to?(:call)
|
25
|
+
@options = [app, options].grep(Hash).first || {}
|
26
|
+
|
27
|
+
@endpoint = @options[:mount] || DEFAULT_ENDPOINT
|
28
|
+
@endpoint_re = Regexp.new('^' + @endpoint + '(/[^/]*)*(\\.js)?$')
|
29
|
+
@server = Server.new(@options)
|
30
|
+
|
31
|
+
return unless extensions = @options[:extensions]
|
32
|
+
[*extensions].each { |extension| add_extension(extension) }
|
33
|
+
end
|
34
|
+
|
35
|
+
def add_extension(extension)
|
36
|
+
@server.add_extension(extension)
|
37
|
+
end
|
38
|
+
|
39
|
+
def remove_extension(extension)
|
40
|
+
@server.remove_extension(extension)
|
41
|
+
end
|
42
|
+
|
43
|
+
def get_client
|
44
|
+
@client ||= Client.new(@server)
|
45
|
+
end
|
46
|
+
|
47
|
+
def listen(port, ssl_options = nil)
|
48
|
+
Faye::WebSocket.load_adapter('thin')
|
49
|
+
handler = Rack::Handler.get('thin')
|
50
|
+
handler.run(self, :Port => port) do |s|
|
51
|
+
if ssl_options
|
52
|
+
s.ssl = true
|
53
|
+
s.ssl_options = {
|
54
|
+
:private_key_file => ssl_options[:key],
|
55
|
+
:cert_chain_file => ssl_options[:cert]
|
56
|
+
}
|
57
|
+
end
|
58
|
+
@thin_server = s
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def stop
|
63
|
+
return unless @thin_server
|
64
|
+
@thin_server.stop
|
65
|
+
@thin_server = nil
|
66
|
+
end
|
67
|
+
|
68
|
+
def call(env)
|
69
|
+
Faye.ensure_reactor_running!
|
70
|
+
request = Rack::Request.new(env)
|
71
|
+
|
72
|
+
unless request.path_info =~ @endpoint_re
|
73
|
+
env['faye.client'] = get_client
|
74
|
+
return @app ? @app.call(env) :
|
75
|
+
[404, TYPE_TEXT, ["Sure you're not looking for #{@endpoint} ?"]] #"
|
76
|
+
end
|
77
|
+
|
78
|
+
return serve_client_script(env) if request.path_info =~ /\.js$/
|
79
|
+
return handle_options(request) if env['REQUEST_METHOD'] == 'OPTIONS'
|
80
|
+
return handle_websocket(env) if Faye::WebSocket.websocket?(env)
|
81
|
+
return handle_eventsource(env) if Faye::EventSource.eventsource?(env)
|
82
|
+
|
83
|
+
handle_request(request)
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def serve_client_script(env)
|
89
|
+
@client_script ||= File.read(SCRIPT_PATH)
|
90
|
+
@client_digest ||= Digest::SHA1.hexdigest(@client_script)
|
91
|
+
@client_mtime ||= File.mtime(SCRIPT_PATH)
|
92
|
+
|
93
|
+
headers = TYPE_SCRIPT.dup
|
94
|
+
ims = env['HTTP_IF_MODIFIED_SINCE']
|
95
|
+
|
96
|
+
headers['Content-Length'] = @client_script.bytesize.to_s unless env[HTTP_X_NO_CONTENT_LENGTH]
|
97
|
+
headers['ETag'] = @client_digest
|
98
|
+
headers['Last-Modified'] = @client_mtime.httpdate
|
99
|
+
|
100
|
+
if env['HTTP_IF_NONE_MATCH'] == @client_digest
|
101
|
+
[304, headers, ['']]
|
102
|
+
elsif ims and @client_mtime <= Time.httpdate(ims)
|
103
|
+
[304, headers, ['']]
|
104
|
+
else
|
105
|
+
[200, headers, [@client_script]]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def handle_request(request)
|
110
|
+
json_msg = message_from_request(request)
|
111
|
+
message = Yajl::Parser.parse(json_msg)
|
112
|
+
jsonp = request.params['jsonp'] || JSONP_CALLBACK
|
113
|
+
headers = request.get? ? TYPE_SCRIPT.dup : TYPE_JSON.dup
|
114
|
+
origin = request.env['HTTP_ORIGIN']
|
115
|
+
callback = request.env['async.callback']
|
116
|
+
|
117
|
+
debug 'Received ?: ?', request.env['REQUEST_METHOD'], json_msg
|
118
|
+
@server.flush_connection(message) if request.get?
|
119
|
+
|
120
|
+
headers['Access-Control-Allow-Origin'] = origin if origin
|
121
|
+
headers['Cache-Control'] = 'no-cache, no-store' if request.get?
|
122
|
+
|
123
|
+
@server.process(message, false) do |replies|
|
124
|
+
response = Faye.to_json(replies)
|
125
|
+
response = "#{ jsonp }(#{ response });" if request.get?
|
126
|
+
headers['Content-Length'] = response.bytesize.to_s unless request.env[HTTP_X_NO_CONTENT_LENGTH]
|
127
|
+
headers['Connection'] = 'close'
|
128
|
+
debug 'Returning ?', response
|
129
|
+
callback.call [200, headers, [response]]
|
130
|
+
end
|
131
|
+
|
132
|
+
ASYNC_RESPONSE
|
133
|
+
rescue => e
|
134
|
+
error "#{e.message}\nBacktrace:\n#{e.backtrace * "\n"}"
|
135
|
+
[400, TYPE_TEXT, ['Bad request']]
|
136
|
+
end
|
137
|
+
|
138
|
+
def handle_websocket(env)
|
139
|
+
ws = Faye::WebSocket.new(env, nil, :ping => @options[:ping])
|
140
|
+
client_id = nil
|
141
|
+
|
142
|
+
ws.onmessage = lambda do |event|
|
143
|
+
begin
|
144
|
+
message = Yajl::Parser.parse(event.data)
|
145
|
+
client_id = Faye.client_id_from_messages(message)
|
146
|
+
|
147
|
+
debug "Received via WebSocket[#{ws.version}]: ?", message
|
148
|
+
@server.open_socket(client_id, ws)
|
149
|
+
|
150
|
+
@server.process(message, false) do |replies|
|
151
|
+
ws.send(Faye.to_json(replies)) if ws
|
152
|
+
end
|
153
|
+
rescue => e
|
154
|
+
error "#{e.message}\nBacktrace:\n#{e.backtrace * "\n"}"
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
ws.onclose = lambda do |event|
|
159
|
+
@server.close_socket(client_id)
|
160
|
+
ws = nil
|
161
|
+
end
|
162
|
+
|
163
|
+
ws.rack_response
|
164
|
+
end
|
165
|
+
|
166
|
+
def handle_eventsource(env)
|
167
|
+
es = Faye::EventSource.new(env, :ping => @options[:ping])
|
168
|
+
client_id = es.url.split('/').pop
|
169
|
+
|
170
|
+
debug 'Opened EventSource connection for ?', client_id
|
171
|
+
@server.open_socket(client_id, es)
|
172
|
+
|
173
|
+
es.onclose = lambda do |event|
|
174
|
+
@server.close_socket(client_id)
|
175
|
+
es = nil
|
176
|
+
end
|
177
|
+
|
178
|
+
es.rack_response
|
179
|
+
end
|
180
|
+
|
181
|
+
def message_from_request(request)
|
182
|
+
message = request.params['message']
|
183
|
+
return message if message
|
184
|
+
|
185
|
+
# Some clients do not send a content-type, e.g.
|
186
|
+
# Internet Explorer when using cross-origin-long-polling
|
187
|
+
# Some use application/xml when using CORS
|
188
|
+
content_type = request.env['CONTENT_TYPE'] || ''
|
189
|
+
|
190
|
+
if content_type.split(';').first == 'application/json'
|
191
|
+
request.body.read
|
192
|
+
else
|
193
|
+
CGI.parse(request.body.read)['message'][0]
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def handle_options(request)
|
198
|
+
headers = {
|
199
|
+
'Access-Control-Allow-Origin' => '*',
|
200
|
+
'Access-Control-Allow-Credentials' => 'false',
|
201
|
+
'Access-Control-Max-Age' => '86400',
|
202
|
+
'Access-Control-Allow-Methods' => 'POST, GET, PUT, DELETE, OPTIONS',
|
203
|
+
'Access-Control-Allow-Headers' => 'Accept, Content-Type, X-Requested-With'
|
204
|
+
}
|
205
|
+
[200, headers, ['']]
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Faye
|
2
|
+
module Engine
|
3
|
+
|
4
|
+
class Connection
|
5
|
+
include EventMachine::Deferrable
|
6
|
+
include Timeouts
|
7
|
+
|
8
|
+
attr_accessor :socket
|
9
|
+
|
10
|
+
def initialize(engine, id, options = {})
|
11
|
+
@engine = engine
|
12
|
+
@id = id
|
13
|
+
@options = options
|
14
|
+
@inbox = Set.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def deliver(message)
|
18
|
+
return socket.send(Faye.to_json([message])) if socket
|
19
|
+
return unless @inbox.add?(message)
|
20
|
+
begin_delivery_timeout
|
21
|
+
end
|
22
|
+
|
23
|
+
def connect(options, &block)
|
24
|
+
options = options || {}
|
25
|
+
timeout = options['timeout'] ? options['timeout'] / 1000.0 : @engine.timeout
|
26
|
+
|
27
|
+
set_deferred_status(:deferred)
|
28
|
+
callback(&block)
|
29
|
+
|
30
|
+
begin_delivery_timeout
|
31
|
+
begin_connection_timeout(timeout)
|
32
|
+
end
|
33
|
+
|
34
|
+
def flush!(force = false)
|
35
|
+
release_connection!(force)
|
36
|
+
set_deferred_status(:succeeded, @inbox.entries)
|
37
|
+
@inbox = []
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def release_connection!(force = false)
|
43
|
+
@engine.close_connection(@id) if force or socket.nil?
|
44
|
+
remove_timeout(:connection)
|
45
|
+
remove_timeout(:delivery)
|
46
|
+
end
|
47
|
+
|
48
|
+
def begin_delivery_timeout
|
49
|
+
return if @inbox.empty?
|
50
|
+
add_timeout(:delivery, MAX_DELAY) { flush! }
|
51
|
+
end
|
52
|
+
|
53
|
+
def begin_connection_timeout(timeout)
|
54
|
+
add_timeout(:connection, timeout) { flush! }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module Faye
|
2
|
+
module Engine
|
3
|
+
|
4
|
+
class Memory
|
5
|
+
include Timeouts
|
6
|
+
|
7
|
+
def self.create(server, options)
|
8
|
+
new(server, options)
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(server, options)
|
12
|
+
@server = server
|
13
|
+
@options = options
|
14
|
+
@namespace = Namespace.new
|
15
|
+
@clients = {}
|
16
|
+
@channels = {}
|
17
|
+
@messages = {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def create_client(&callback)
|
21
|
+
client_id = @namespace.generate
|
22
|
+
@server.debug 'Created new client ?', client_id
|
23
|
+
ping(client_id)
|
24
|
+
@server.trigger(:handshake, client_id)
|
25
|
+
callback.call(client_id)
|
26
|
+
end
|
27
|
+
|
28
|
+
def destroy_client(client_id, &callback)
|
29
|
+
return unless @namespace.exists?(client_id)
|
30
|
+
|
31
|
+
if @clients.has_key?(client_id)
|
32
|
+
@clients[client_id].each { |channel| unsubscribe(client_id, channel) }
|
33
|
+
end
|
34
|
+
|
35
|
+
remove_timeout(client_id)
|
36
|
+
@namespace.release(client_id)
|
37
|
+
@messages.delete(client_id)
|
38
|
+
@server.debug 'Destroyed client ?', client_id
|
39
|
+
@server.trigger(:disconnect, client_id)
|
40
|
+
callback.call if callback
|
41
|
+
end
|
42
|
+
|
43
|
+
def client_exists(client_id, &callback)
|
44
|
+
callback.call(@namespace.exists?(client_id))
|
45
|
+
end
|
46
|
+
|
47
|
+
def ping(client_id)
|
48
|
+
timeout = @server.timeout
|
49
|
+
return unless Numeric === timeout
|
50
|
+
@server.debug 'Ping ?, ?', client_id, timeout
|
51
|
+
remove_timeout(client_id)
|
52
|
+
add_timeout(client_id, 2 * timeout) { destroy_client(client_id) }
|
53
|
+
end
|
54
|
+
|
55
|
+
def subscribe(client_id, channel, &callback)
|
56
|
+
@clients[client_id] ||= Set.new
|
57
|
+
should_trigger = @clients[client_id].add?(channel)
|
58
|
+
|
59
|
+
@channels[channel] ||= Set.new
|
60
|
+
@channels[channel].add(client_id)
|
61
|
+
|
62
|
+
@server.debug 'Subscribed client ? to channel ?', client_id, channel
|
63
|
+
@server.trigger(:subscribe, client_id, channel) if should_trigger
|
64
|
+
callback.call(true) if callback
|
65
|
+
end
|
66
|
+
|
67
|
+
def unsubscribe(client_id, channel, &callback)
|
68
|
+
if @clients.has_key?(client_id)
|
69
|
+
should_trigger = @clients[client_id].delete?(channel)
|
70
|
+
@clients.delete(client_id) if @clients[client_id].empty?
|
71
|
+
end
|
72
|
+
|
73
|
+
if @channels.has_key?(channel)
|
74
|
+
@channels[channel].delete(client_id)
|
75
|
+
@channels.delete(channel) if @channels[channel].empty?
|
76
|
+
end
|
77
|
+
|
78
|
+
@server.debug 'Unsubscribed client ? from channel ?', client_id, channel
|
79
|
+
@server.trigger(:unsubscribe, client_id, channel) if should_trigger
|
80
|
+
callback.call(true) if callback
|
81
|
+
end
|
82
|
+
|
83
|
+
def publish(message, channels)
|
84
|
+
@server.debug 'Publishing message ?', message
|
85
|
+
|
86
|
+
clients = Set.new
|
87
|
+
|
88
|
+
channels.each do |channel|
|
89
|
+
next unless subs = @channels[channel]
|
90
|
+
subs.each(&clients.method(:add))
|
91
|
+
end
|
92
|
+
|
93
|
+
clients.each do |client_id|
|
94
|
+
@server.debug 'Queueing for client ?: ?', client_id, message
|
95
|
+
@messages[client_id] ||= []
|
96
|
+
@messages[client_id] << Faye.copy_object(message)
|
97
|
+
empty_queue(client_id)
|
98
|
+
end
|
99
|
+
|
100
|
+
@server.trigger(:publish, message['clientId'], message['channel'], message['data'])
|
101
|
+
end
|
102
|
+
|
103
|
+
def empty_queue(client_id)
|
104
|
+
return unless @server.has_connection?(client_id)
|
105
|
+
@server.deliver(client_id, @messages[client_id])
|
106
|
+
@messages.delete(client_id)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module Faye
|
2
|
+
module Engine
|
3
|
+
|
4
|
+
METHODS = %w[create_client client_exists destroy_client ping subscribe unsubscribe]
|
5
|
+
MAX_DELAY = 0.0
|
6
|
+
INTERVAL = 0.0
|
7
|
+
TIMEOUT = 60.0
|
8
|
+
ID_LENGTH = 128
|
9
|
+
|
10
|
+
autoload :Connection, File.expand_path('../connection', __FILE__)
|
11
|
+
autoload :Memory, File.expand_path('../memory', __FILE__)
|
12
|
+
|
13
|
+
def self.ensure_reactor_running!
|
14
|
+
Thread.new { EM.run } unless EM.reactor_running?
|
15
|
+
Thread.pass until EM.reactor_running?
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.get(options)
|
19
|
+
Proxy.new(options)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.random(bitlength = ID_LENGTH)
|
23
|
+
limit = 2 ** bitlength - 1
|
24
|
+
max_size = limit.to_s(36).size
|
25
|
+
string = rand(limit).to_s(36)
|
26
|
+
string = '0' + string while string.size < max_size
|
27
|
+
string
|
28
|
+
end
|
29
|
+
|
30
|
+
class Proxy
|
31
|
+
include Publisher
|
32
|
+
include Logging
|
33
|
+
|
34
|
+
attr_reader :interval, :timeout
|
35
|
+
|
36
|
+
extend Forwardable
|
37
|
+
def_delegators :@engine, *METHODS
|
38
|
+
|
39
|
+
def initialize(options)
|
40
|
+
@options = options
|
41
|
+
@connections = {}
|
42
|
+
@interval = @options[:interval] || INTERVAL
|
43
|
+
@timeout = @options[:timeout] || TIMEOUT
|
44
|
+
|
45
|
+
engine_class = @options[:type] || Memory
|
46
|
+
@engine = engine_class.create(self, @options)
|
47
|
+
|
48
|
+
debug 'Created new engine: ?', @options
|
49
|
+
end
|
50
|
+
|
51
|
+
def connect(client_id, options = {}, &callback)
|
52
|
+
debug 'Accepting connection from ?', client_id
|
53
|
+
@engine.ping(client_id)
|
54
|
+
conn = connection(client_id, true)
|
55
|
+
conn.connect(options, &callback)
|
56
|
+
@engine.empty_queue(client_id)
|
57
|
+
end
|
58
|
+
|
59
|
+
def has_connection?(client_id)
|
60
|
+
@connections.has_key?(client_id)
|
61
|
+
end
|
62
|
+
|
63
|
+
def connection(client_id, create)
|
64
|
+
conn = @connections[client_id]
|
65
|
+
return conn if conn or not create
|
66
|
+
@connections[client_id] = Connection.new(self, client_id)
|
67
|
+
end
|
68
|
+
|
69
|
+
def close_connection(client_id)
|
70
|
+
debug 'Closing connection for ?', client_id
|
71
|
+
@connections.delete(client_id)
|
72
|
+
end
|
73
|
+
|
74
|
+
def open_socket(client_id, socket)
|
75
|
+
return unless client_id
|
76
|
+
conn = connection(client_id, true)
|
77
|
+
conn.socket = socket
|
78
|
+
end
|
79
|
+
|
80
|
+
def deliver(client_id, messages)
|
81
|
+
return if !messages || messages.empty?
|
82
|
+
conn = connection(client_id, false)
|
83
|
+
return false unless conn
|
84
|
+
messages.each(&conn.method(:deliver))
|
85
|
+
true
|
86
|
+
end
|
87
|
+
|
88
|
+
def generate_id
|
89
|
+
Engine.random
|
90
|
+
end
|
91
|
+
|
92
|
+
def flush(client_id)
|
93
|
+
return unless client_id
|
94
|
+
debug 'Flushing connection for ?', client_id
|
95
|
+
conn = connection(client_id, false)
|
96
|
+
conn.flush!(true) if conn
|
97
|
+
end
|
98
|
+
|
99
|
+
def disconnect
|
100
|
+
@engine.disconnect if @engine.respond_to?(:disconnect)
|
101
|
+
end
|
102
|
+
|
103
|
+
def publish(message)
|
104
|
+
channels = Channel.expand(message['channel'])
|
105
|
+
@engine.publish(message, channels)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|