faye 0.2.2 → 0.3.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.
Potentially problematic release.
This version of faye might be problematic. Click here for more details.
- data/History.txt +11 -0
- data/Manifest.txt +7 -0
- data/README.txt +116 -34
- data/Rakefile +6 -1
- data/build/faye-client-min.js +1 -1
- data/build/faye.js +515 -103
- data/examples/node/app.js +4 -4
- data/examples/node/client.js +23 -0
- data/examples/node/faye-client-min.js +1 -1
- data/examples/node/faye.js +515 -103
- data/examples/rack/client.rb +25 -0
- data/examples/shared/public/index.html +0 -2
- data/jake.yml +5 -2
- data/lib/faye-client-min.js +1 -1
- data/lib/faye.rb +2 -3
- data/lib/faye/channel.rb +14 -13
- data/lib/faye/client.rb +246 -0
- data/lib/faye/connection.rb +18 -17
- data/lib/faye/namespace.rb +16 -0
- data/lib/faye/rack_adapter.rb +12 -2
- data/lib/faye/server.rb +19 -20
- data/lib/faye/transport.rb +103 -0
- data/test/scenario.js +99 -0
- data/test/test_channel.rb +19 -13
- data/test/test_clients.js +177 -0
- data/test/test_server.rb +42 -0
- metadata +23 -6
data/lib/faye/rack_adapter.rb
CHANGED
@@ -9,6 +9,7 @@ module Faye
|
|
9
9
|
ASYNC_RESPONSE = [-1, {}, []].freeze
|
10
10
|
|
11
11
|
DEFAULT_ENDPOINT = '/bayeux'
|
12
|
+
SCRIPT_PATH = File.join(ROOT, 'faye-client-min.js')
|
12
13
|
|
13
14
|
TYPE_JSON = {'Content-Type' => 'text/json'}
|
14
15
|
TYPE_SCRIPT = {'Content-Type' => 'text/javascript'}
|
@@ -23,6 +24,15 @@ module Faye
|
|
23
24
|
@server = Server.new(@options)
|
24
25
|
end
|
25
26
|
|
27
|
+
def get_client
|
28
|
+
@client ||= Client.new(@server)
|
29
|
+
end
|
30
|
+
|
31
|
+
def run(port)
|
32
|
+
handler = Rack::Handler.get('thin')
|
33
|
+
handler.run(self, :Port => port)
|
34
|
+
end
|
35
|
+
|
26
36
|
def call(env)
|
27
37
|
request = Rack::Request.new(env)
|
28
38
|
case request.path_info
|
@@ -40,11 +50,11 @@ module Faye
|
|
40
50
|
response
|
41
51
|
end
|
42
52
|
rescue
|
43
|
-
[400,
|
53
|
+
[400, TYPE_TEXT, 'Bad request']
|
44
54
|
end
|
45
55
|
|
46
56
|
when @script then
|
47
|
-
[200, TYPE_SCRIPT, File.new(
|
57
|
+
[200, TYPE_SCRIPT, File.new(SCRIPT_PATH)]
|
48
58
|
|
49
59
|
else
|
50
60
|
env['faye.server'] = @server
|
data/lib/faye/server.rb
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
module Faye
|
2
2
|
class Server
|
3
3
|
def initialize(options = {})
|
4
|
-
@options
|
5
|
-
@channels
|
6
|
-
@clients
|
4
|
+
@options = options
|
5
|
+
@channels = Channel::Tree.new
|
6
|
+
@clients = {}
|
7
|
+
@namespace = Namespace.new
|
7
8
|
end
|
8
9
|
|
9
10
|
# Notifies the server of stale connections that should be deleted
|
@@ -39,12 +40,6 @@ module Faye
|
|
39
40
|
|
40
41
|
private
|
41
42
|
|
42
|
-
def generate_id
|
43
|
-
id = Faye.random
|
44
|
-
id = Faye.random while @clients.has_key?(id)
|
45
|
-
connection(id).id
|
46
|
-
end
|
47
|
-
|
48
43
|
def connection(id)
|
49
44
|
return @clients[id] if @clients.has_key?(id)
|
50
45
|
client = Connection.new(id, @options)
|
@@ -62,6 +57,8 @@ module Faye
|
|
62
57
|
client_id = message['clientId']
|
63
58
|
channel = message['channel']
|
64
59
|
|
60
|
+
@channels.glob(channel).each { |c| c << message }
|
61
|
+
|
65
62
|
if Channel.meta?(channel)
|
66
63
|
response = __send__(Channel.parse(channel)[1], message, local)
|
67
64
|
|
@@ -82,8 +79,6 @@ module Faye
|
|
82
79
|
|
83
80
|
return callback[[]] if message['clientId'].nil? or Channel.service?(channel)
|
84
81
|
|
85
|
-
@channels.glob(channel).each { |c| c << message }
|
86
|
-
|
87
82
|
callback[ { 'channel' => channel,
|
88
83
|
'successful' => true,
|
89
84
|
'id' => message['id'] } ]
|
@@ -97,23 +92,27 @@ module Faye
|
|
97
92
|
def handshake(message, local = false)
|
98
93
|
response = { 'channel' => Channel::HANDSHAKE,
|
99
94
|
'version' => BAYEUX_VERSION,
|
100
|
-
'supportedConnectionTypes' => CONNECTION_TYPES,
|
101
95
|
'id' => message['id'] }
|
102
96
|
|
103
97
|
response['error'] = Error.parameter_missing('version') if message['version'].nil?
|
104
98
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
99
|
+
unless local
|
100
|
+
response['supportedConnectionTypes'] = CONNECTION_TYPES
|
101
|
+
|
102
|
+
client_conns = message['supportedConnectionTypes']
|
103
|
+
if client_conns
|
104
|
+
common_conns = client_conns.select { |c| CONNECTION_TYPES.include?(c) }
|
105
|
+
response['error'] = Error.conntype_mismatch(*client_conns) if common_conns.empty?
|
106
|
+
else
|
107
|
+
response['error'] = Error.parameter_missing('supportedConnectionTypes')
|
108
|
+
end
|
111
109
|
end
|
112
110
|
|
113
111
|
response['successful'] = response['error'].nil?
|
114
112
|
return response unless response['successful']
|
115
113
|
|
116
|
-
|
114
|
+
client_id = @namespace.generate
|
115
|
+
response['clientId'] = connection(client_id).id
|
117
116
|
response
|
118
117
|
end
|
119
118
|
|
@@ -184,7 +183,7 @@ module Faye
|
|
184
183
|
|
185
184
|
subscription.each do |channel|
|
186
185
|
next if response['error']
|
187
|
-
response['error'] = Error.channel_forbidden(channel) unless Channel.subscribable?(channel)
|
186
|
+
response['error'] = Error.channel_forbidden(channel) unless local or Channel.subscribable?(channel)
|
188
187
|
response['error'] = Error.channel_invalid(channel) unless Channel.valid?(channel)
|
189
188
|
|
190
189
|
next if response['error']
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'em-http'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Faye
|
5
|
+
|
6
|
+
class Transport
|
7
|
+
def initialize(client, endpoint)
|
8
|
+
@client = client
|
9
|
+
@endpoint = endpoint
|
10
|
+
end
|
11
|
+
|
12
|
+
def connection_type
|
13
|
+
self.class.connection_type
|
14
|
+
end
|
15
|
+
|
16
|
+
def send(message, &block)
|
17
|
+
if message.is_a?(Hash) and not message.has_key?('id')
|
18
|
+
message['id'] = @client.namespace.generate
|
19
|
+
end
|
20
|
+
|
21
|
+
request(message) { |responses|
|
22
|
+
if block_given?
|
23
|
+
[responses].flatten.each do |response|
|
24
|
+
|
25
|
+
if message.is_a?(Hash) and response['id'] == message['id']
|
26
|
+
block.call(response)
|
27
|
+
end
|
28
|
+
|
29
|
+
if response['advice']
|
30
|
+
@client.handle_advice(response['advice'])
|
31
|
+
end
|
32
|
+
|
33
|
+
if response['data'] and response['channel']
|
34
|
+
@client.send_to_subscribers(response)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
@transports = {}
|
43
|
+
|
44
|
+
class << self
|
45
|
+
attr_accessor :connection_type
|
46
|
+
|
47
|
+
def get(client, connection_types = nil)
|
48
|
+
endpoint = client.endpoint
|
49
|
+
connection_types ||= supported_connection_types
|
50
|
+
|
51
|
+
candidate_class = @transports.find do |type, klass|
|
52
|
+
connection_types.include?(type) and
|
53
|
+
klass.usable?(endpoint)
|
54
|
+
end
|
55
|
+
|
56
|
+
unless candidate_class
|
57
|
+
raise "Could not find a usable connection type for #{ endpoint }"
|
58
|
+
end
|
59
|
+
|
60
|
+
candidate_class.last.new(client, endpoint)
|
61
|
+
end
|
62
|
+
|
63
|
+
def register(type, klass)
|
64
|
+
@transports[type] = klass
|
65
|
+
klass.connection_type = type
|
66
|
+
end
|
67
|
+
|
68
|
+
def supported_connection_types
|
69
|
+
@transports.keys
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class HttpTransport < Transport
|
75
|
+
def self.usable?(endpoint)
|
76
|
+
endpoint.is_a?(String)
|
77
|
+
end
|
78
|
+
|
79
|
+
def request(message, &block)
|
80
|
+
params = {:message => JSON.unparse(message)}
|
81
|
+
request = EventMachine::HttpRequest.new(@endpoint).post(:body => params, :timeout => -1)
|
82
|
+
request.callback do
|
83
|
+
block.call(JSON.parse(request.response))
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
Transport.register 'long-polling', HttpTransport
|
88
|
+
|
89
|
+
class LocalTransport < Transport
|
90
|
+
def self.usable?(endpoint)
|
91
|
+
endpoint.is_a?(Server)
|
92
|
+
end
|
93
|
+
|
94
|
+
def request(message, &block)
|
95
|
+
@endpoint.process(message, true) do |response|
|
96
|
+
block.call(response)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
Transport.register 'in-process', LocalTransport
|
101
|
+
|
102
|
+
end
|
103
|
+
|
data/test/scenario.js
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
var sys = require('sys'),
|
2
|
+
path = require('path'),
|
3
|
+
http = require('http'),
|
4
|
+
assert = require('assert'),
|
5
|
+
faye = require('../build/faye');
|
6
|
+
|
7
|
+
Scenario = Faye.Class({
|
8
|
+
initialize: function(name, block) {
|
9
|
+
this._name = name;
|
10
|
+
this._block = block;
|
11
|
+
this._clients = {};
|
12
|
+
this._inbox = {};
|
13
|
+
this._pool = 0;
|
14
|
+
},
|
15
|
+
|
16
|
+
run: function() {
|
17
|
+
sys.puts('\n' + this._name);
|
18
|
+
sys.puts('----------------------------------------------------------------');
|
19
|
+
this._block.call(this);
|
20
|
+
},
|
21
|
+
|
22
|
+
server: function(port) {
|
23
|
+
sys.puts('Starting server on port ' + port);
|
24
|
+
this._endpoint = 'http://0.0.0.0:' + port + '/comet';
|
25
|
+
var comet = this._comet = new faye.NodeAdapter({mount: '/comet', timeout: 30});
|
26
|
+
this._server = http.createServer(function(request, response) {
|
27
|
+
comet.call(request, response);
|
28
|
+
});
|
29
|
+
this._server.listen(port);
|
30
|
+
},
|
31
|
+
|
32
|
+
httpClient: function(name, channels) {
|
33
|
+
this._setupClient(new faye.Client(this._endpoint), name, channels);
|
34
|
+
},
|
35
|
+
|
36
|
+
localClient: function(name, channels) {
|
37
|
+
this._setupClient(this._comet.getClient(), name, channels);
|
38
|
+
},
|
39
|
+
|
40
|
+
_setupClient: function(client, name, channels) {
|
41
|
+
Faye.each(channels, function(channel) {
|
42
|
+
sys.puts('Client ' + name + ' subscribing to ' + channel);
|
43
|
+
client.subscribe(channel, function(message) {
|
44
|
+
var box = this._inbox[name];
|
45
|
+
box[channel] = box[channel] || [];
|
46
|
+
box[channel].push(message);
|
47
|
+
}, this);
|
48
|
+
}, this);
|
49
|
+
this._clients[name] = client;
|
50
|
+
this._inbox[name] = {};
|
51
|
+
this._pool += 1;
|
52
|
+
},
|
53
|
+
|
54
|
+
send: function(from, channel, message) {
|
55
|
+
var self = this;
|
56
|
+
setTimeout(function() {
|
57
|
+
var displayMessage = JSON.stringify(message);
|
58
|
+
sys.puts('Client ' + from + ' publishing ' + displayMessage + ' to ' + channel);
|
59
|
+
self._clients[from].publish(channel, message);
|
60
|
+
}, 500);
|
61
|
+
},
|
62
|
+
|
63
|
+
checkInbox: function(expectedInbox) {
|
64
|
+
var self = this;
|
65
|
+
setTimeout(function() {
|
66
|
+
self._checkInbox(expectedInbox);
|
67
|
+
sys.puts('Shutting down server\n');
|
68
|
+
Faye.each(this._clients, function(name, client) { client.disconnect() });
|
69
|
+
self._server.close();
|
70
|
+
Scenario.runNext();
|
71
|
+
}, 1000);
|
72
|
+
},
|
73
|
+
|
74
|
+
_checkInbox: function(expectedInbox) {
|
75
|
+
sys.puts(JSON.stringify(this._inbox));
|
76
|
+
assert.deepEqual(this._inbox, expectedInbox);
|
77
|
+
}
|
78
|
+
});
|
79
|
+
|
80
|
+
Faye.extend(Scenario, {
|
81
|
+
_queue: [],
|
82
|
+
|
83
|
+
enqueue: function(name, block) {
|
84
|
+
this._queue.push(new this(name, block));
|
85
|
+
},
|
86
|
+
|
87
|
+
runNext: function() {
|
88
|
+
this.running = true;
|
89
|
+
var scenario = this._queue.shift();
|
90
|
+
if (!scenario) return;
|
91
|
+
scenario.run();
|
92
|
+
}
|
93
|
+
});
|
94
|
+
|
95
|
+
exports.run = function(name, block) {
|
96
|
+
Scenario.enqueue(name, block);
|
97
|
+
if (!Scenario.running) Scenario.runNext();
|
98
|
+
};
|
99
|
+
|
data/test/test_channel.rb
CHANGED
@@ -16,19 +16,25 @@ class TestChannel < Test::Unit::TestCase
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def test_globbing
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
19
|
+
tree = Channel::Tree.new
|
20
|
+
tree['/foo/bar'] = 1
|
21
|
+
tree['/foo/boo'] = 2
|
22
|
+
tree['/foo'] = 3
|
23
|
+
tree['/foobar'] = 4
|
24
|
+
tree['/foo/bar/boo'] = 5
|
25
|
+
tree['/foobar/boo'] = 6
|
26
|
+
tree['/foo/*'] = 7
|
27
|
+
tree['/foo/**'] = 8
|
28
|
+
|
29
|
+
assert_equal [1,2,7,8], tree.glob('/foo/*').sort
|
30
|
+
assert_equal [1,7,8], tree.glob('/foo/bar').sort
|
31
|
+
assert_equal [1,2,5,7,8], tree.glob('/foo/**').sort
|
32
|
+
assert_equal [5,8], tree.glob('/foo/bar/boo').sort
|
33
|
+
|
34
|
+
tree['/channels/hello'] = 'A'
|
35
|
+
tree['/channels/name'] = 'B'
|
36
|
+
tree['/channels/nested/hello'] = 'C'
|
28
37
|
|
29
|
-
assert_equal
|
30
|
-
assert_equal [1,7,8], globber.glob('/foo/bar').sort
|
31
|
-
assert_equal [1,2,5,7,8], globber.glob('/foo/**').sort
|
32
|
-
assert_equal [5,8], globber.glob('/foo/bar/boo').sort
|
38
|
+
assert_equal %w[A B C], tree.glob('/channels/**').sort
|
33
39
|
end
|
34
40
|
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
var Scenario = require('./scenario'),
|
2
|
+
faye = require('../build/faye'),
|
3
|
+
assert = require('assert');
|
4
|
+
|
5
|
+
(function() {
|
6
|
+
var tree = new Faye.Channel.Tree();
|
7
|
+
var list = '/foo/bar /foo/boo /foo /foobar /foo/bar/boo /foobar/boo /foo/* /foo/**'.split(' ');
|
8
|
+
|
9
|
+
Faye.each(list, function(c, i) { tree.set(c, i + 1) });
|
10
|
+
|
11
|
+
assert.deepEqual(tree.glob('/foo/*').sort(), [1,2,7,8]);
|
12
|
+
assert.deepEqual(tree.glob('/foo/bar').sort(), [1,7,8]);
|
13
|
+
assert.deepEqual(tree.glob('/foo/**').sort(), [1,2,5,7,8]);
|
14
|
+
assert.deepEqual(tree.glob('/foo/bar/boo').sort(), [5,8]);
|
15
|
+
|
16
|
+
tree.set('/channels/hello', 'A');
|
17
|
+
tree.set('/channels/name', 'B');
|
18
|
+
tree.set('/channels/nested/hello', 'C');
|
19
|
+
|
20
|
+
assert.deepEqual(tree.glob('/channels/**').sort(), ['A','B','C']);
|
21
|
+
})();
|
22
|
+
|
23
|
+
Scenario.run("Two HTTP clients, no messages delivered",
|
24
|
+
function() { with(this) {
|
25
|
+
server(8000);
|
26
|
+
httpClient('A', ['/channels/a']);
|
27
|
+
httpClient('B', []);
|
28
|
+
send('B', '/channels/b', {hello: 'world'});
|
29
|
+
checkInbox({
|
30
|
+
A: {},
|
31
|
+
B: {}
|
32
|
+
});
|
33
|
+
}});
|
34
|
+
|
35
|
+
Scenario.run("Two HTTP clients, single subscription",
|
36
|
+
function() { with(this) {
|
37
|
+
server(8000);
|
38
|
+
httpClient('A', ['/channels/a']);
|
39
|
+
httpClient('B', []);
|
40
|
+
send('B', '/channels/a', {hello: 'world'});
|
41
|
+
checkInbox({
|
42
|
+
A: {
|
43
|
+
'/channels/a': [{hello: 'world'}]
|
44
|
+
},
|
45
|
+
B: {}
|
46
|
+
});
|
47
|
+
}});
|
48
|
+
|
49
|
+
Scenario.run("Two HTTP clients, multiple subscriptions",
|
50
|
+
function() { with(this) {
|
51
|
+
server(8000);
|
52
|
+
httpClient('A', ['/channels/a', '/channels/*']);
|
53
|
+
httpClient('B', []);
|
54
|
+
send('B', '/channels/a', {hello: 'world'});
|
55
|
+
checkInbox({
|
56
|
+
A: {
|
57
|
+
'/channels/a': [{hello: 'world'}],
|
58
|
+
'/channels/*': [{hello: 'world'}]
|
59
|
+
},
|
60
|
+
B: {}
|
61
|
+
});
|
62
|
+
}});
|
63
|
+
|
64
|
+
Scenario.run("Three HTTP clients, single receiver",
|
65
|
+
function() { with(this) {
|
66
|
+
server(8000);
|
67
|
+
httpClient('A', ['/channels/a']);
|
68
|
+
httpClient('B', []);
|
69
|
+
httpClient('C', ['/channels/c']);
|
70
|
+
send('B', '/channels/a', {chunky: 'bacon'});
|
71
|
+
checkInbox({
|
72
|
+
A: {
|
73
|
+
'/channels/a': [{chunky: 'bacon'}]
|
74
|
+
},
|
75
|
+
B: {},
|
76
|
+
C: {}
|
77
|
+
});
|
78
|
+
}});
|
79
|
+
|
80
|
+
Scenario.run("Three HTTP clients, multiple receivers",
|
81
|
+
function() { with(this) {
|
82
|
+
server(8000);
|
83
|
+
httpClient('A', ['/channels/shared']);
|
84
|
+
httpClient('B', []);
|
85
|
+
httpClient('C', ['/channels/shared']);
|
86
|
+
send('B', '/channels/shared', {chunky: 'bacon'});
|
87
|
+
checkInbox({
|
88
|
+
A: {
|
89
|
+
'/channels/shared': [{chunky: 'bacon'}]
|
90
|
+
},
|
91
|
+
B: {},
|
92
|
+
C: {
|
93
|
+
'/channels/shared': [{chunky: 'bacon'}]
|
94
|
+
}
|
95
|
+
});
|
96
|
+
}});
|
97
|
+
|
98
|
+
Scenario.run("Two HTTP clients, single wildcard on receiver",
|
99
|
+
function() { with(this) {
|
100
|
+
server(8000);
|
101
|
+
httpClient('A', ['/channels/*']);
|
102
|
+
httpClient('B', []);
|
103
|
+
send('B', '/channels/anything', {msg: 'hey'});
|
104
|
+
checkInbox({
|
105
|
+
A: {
|
106
|
+
'/channels/*': [{msg: 'hey'}]
|
107
|
+
},
|
108
|
+
B: {}
|
109
|
+
});
|
110
|
+
}});
|
111
|
+
|
112
|
+
Scenario.run("Two HTTP clients, single wildcard on sender",
|
113
|
+
function() { with(this) {
|
114
|
+
server(8000);
|
115
|
+
httpClient('A', ['/channels/name', '/channels/hello', '/channels/nested/hello']);
|
116
|
+
httpClient('B', []);
|
117
|
+
send('B', '/channels/*', {msg: 'hey'});
|
118
|
+
checkInbox({
|
119
|
+
A: {
|
120
|
+
'/channels/name': [{msg: 'hey'}],
|
121
|
+
'/channels/hello': [{msg: 'hey'}]
|
122
|
+
},
|
123
|
+
B: {}
|
124
|
+
});
|
125
|
+
}});
|
126
|
+
|
127
|
+
Scenario.run("Two HTTP clients, single wildcard on both",
|
128
|
+
function() { with(this) {
|
129
|
+
server(8000);
|
130
|
+
httpClient('A', ['/channels/*']);
|
131
|
+
httpClient('B', []);
|
132
|
+
send('B', '/channels/*', {msg: 'hey'});
|
133
|
+
checkInbox({
|
134
|
+
A: {
|
135
|
+
'/channels/*': [{msg: 'hey'}]
|
136
|
+
},
|
137
|
+
B: {}
|
138
|
+
});
|
139
|
+
}});
|
140
|
+
|
141
|
+
Scenario.run("Two local clients, double wildcard on sender",
|
142
|
+
function() { with(this) {
|
143
|
+
server(8000);
|
144
|
+
localClient('A', ['/channels/name', '/channels/hello', '/channels/nested/hello']);
|
145
|
+
localClient('B', []);
|
146
|
+
send('B', '/channels/**', {msg: 'hey'});
|
147
|
+
checkInbox({
|
148
|
+
A: {
|
149
|
+
'/channels/name': [{msg: 'hey'}],
|
150
|
+
'/channels/hello': [{msg: 'hey'}],
|
151
|
+
'/channels/nested/hello': [{msg: 'hey'}]
|
152
|
+
},
|
153
|
+
B: {}
|
154
|
+
});
|
155
|
+
}});
|
156
|
+
|
157
|
+
Scenario.run("Two local clients, one HTTP, double wildcard on sender and one subscription",
|
158
|
+
function() { with(this) {
|
159
|
+
server(8000);
|
160
|
+
localClient('A', ['/channels/hello', '/channels/nested/hello']);
|
161
|
+
localClient('B', []);
|
162
|
+
httpClient('C', ['/channels/name', '/channels/foo/**']);
|
163
|
+
send('B', '/channels/**', {msg: 'hey'});
|
164
|
+
checkInbox({
|
165
|
+
A: {
|
166
|
+
'/channels/hello': [{msg: 'hey'}],
|
167
|
+
'/channels/nested/hello': [{msg: 'hey'}]
|
168
|
+
},
|
169
|
+
B: {},
|
170
|
+
C: {
|
171
|
+
'/channels/name': [{msg: 'hey'}],
|
172
|
+
'/channels/foo/**': [{msg: 'hey'}],
|
173
|
+
'/channels/name': [{msg: 'hey'}],
|
174
|
+
}
|
175
|
+
});
|
176
|
+
}});
|
177
|
+
|