face-faye 0.8.9
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/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
|
+
|