face-faye 0.8.9
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +304 -0
- data/README.rdoc +83 -0
- data/lib/faye-browser-min.js +2 -0
- data/lib/faye-browser-min.js.map +8 -0
- data/lib/faye-browser.js +2194 -0
- data/lib/faye.rb +122 -0
- data/lib/faye/adapters/rack_adapter.rb +216 -0
- data/lib/faye/adapters/static_server.rb +56 -0
- data/lib/faye/engines/connection.rb +60 -0
- data/lib/faye/engines/memory.rb +112 -0
- data/lib/faye/engines/proxy.rb +121 -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 +376 -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 +293 -0
- data/lib/faye/protocol/socket.rb +23 -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 +116 -0
- data/lib/faye/transport/web_socket.rb +92 -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 +729 -0
- data/spec/javascript/engine/memory_spec.js +7 -0
- data/spec/javascript/engine_spec.js +417 -0
- data/spec/javascript/faye_spec.js +34 -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 +131 -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 +121 -0
- data/spec/javascript/transport_spec.js +135 -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 +741 -0
- data/spec/ruby/engine/memory_spec.rb +7 -0
- data/spec/ruby/engine_examples.rb +427 -0
- data/spec/ruby/faye_spec.rb +30 -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 +133 -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 +121 -0
- data/spec/ruby/transport_spec.rb +136 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/testswarm +42 -0
- data/spec/thin_proxy.rb +37 -0
- metadata +441 -0
@@ -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,121 @@
|
|
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 = 160
|
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 { EventMachine.run } unless EventMachine.reactor_running?
|
15
|
+
Thread.pass until EventMachine.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
|
+
bind :disconnect do |client_id|
|
49
|
+
EventMachine.next_tick { close_connection(client_id) }
|
50
|
+
end
|
51
|
+
|
52
|
+
debug 'Created new engine: ?', @options
|
53
|
+
end
|
54
|
+
|
55
|
+
def connect(client_id, options = {}, &callback)
|
56
|
+
debug 'Accepting connection from ?', client_id
|
57
|
+
@engine.ping(client_id)
|
58
|
+
conn = connection(client_id, true)
|
59
|
+
conn.connect(options, &callback)
|
60
|
+
@engine.empty_queue(client_id)
|
61
|
+
end
|
62
|
+
|
63
|
+
def has_connection?(client_id)
|
64
|
+
@connections.has_key?(client_id)
|
65
|
+
end
|
66
|
+
|
67
|
+
def connection(client_id, create)
|
68
|
+
conn = @connections[client_id]
|
69
|
+
return conn if conn or not create
|
70
|
+
@connections[client_id] = Connection.new(self, client_id)
|
71
|
+
trigger('connection:open', client_id)
|
72
|
+
@connections[client_id]
|
73
|
+
end
|
74
|
+
|
75
|
+
def close_connection(client_id)
|
76
|
+
debug 'Closing connection for ?', client_id
|
77
|
+
conn = @connections[client_id]
|
78
|
+
return unless conn
|
79
|
+
conn.socket.close if conn.socket
|
80
|
+
trigger('connection:close', client_id)
|
81
|
+
@connections.delete(client_id)
|
82
|
+
end
|
83
|
+
|
84
|
+
def open_socket(client_id, socket)
|
85
|
+
return unless client_id
|
86
|
+
conn = connection(client_id, true)
|
87
|
+
conn.socket = socket
|
88
|
+
end
|
89
|
+
|
90
|
+
def deliver(client_id, messages)
|
91
|
+
return if !messages || messages.empty?
|
92
|
+
conn = connection(client_id, false)
|
93
|
+
return false unless conn
|
94
|
+
messages.each(&conn.method(:deliver))
|
95
|
+
true
|
96
|
+
end
|
97
|
+
|
98
|
+
def generate_id
|
99
|
+
Engine.random
|
100
|
+
end
|
101
|
+
|
102
|
+
def flush(client_id)
|
103
|
+
return unless client_id
|
104
|
+
debug 'Flushing connection for ?', client_id
|
105
|
+
conn = connection(client_id, false)
|
106
|
+
conn.flush!(true) if conn
|
107
|
+
end
|
108
|
+
|
109
|
+
def disconnect
|
110
|
+
@engine.disconnect if @engine.respond_to?(:disconnect)
|
111
|
+
end
|
112
|
+
|
113
|
+
def publish(message)
|
114
|
+
channels = Channel.expand(message['channel'])
|
115
|
+
@engine.publish(message, channels)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
data/lib/faye/error.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
module Faye
|
2
|
+
class Error
|
3
|
+
|
4
|
+
def self.method_missing(type, *args)
|
5
|
+
code = const_get(type.to_s.upcase)
|
6
|
+
new(code[0], args, code[1]).to_s
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.parse(message)
|
10
|
+
message ||= ''
|
11
|
+
return new(nil, [], message) unless Grammar::ERROR =~ message
|
12
|
+
|
13
|
+
parts = message.split(':')
|
14
|
+
code = parts[0].to_i
|
15
|
+
params = parts[1].split(',')
|
16
|
+
message = parts[2]
|
17
|
+
|
18
|
+
new(code, params, message)
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :code, :params, :message
|
22
|
+
|
23
|
+
def initialize(code, params, message)
|
24
|
+
@code = code
|
25
|
+
@params = params
|
26
|
+
@message = message
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
"#{ @code }:#{ @params * ',' }:#{ @message }"
|
31
|
+
end
|
32
|
+
|
33
|
+
# http://code.google.com/p/cometd/wiki/BayeuxCodes
|
34
|
+
VERSION_MISMATCH = [300, 'Version mismatch']
|
35
|
+
CONNTYPE_MISMATCH = [301, 'Connection types not supported']
|
36
|
+
EXT_MISMATCH = [302, 'Extension mismatch']
|
37
|
+
BAD_REQUEST = [400, 'Bad request']
|
38
|
+
CLIENT_UNKNOWN = [401, 'Unknown client']
|
39
|
+
PARAMETER_MISSING = [402, 'Missing required parameter']
|
40
|
+
CHANNEL_FORBIDDEN = [403, 'Forbidden channel']
|
41
|
+
CHANNEL_UNKNOWN = [404, 'Unknown channel']
|
42
|
+
CHANNEL_INVALID = [405, 'Invalid channel']
|
43
|
+
EXT_UNKNOWN = [406, 'Unknown extension']
|
44
|
+
PUBLISH_FAILED = [407, 'Failed to publish']
|
45
|
+
SERVER_ERROR = [500, 'Internal server error']
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Faye
|
2
|
+
module Logging
|
3
|
+
|
4
|
+
DEFAULT_LOG_LEVEL = :error
|
5
|
+
|
6
|
+
LOG_LEVELS = {
|
7
|
+
:error => 3,
|
8
|
+
:warn => 2,
|
9
|
+
:info => 1,
|
10
|
+
:debug => 0
|
11
|
+
}
|
12
|
+
|
13
|
+
class << self
|
14
|
+
attr_writer :log_level
|
15
|
+
|
16
|
+
def log_level
|
17
|
+
@log_level || DEFAULT_LOG_LEVEL
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_writer :log_level
|
22
|
+
|
23
|
+
def log_level
|
24
|
+
@log_level || Logging.log_level
|
25
|
+
end
|
26
|
+
|
27
|
+
def log(message_args, level)
|
28
|
+
return unless Faye.logger
|
29
|
+
return if LOG_LEVELS[log_level] > LOG_LEVELS[level]
|
30
|
+
|
31
|
+
message = message_args.shift.gsub(/\?/) do
|
32
|
+
Faye.to_json(message_args.shift)
|
33
|
+
end
|
34
|
+
|
35
|
+
timestamp = Time.now.strftime('%Y-%m-%d %H:%M:%S')
|
36
|
+
banner = " [#{ level.to_s.upcase }] [#{ self.class.name }] "
|
37
|
+
|
38
|
+
Faye.logger.call(timestamp + banner + message)
|
39
|
+
end
|
40
|
+
|
41
|
+
LOG_LEVELS.each do |level, value|
|
42
|
+
define_method(level) { |*args| log(args, level) }
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Faye
|
2
|
+
module Publisher
|
3
|
+
|
4
|
+
def count_listeners(event_type)
|
5
|
+
return 0 unless @subscribers and @subscribers[event_type]
|
6
|
+
@subscribers[event_type].size
|
7
|
+
end
|
8
|
+
|
9
|
+
def bind(event_type, &listener)
|
10
|
+
@subscribers ||= {}
|
11
|
+
list = @subscribers[event_type] ||= []
|
12
|
+
list << listener
|
13
|
+
end
|
14
|
+
|
15
|
+
def unbind(event_type, &listener)
|
16
|
+
return unless @subscribers and @subscribers[event_type]
|
17
|
+
return @subscribers.delete(event_type) unless listener
|
18
|
+
|
19
|
+
@subscribers[event_type].delete_if(&listener.method(:==))
|
20
|
+
end
|
21
|
+
|
22
|
+
def trigger(event_type, *args)
|
23
|
+
return unless @subscribers and @subscribers[event_type]
|
24
|
+
listeners = @subscribers[event_type].dup
|
25
|
+
listeners.each { |listener| listener.call(*args) }
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Faye
|
2
|
+
module Timeouts
|
3
|
+
def add_timeout(name, delay, &block)
|
4
|
+
Engine.ensure_reactor_running!
|
5
|
+
@timeouts ||= {}
|
6
|
+
return if @timeouts.has_key?(name)
|
7
|
+
@timeouts[name] = EventMachine.add_timer(delay) do
|
8
|
+
@timeouts.delete(name)
|
9
|
+
block.call
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def remove_timeout(name)
|
14
|
+
@timeouts ||= {}
|
15
|
+
timeout = @timeouts[name]
|
16
|
+
return if timeout.nil?
|
17
|
+
EventMachine.cancel_timer(timeout)
|
18
|
+
@timeouts.delete(name)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
@@ -0,0 +1,124 @@
|
|
1
|
+
module Faye
|
2
|
+
class Channel
|
3
|
+
|
4
|
+
include Publisher
|
5
|
+
attr_reader :name
|
6
|
+
|
7
|
+
def initialize(name)
|
8
|
+
@name = name
|
9
|
+
end
|
10
|
+
|
11
|
+
def <<(message)
|
12
|
+
trigger(:message, message)
|
13
|
+
end
|
14
|
+
|
15
|
+
def unused?
|
16
|
+
count_listeners(:message).zero?
|
17
|
+
end
|
18
|
+
|
19
|
+
HANDSHAKE = '/meta/handshake'
|
20
|
+
CONNECT = '/meta/connect'
|
21
|
+
SUBSCRIBE = '/meta/subscribe'
|
22
|
+
UNSUBSCRIBE = '/meta/unsubscribe'
|
23
|
+
DISCONNECT = '/meta/disconnect'
|
24
|
+
|
25
|
+
META = 'meta'
|
26
|
+
SERVICE = 'service'
|
27
|
+
|
28
|
+
class << self
|
29
|
+
def expand(name)
|
30
|
+
segments = parse(name)
|
31
|
+
channels = ['/**', name]
|
32
|
+
|
33
|
+
copy = segments.dup
|
34
|
+
copy[copy.size - 1] = '*'
|
35
|
+
channels << unparse(copy)
|
36
|
+
|
37
|
+
1.upto(segments.size - 1) do |i|
|
38
|
+
copy = segments[0...i]
|
39
|
+
copy << '**'
|
40
|
+
channels << unparse(copy)
|
41
|
+
end
|
42
|
+
|
43
|
+
channels
|
44
|
+
end
|
45
|
+
|
46
|
+
def valid?(name)
|
47
|
+
Grammar::CHANNEL_NAME =~ name or
|
48
|
+
Grammar::CHANNEL_PATTERN =~ name
|
49
|
+
end
|
50
|
+
|
51
|
+
def parse(name)
|
52
|
+
return nil unless valid?(name)
|
53
|
+
name.split('/')[1..-1]
|
54
|
+
end
|
55
|
+
|
56
|
+
def unparse(segments)
|
57
|
+
'/' + segments.join('/')
|
58
|
+
end
|
59
|
+
|
60
|
+
def meta?(name)
|
61
|
+
segments = parse(name)
|
62
|
+
segments ? (segments.first == META) : nil
|
63
|
+
end
|
64
|
+
|
65
|
+
def service?(name)
|
66
|
+
segments = parse(name)
|
67
|
+
segments ? (segments.first == SERVICE) : nil
|
68
|
+
end
|
69
|
+
|
70
|
+
def subscribable?(name)
|
71
|
+
return nil unless valid?(name)
|
72
|
+
not meta?(name) and not service?(name)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class Set
|
77
|
+
def initialize(parent = nil, value = nil)
|
78
|
+
@channels = {}
|
79
|
+
end
|
80
|
+
|
81
|
+
def keys
|
82
|
+
@channels.keys
|
83
|
+
end
|
84
|
+
|
85
|
+
def remove(name)
|
86
|
+
@channels.delete(name)
|
87
|
+
end
|
88
|
+
|
89
|
+
def has_subscription?(name)
|
90
|
+
@channels.has_key?(name)
|
91
|
+
end
|
92
|
+
|
93
|
+
def subscribe(names, callback)
|
94
|
+
return unless callback
|
95
|
+
names.each do |name|
|
96
|
+
channel = @channels[name] ||= Channel.new(name)
|
97
|
+
channel.bind(:message, &callback)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def unsubscribe(name, callback)
|
102
|
+
channel = @channels[name]
|
103
|
+
return false unless channel
|
104
|
+
channel.unbind(:message, &callback)
|
105
|
+
if channel.unused?
|
106
|
+
remove(name)
|
107
|
+
true
|
108
|
+
else
|
109
|
+
false
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def distribute_message(message)
|
114
|
+
channels = Channel.expand(message['channel'])
|
115
|
+
channels.each do |name|
|
116
|
+
channel = @channels[name]
|
117
|
+
channel.trigger(:message, message['data']) if channel
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|