faye 0.5.5 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of faye might be problematic. Click here for more details.
- data/History.txt +14 -0
- data/README.rdoc +98 -0
- data/Rakefile +17 -15
- data/lib/faye-browser-min.js +1 -1
- data/lib/faye.rb +14 -5
- data/lib/faye/adapters/rack_adapter.rb +12 -5
- data/lib/faye/engines/base.rb +62 -0
- data/lib/faye/engines/connection.rb +63 -0
- data/lib/faye/engines/memory.rb +89 -0
- data/lib/faye/engines/redis.rb +141 -0
- data/lib/faye/error.rb +16 -4
- data/lib/faye/mixins/publisher.rb +6 -0
- data/lib/faye/protocol/channel.rb +34 -86
- data/lib/faye/protocol/client.rb +36 -52
- data/lib/faye/protocol/extensible.rb +3 -0
- data/lib/faye/protocol/server.rb +119 -169
- data/lib/faye/transport/http.rb +45 -0
- data/lib/faye/transport/local.rb +15 -0
- data/lib/faye/{network → transport}/transport.rb +36 -49
- data/spec/browser.html +35 -0
- data/spec/install.sh +48 -0
- data/spec/javascript/channel_spec.js +15 -0
- data/spec/javascript/client_spec.js +610 -0
- data/spec/javascript/engine_spec.js +319 -0
- data/spec/javascript/faye_spec.js +15 -0
- data/spec/javascript/grammar_spec.js +66 -0
- data/spec/javascript/node_adapter_spec.js +276 -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 +153 -0
- data/spec/javascript/server/subscribe_spec.js +245 -0
- data/spec/javascript/server/unsubscribe_spec.js +245 -0
- data/spec/javascript/server_spec.js +146 -0
- data/spec/javascript/transport_spec.js +130 -0
- data/spec/node.js +34 -0
- data/spec/ruby/channel_spec.rb +17 -0
- data/spec/ruby/client_spec.rb +615 -0
- data/spec/ruby/engine_spec.rb +312 -0
- data/spec/ruby/faye_spec.rb +14 -0
- data/spec/ruby/grammar_spec.rb +68 -0
- data/spec/ruby/rack_adapter_spec.rb +209 -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 +69 -0
- data/spec/ruby/server/handshake_spec.rb +151 -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 +138 -0
- data/spec/ruby/transport_spec.rb +128 -0
- data/spec/spec_helper.rb +5 -0
- data/spec/testswarm.pl +200 -0
- data/spec/thin_proxy.rb +36 -0
- metadata +119 -84
- data/Manifest.txt +0 -27
- data/README.txt +0 -98
- data/lib/faye/protocol/connection.rb +0 -111
- data/test/scenario.rb +0 -172
- data/test/test_channel.rb +0 -54
- data/test/test_clients.rb +0 -381
- data/test/test_grammar.rb +0 -86
- data/test/test_server.rb +0 -488
@@ -0,0 +1,15 @@
|
|
1
|
+
module Faye
|
2
|
+
|
3
|
+
class Transport::Local < Transport
|
4
|
+
def self.usable?(endpoint)
|
5
|
+
endpoint.is_a?(Server)
|
6
|
+
end
|
7
|
+
|
8
|
+
def request(message, timeout)
|
9
|
+
@endpoint.process(message, true) { |responses| receive(responses) }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
Transport.register 'in-process', Transport::Local
|
14
|
+
|
15
|
+
end
|
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'em-http'
|
2
1
|
require 'json'
|
3
2
|
require 'uri'
|
4
3
|
|
@@ -6,21 +5,51 @@ module Faye
|
|
6
5
|
class Transport
|
7
6
|
|
8
7
|
include Logging
|
8
|
+
include Timeouts
|
9
9
|
|
10
10
|
def initialize(client, endpoint)
|
11
11
|
debug('Created new ? transport for ?', connection_type, endpoint)
|
12
|
-
@client
|
13
|
-
@endpoint
|
12
|
+
@client = client
|
13
|
+
@endpoint = endpoint
|
14
|
+
@outbox = []
|
15
|
+
end
|
16
|
+
|
17
|
+
def batching?
|
18
|
+
true
|
14
19
|
end
|
15
20
|
|
16
21
|
def connection_type
|
17
22
|
self.class.connection_type
|
18
23
|
end
|
19
24
|
|
20
|
-
def send(
|
21
|
-
|
22
|
-
|
23
|
-
request(
|
25
|
+
def send(message, timeout)
|
26
|
+
debug('Client ? sending message to ?: ?', @client.client_id, @endpoint, message)
|
27
|
+
|
28
|
+
return request([message], timeout) unless batching?
|
29
|
+
|
30
|
+
@outbox << message
|
31
|
+
@timeout = timeout
|
32
|
+
|
33
|
+
return flush if message['channel'] == Channel::HANDSHAKE
|
34
|
+
|
35
|
+
if message['channel'] == Channel::CONNECT
|
36
|
+
@connection_message = message
|
37
|
+
end
|
38
|
+
|
39
|
+
add_timeout(:publish, Engine::MAX_DELAY) { flush }
|
40
|
+
end
|
41
|
+
|
42
|
+
def flush
|
43
|
+
remove_timeout(:publish)
|
44
|
+
|
45
|
+
if @outbox.size > 1 and @connection_message
|
46
|
+
@connection_message['advice'] = {'timeout' => 0}
|
47
|
+
end
|
48
|
+
|
49
|
+
request(@outbox, @timeout)
|
50
|
+
|
51
|
+
@connection_message = nil
|
52
|
+
@outbox = []
|
24
53
|
end
|
25
54
|
|
26
55
|
def receive(responses)
|
@@ -64,49 +93,7 @@ module Faye
|
|
64
93
|
@transports.map { |t| t.first }
|
65
94
|
end
|
66
95
|
end
|
67
|
-
end
|
68
|
-
|
69
|
-
class HttpTransport < Transport
|
70
|
-
def self.usable?(endpoint)
|
71
|
-
endpoint.is_a?(String)
|
72
|
-
end
|
73
|
-
|
74
|
-
def request(message, timeout)
|
75
|
-
retry_block = retry_block(message, timeout)
|
76
|
-
|
77
|
-
content = JSON.unparse(message)
|
78
|
-
params = {
|
79
|
-
:head => {
|
80
|
-
'Content-Type' => 'application/json',
|
81
|
-
'host' => URI.parse(@endpoint).host,
|
82
|
-
'Content-Length' => content.length
|
83
|
-
},
|
84
|
-
:body => content,
|
85
|
-
:timeout => -1
|
86
|
-
}
|
87
|
-
request = EventMachine::HttpRequest.new(@endpoint).post(params)
|
88
|
-
request.callback do
|
89
|
-
begin
|
90
|
-
receive(JSON.parse(request.response))
|
91
|
-
rescue
|
92
|
-
retry_block.call
|
93
|
-
end
|
94
|
-
end
|
95
|
-
request.errback { retry_block.call }
|
96
|
-
end
|
97
|
-
end
|
98
|
-
Transport.register 'long-polling', HttpTransport
|
99
|
-
|
100
|
-
class LocalTransport < Transport
|
101
|
-
def self.usable?(endpoint)
|
102
|
-
endpoint.is_a?(Server)
|
103
|
-
end
|
104
96
|
|
105
|
-
def request(message, timeout)
|
106
|
-
@endpoint.process(message, true) { |responses| receive(responses) }
|
107
|
-
end
|
108
97
|
end
|
109
|
-
Transport.register 'in-process', LocalTransport
|
110
|
-
|
111
98
|
end
|
112
99
|
|
data/spec/browser.html
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
<!doctype html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
5
|
+
<title>Faye test suite</title>
|
6
|
+
<script type="text/javascript" src="../vendor/js.class/build/min/loader.js"></script>
|
7
|
+
</head>
|
8
|
+
<body>
|
9
|
+
<script type="text/javascript">
|
10
|
+
|
11
|
+
JS.Packages(function() { with(this) {
|
12
|
+
file('../build/faye-browser-min.js').provides('Faye')
|
13
|
+
autoload(/.*Spec/, {from: './javascript'})
|
14
|
+
}})
|
15
|
+
|
16
|
+
JS.require('Faye', 'JS.Test', 'JS.Range', function() {
|
17
|
+
JS.Test.Unit.Assertions.include({
|
18
|
+
assertYield: function(expected) {
|
19
|
+
var testcase = this
|
20
|
+
return function(actual) { testcase.assertEqual(expected, actual) }
|
21
|
+
}
|
22
|
+
})
|
23
|
+
|
24
|
+
JS.require( 'FayeSpec',
|
25
|
+
'GrammarSpec',
|
26
|
+
'ChannelSpec',
|
27
|
+
'ClientSpec',
|
28
|
+
'TransportSpec',
|
29
|
+
JS.Test.method('autorun'))
|
30
|
+
})
|
31
|
+
|
32
|
+
</script>
|
33
|
+
</body>
|
34
|
+
</html>
|
35
|
+
|
data/spec/install.sh
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# This script installs all the necessary software to run the Ruby and
|
2
|
+
# Node versions of Faye. Tested on Ubuntu 10.04 LTS 64-bit EC2 image:
|
3
|
+
# http://uec-images.ubuntu.com/releases/10.04/release/
|
4
|
+
|
5
|
+
FAYE_BRANCH=extract-engine
|
6
|
+
NODE_VERSION=0.4.7
|
7
|
+
REDIS_VERSION=2.2.7
|
8
|
+
RUBY_VERSION=1.9.2
|
9
|
+
|
10
|
+
sudo apt-get update
|
11
|
+
sudo apt-get install build-essential g++ git-core \
|
12
|
+
openssl libcurl4-openssl-dev libreadline-dev \
|
13
|
+
curl wget
|
14
|
+
|
15
|
+
bash < <(curl -s https://rvm.beginrescueend.com/install/rvm)
|
16
|
+
echo "source \"\$HOME/.rvm/scripts/rvm\"" | tee -a ~/.bashrc
|
17
|
+
source ~/.rvm/scripts/rvm
|
18
|
+
rvm install 1.9.2
|
19
|
+
rvm --default use 1.9.2
|
20
|
+
|
21
|
+
echo "install: --no-rdoc --no-ri
|
22
|
+
update: --no-rdoc --no-ri" | tee ~/.gemrc
|
23
|
+
gem install rake bundler
|
24
|
+
|
25
|
+
cd
|
26
|
+
git clone git://github.com/creationix/nvm.git ~/.nvm
|
27
|
+
. ~/.nvm/nvm.sh
|
28
|
+
echo ". ~/.nvm/nvm.sh" | tee -a ~/.bashrc
|
29
|
+
nvm install v$NODE_VERSION
|
30
|
+
nvm use v$NODE_VERSION
|
31
|
+
npm install redis
|
32
|
+
|
33
|
+
cd /usr/src
|
34
|
+
sudo wget http://redis.googlecode.com/files/redis-$REDIS_VERSION.tar.gz
|
35
|
+
sudo tar zxvf redis-$REDIS_VERSION.tar.gz
|
36
|
+
cd redis-$REDIS_VERSION
|
37
|
+
sudo make
|
38
|
+
sudo ln -s /usr/src/redis-$REDIS_VERSION/src/redis-server /usr/bin/redis-server
|
39
|
+
sudo ln -s /usr/src/redis-$REDIS_VERSION/src/redis-cli /usr/bin/redis-cli
|
40
|
+
|
41
|
+
cd
|
42
|
+
git clone git://github.com/jcoglan/faye.git
|
43
|
+
cd faye
|
44
|
+
git checkout $FAYE_BRANCH
|
45
|
+
git submodule update --init --recursive
|
46
|
+
bundle install
|
47
|
+
cd vendor/js.class && jake
|
48
|
+
cd ../.. && jake
|
@@ -0,0 +1,15 @@
|
|
1
|
+
JS.ENV.ChannelSpec = JS.Test.describe("Channel", function() { with(this) {
|
2
|
+
describe("expand", function() { with(this) {
|
3
|
+
it("returns all patterns that match a channel", function() { with(this) {
|
4
|
+
|
5
|
+
assertEqual( ["/**", "/foo", "/*"],
|
6
|
+
Faye.Channel.expand("/foo") )
|
7
|
+
|
8
|
+
assertEqual( ["/**", "/foo/bar", "/foo/*", "/foo/**"],
|
9
|
+
Faye.Channel.expand("/foo/bar") )
|
10
|
+
|
11
|
+
assertEqual( ["/**", "/foo/bar/qux", "/foo/bar/*", "/foo/**", "/foo/bar/**"],
|
12
|
+
Faye.Channel.expand("/foo/bar/qux") )
|
13
|
+
}})
|
14
|
+
}})
|
15
|
+
}})
|
@@ -0,0 +1,610 @@
|
|
1
|
+
JS.ENV.ClientSpec = JS.Test.describe("Client", function() { with(this) {
|
2
|
+
before(function() { with(this) {
|
3
|
+
this.transport = {connectionType: "fake", send: function() {}}
|
4
|
+
stub(Faye.Transport, "get").yields([transport])
|
5
|
+
}})
|
6
|
+
|
7
|
+
before(function() { with(this) {
|
8
|
+
stub("setTimeout")
|
9
|
+
}})
|
10
|
+
|
11
|
+
define("stubResponse", function(response) { with(this) {
|
12
|
+
stub(transport, "send", function(message) {
|
13
|
+
response.id = message.id
|
14
|
+
client.receiveMessage(response)
|
15
|
+
})
|
16
|
+
}})
|
17
|
+
|
18
|
+
define("createClient", function() { with(this) {
|
19
|
+
this.client = new Faye.Client("http://localhost/")
|
20
|
+
}})
|
21
|
+
|
22
|
+
define("createConnectedClient", function() { with(this) {
|
23
|
+
createClient()
|
24
|
+
stubResponse({channel: "/meta/handshake",
|
25
|
+
successful: true,
|
26
|
+
version: "1.0",
|
27
|
+
supportedConnectionTypes: ["websocket"],
|
28
|
+
clientId: "fakeid" })
|
29
|
+
|
30
|
+
client.handshake()
|
31
|
+
}})
|
32
|
+
|
33
|
+
define("subscribe", function(client, channel, callback) { with(this) {
|
34
|
+
stubResponse({channel: "/meta/subscribe",
|
35
|
+
successful: true,
|
36
|
+
clientId: "fakeid",
|
37
|
+
subscription: channel })
|
38
|
+
|
39
|
+
this.subsCalled = 0
|
40
|
+
callback = callback || function() { subsCalled += 1 }
|
41
|
+
client.subscribe(channel, callback)
|
42
|
+
}})
|
43
|
+
|
44
|
+
describe("initialize", function() { with(this) {
|
45
|
+
it("creates a transport the server must support", function() { with(this) {
|
46
|
+
expect(Faye.Transport, "get").given(instanceOf(Faye.Client),
|
47
|
+
["long-polling", "callback-polling", "in-process"])
|
48
|
+
.yielding([transport])
|
49
|
+
new Faye.Client("http://localhost/")
|
50
|
+
}})
|
51
|
+
|
52
|
+
it("puts the client in the UNCONNECTED state", function() { with(this) {
|
53
|
+
stub(Faye.Transport, "get")
|
54
|
+
var client = new Faye.Client("http://localhost/")
|
55
|
+
assertEqual( "UNCONNECTED", client.getState() )
|
56
|
+
}})
|
57
|
+
}})
|
58
|
+
|
59
|
+
describe("handshake", function() { with(this) {
|
60
|
+
before(function() { this.createClient() })
|
61
|
+
|
62
|
+
it("sends a handshake message to the server", function() { with(this) {
|
63
|
+
expect(transport, "send").given({
|
64
|
+
channel: "/meta/handshake",
|
65
|
+
version: "1.0",
|
66
|
+
supportedConnectionTypes: ["fake"],
|
67
|
+
id: instanceOf("string")
|
68
|
+
}, 60)
|
69
|
+
client.handshake()
|
70
|
+
}})
|
71
|
+
|
72
|
+
it("puts the client in the CONNECTING state", function() { with(this) {
|
73
|
+
stub(transport, "send")
|
74
|
+
client.handshake()
|
75
|
+
assertEqual( "CONNECTING", client.getState() )
|
76
|
+
}})
|
77
|
+
|
78
|
+
describe("with an outgoing extension installed", function() { with(this) {
|
79
|
+
before(function() { with(this) {
|
80
|
+
var extension = {
|
81
|
+
outgoing: function(message, callback) {
|
82
|
+
message.ext = {auth: "password"}
|
83
|
+
callback(message)
|
84
|
+
}
|
85
|
+
}
|
86
|
+
client.addExtension(extension)
|
87
|
+
}})
|
88
|
+
|
89
|
+
it("passes the handshake message through the extension", function() { with(this) {
|
90
|
+
expect(transport, "send").given({
|
91
|
+
channel: "/meta/handshake",
|
92
|
+
version: "1.0",
|
93
|
+
supportedConnectionTypes: ["fake"],
|
94
|
+
id: instanceOf("string"),
|
95
|
+
ext: {auth: "password"}
|
96
|
+
}, 60)
|
97
|
+
client.handshake()
|
98
|
+
}})
|
99
|
+
}})
|
100
|
+
|
101
|
+
describe("on successful response", function() { with(this) {
|
102
|
+
before(function() { with(this) {
|
103
|
+
stubResponse({channel: "/meta/handshake",
|
104
|
+
successful: true,
|
105
|
+
version: "1.0",
|
106
|
+
supportedConnectionTypes: ["websocket"],
|
107
|
+
clientId: "fakeid" })
|
108
|
+
}})
|
109
|
+
|
110
|
+
it("stores the clientId", function() { with(this) {
|
111
|
+
client.handshake()
|
112
|
+
assertEqual( "fakeid", client.getClientId() )
|
113
|
+
}})
|
114
|
+
|
115
|
+
it("puts the client in the CONNECTED state", function() { with(this) {
|
116
|
+
client.handshake()
|
117
|
+
assertEqual( "CONNECTED", client.getState() )
|
118
|
+
}})
|
119
|
+
|
120
|
+
it("selects a new transport based on what the server supports", function() { with(this) {
|
121
|
+
expect(Faye.Transport, "get").given(instanceOf(Faye.Client), ["websocket"])
|
122
|
+
.yielding([transport])
|
123
|
+
client.handshake()
|
124
|
+
}})
|
125
|
+
|
126
|
+
it("registers any pre-existing subscriptions", function() { with(this) {
|
127
|
+
expect(client, "subscribe").given([], true)
|
128
|
+
client.handshake()
|
129
|
+
}})
|
130
|
+
}})
|
131
|
+
|
132
|
+
describe("on unsuccessful response", function() { with(this) {
|
133
|
+
before(function() { with(this) {
|
134
|
+
stubResponse({channel: "/meta/handshake",
|
135
|
+
successful: false,
|
136
|
+
version: "1.0",
|
137
|
+
supportedConnectionTypes: ["websocket"] })
|
138
|
+
}})
|
139
|
+
|
140
|
+
it("schedules a retry", function() { with(this) {
|
141
|
+
expect("setTimeout")
|
142
|
+
client.handshake()
|
143
|
+
}})
|
144
|
+
|
145
|
+
it("puts the client in the UNCONNECTED state", function() { with(this) {
|
146
|
+
stub("setTimeout")
|
147
|
+
client.handshake()
|
148
|
+
assertEqual( "UNCONNECTED", client.getState() )
|
149
|
+
}})
|
150
|
+
}})
|
151
|
+
|
152
|
+
describe("with existing subscriptions after a server restart", function() { with(this) {
|
153
|
+
before(function() { with(this) {
|
154
|
+
createConnectedClient()
|
155
|
+
|
156
|
+
this.message = null
|
157
|
+
subscribe(client, "/messages/foo", function(m) { message = m })
|
158
|
+
|
159
|
+
client.receiveMessage({advice: {reconnect: "handshake"}})
|
160
|
+
|
161
|
+
stubResponse({channel: "/meta/handshake",
|
162
|
+
successful: true,
|
163
|
+
version: "1.0",
|
164
|
+
supportedConnectionTypes: ["websocket"],
|
165
|
+
clientId: "reconnectid" })
|
166
|
+
}})
|
167
|
+
|
168
|
+
it("resends the subscriptions to the server", function() { with(this) {
|
169
|
+
expect(transport, "send").given({
|
170
|
+
channel: "/meta/subscribe",
|
171
|
+
clientId: "reconnectid",
|
172
|
+
subscription: "/messages/foo",
|
173
|
+
id: instanceOf("string")
|
174
|
+
}, 60)
|
175
|
+
client.handshake()
|
176
|
+
}})
|
177
|
+
|
178
|
+
it("retains the listeners for the subscriptions", function() { with(this) {
|
179
|
+
client.handshake()
|
180
|
+
client.receiveMessage({channel: "/messages/foo", "data": "ok"})
|
181
|
+
assertEqual( "ok", message )
|
182
|
+
}})
|
183
|
+
}})
|
184
|
+
|
185
|
+
describe("with a connected client", function() { with(this) {
|
186
|
+
before(function() { this.createConnectedClient() })
|
187
|
+
|
188
|
+
it("does not send a handshake message to the server", function() { with(this) {
|
189
|
+
expect(transport, "send").given({
|
190
|
+
channel: "/meta/handshake",
|
191
|
+
version: "1.0",
|
192
|
+
supportedConnectionTypes: ["fake"],
|
193
|
+
id: instanceOf("string")
|
194
|
+
}, 60)
|
195
|
+
.exactly(0)
|
196
|
+
|
197
|
+
client.handshake()
|
198
|
+
}})
|
199
|
+
}})
|
200
|
+
}})
|
201
|
+
|
202
|
+
describe("connect", function() { with(this) {
|
203
|
+
describe("with an unconnected client", function() { with(this) {
|
204
|
+
before(function() { with(this) {
|
205
|
+
stubResponse({channel: "/meta/handshake",
|
206
|
+
successful: true,
|
207
|
+
version: "1.0",
|
208
|
+
supportedConnectionTypes: ["websocket"],
|
209
|
+
clientId: "handshakeid" })
|
210
|
+
|
211
|
+
createClient()
|
212
|
+
}})
|
213
|
+
|
214
|
+
it("handshakes before connecting", function() { with(this) {
|
215
|
+
expect(transport, "send").given({
|
216
|
+
channel: "/meta/connect",
|
217
|
+
clientId: "handshakeid",
|
218
|
+
connectionType: "fake",
|
219
|
+
id: instanceOf("string")
|
220
|
+
}, 60)
|
221
|
+
client.connect()
|
222
|
+
}})
|
223
|
+
}})
|
224
|
+
|
225
|
+
describe("with a connected client", function() { with(this) {
|
226
|
+
before(function() { this.createConnectedClient() })
|
227
|
+
|
228
|
+
it("sends a connect message to the server", function() { with(this) {
|
229
|
+
expect(transport, "send").given({
|
230
|
+
channel: "/meta/connect",
|
231
|
+
clientId: "fakeid",
|
232
|
+
connectionType: "fake",
|
233
|
+
id: instanceOf("string")
|
234
|
+
}, 60)
|
235
|
+
client.connect()
|
236
|
+
}})
|
237
|
+
|
238
|
+
it("only opens one connect request at a time", function() { with(this) {
|
239
|
+
expect(transport, "send").given({
|
240
|
+
channel: "/meta/connect",
|
241
|
+
clientId: "fakeid",
|
242
|
+
connectionType: "fake",
|
243
|
+
id: instanceOf("string")
|
244
|
+
}, 60)
|
245
|
+
.exactly(1)
|
246
|
+
|
247
|
+
client.connect()
|
248
|
+
client.connect()
|
249
|
+
}})
|
250
|
+
}})
|
251
|
+
}})
|
252
|
+
|
253
|
+
describe("disconnect", function() { with(this) {
|
254
|
+
before(function() { this.createConnectedClient() })
|
255
|
+
|
256
|
+
it("sends a disconnect message to the server", function() { with(this) {
|
257
|
+
expect(transport, "send").given({
|
258
|
+
channel: "/meta/disconnect",
|
259
|
+
clientId: "fakeid",
|
260
|
+
id: instanceOf("string")
|
261
|
+
}, 60)
|
262
|
+
client.disconnect()
|
263
|
+
}})
|
264
|
+
|
265
|
+
it("puts the client in the DISCONNECTED state", function() { with(this) {
|
266
|
+
client.disconnect()
|
267
|
+
assertEqual( "DISCONNECTED", client.getState() )
|
268
|
+
}})
|
269
|
+
}})
|
270
|
+
|
271
|
+
describe("subscribe", function() { with(this) {
|
272
|
+
before(function() { with(this) {
|
273
|
+
createConnectedClient()
|
274
|
+
this.subscribeMessage = {
|
275
|
+
channel: "/meta/subscribe",
|
276
|
+
clientId: "fakeid",
|
277
|
+
subscription: "/foo",
|
278
|
+
id: instanceOf("string")
|
279
|
+
}
|
280
|
+
}})
|
281
|
+
|
282
|
+
describe("with no prior subscriptions", function() { with(this) {
|
283
|
+
it("sends a subscribe message to the server", function() { with(this) {
|
284
|
+
expect(transport, "send").given(subscribeMessage, 60)
|
285
|
+
client.subscribe("/foo")
|
286
|
+
}})
|
287
|
+
|
288
|
+
// The Bayeux spec says the server should accept a list of subscriptions
|
289
|
+
// in one message but the cometD server doesn't actually support this
|
290
|
+
it("sends multiple subscribe messages if given an array", function() { with(this) {
|
291
|
+
expect(transport, "send").given({
|
292
|
+
channel: "/meta/subscribe",
|
293
|
+
clientId: "fakeid",
|
294
|
+
subscription: "/foo",
|
295
|
+
id: instanceOf("string")
|
296
|
+
}, 60)
|
297
|
+
expect(transport, "send").given({
|
298
|
+
channel: "/meta/subscribe",
|
299
|
+
clientId: "fakeid",
|
300
|
+
subscription: "/bar",
|
301
|
+
id: instanceOf("string")
|
302
|
+
}, 60)
|
303
|
+
client.subscribe(["/foo", "/bar"])
|
304
|
+
}})
|
305
|
+
|
306
|
+
describe("on successful response", function() { with(this) {
|
307
|
+
before(function() { with(this) {
|
308
|
+
stubResponse({channel: "/meta/subscribe",
|
309
|
+
successful: true,
|
310
|
+
clientId: "fakeid",
|
311
|
+
subscription: "/foo/*" })
|
312
|
+
}})
|
313
|
+
|
314
|
+
it("sets up a listener for the subscribed channel", function() { with(this) {
|
315
|
+
var message
|
316
|
+
client.subscribe("/foo/*", function(m) { message = m })
|
317
|
+
client.receiveMessage({channel: "/foo/bar", data: "hi"})
|
318
|
+
assertEqual( "hi", message )
|
319
|
+
}})
|
320
|
+
|
321
|
+
it("does not call the listener for non-matching channels", function() { with(this) {
|
322
|
+
var message
|
323
|
+
client.subscribe("/foo/*", function(m) { message = m })
|
324
|
+
client.receiveMessage({channel: "/bar", data: "hi"})
|
325
|
+
assertEqual( undefined, message )
|
326
|
+
}})
|
327
|
+
|
328
|
+
it("activates the subscription", function() { with(this) {
|
329
|
+
var active = false
|
330
|
+
client.subscribe("/foo/*").callback(function() { active = true })
|
331
|
+
assert( active )
|
332
|
+
}})
|
333
|
+
|
334
|
+
describe("with an incoming extension installed", function() { with(this) {
|
335
|
+
before(function() { with(this) {
|
336
|
+
var extension = {
|
337
|
+
incoming: function(message, callback) {
|
338
|
+
if (message.data) message.data.changed = true
|
339
|
+
callback(message)
|
340
|
+
}
|
341
|
+
}
|
342
|
+
client.addExtension(extension)
|
343
|
+
this.message = null
|
344
|
+
client.subscribe("/foo/*", function(m) { message = m })
|
345
|
+
}})
|
346
|
+
|
347
|
+
it("passes delivered messages through the extension", function() { with(this) {
|
348
|
+
client.receiveMessage({channel: "/foo/bar", data: {hello: "there"}})
|
349
|
+
assertEqual( {hello: "there", changed: true}, message )
|
350
|
+
}})
|
351
|
+
}})
|
352
|
+
|
353
|
+
describe("with an outgoing extension installed", function() { with(this) {
|
354
|
+
before(function() { with(this) {
|
355
|
+
var extension = {
|
356
|
+
outgoing: function(message, callback) {
|
357
|
+
if (message.data) message.data.changed = true
|
358
|
+
callback(message)
|
359
|
+
}
|
360
|
+
}
|
361
|
+
client.addExtension(extension)
|
362
|
+
this.message = null
|
363
|
+
client.subscribe("/foo/*", function(m) { message = m })
|
364
|
+
}})
|
365
|
+
|
366
|
+
it("leaves messages unchanged", function() { with(this) {
|
367
|
+
client.receiveMessage({channel: "/foo/bar", data: {hello: "there"}})
|
368
|
+
assertEqual( {hello: "there"}, message )
|
369
|
+
}})
|
370
|
+
}})
|
371
|
+
|
372
|
+
describe("with an incoming extension that invalidates the response", function() { with(this) {
|
373
|
+
before(function() { with(this) {
|
374
|
+
var extension = {
|
375
|
+
incoming: function(message, callback) {
|
376
|
+
if (message.channel === "/meta/subscribe") message.successful = false
|
377
|
+
callback(message)
|
378
|
+
}
|
379
|
+
}
|
380
|
+
client.addExtension(extension)
|
381
|
+
}})
|
382
|
+
|
383
|
+
it("does not set up a listener for the subscribed channel", function() { with(this) {
|
384
|
+
var message
|
385
|
+
client.subscribe("/foo/*", function(m) { message = m })
|
386
|
+
client.receiveMessage({channel: "/foo/bar", data: "hi"})
|
387
|
+
assertEqual( undefined, message )
|
388
|
+
}})
|
389
|
+
|
390
|
+
it("does not activate the subscription", function() { with(this) {
|
391
|
+
var active = false
|
392
|
+
client.subscribe("/foo/*").callback(function() { active = true })
|
393
|
+
assert( !active )
|
394
|
+
}})
|
395
|
+
}})
|
396
|
+
}})
|
397
|
+
|
398
|
+
describe("on unsuccessful response", function() { with(this) {
|
399
|
+
before(function() { with(this) {
|
400
|
+
stubResponse({channel: "/meta/subscribe",
|
401
|
+
successful: false,
|
402
|
+
error: "403:/meta/foo:Forbidden channel",
|
403
|
+
clientId: "fakeid",
|
404
|
+
subscription: "/meta/foo" })
|
405
|
+
}})
|
406
|
+
|
407
|
+
it("does not set up a listener for the subscribed channel", function() { with(this) {
|
408
|
+
var message
|
409
|
+
client.subscribe("/meta/foo", function(m) { message = m })
|
410
|
+
client.receiveMessage({channel: "/meta/foo", data: "hi"})
|
411
|
+
assertEqual( undefined, message )
|
412
|
+
}})
|
413
|
+
|
414
|
+
it("does not activate the subscription", function() { with(this) {
|
415
|
+
var active = false
|
416
|
+
client.subscribe("/meta/foo").callback(function() { active = true })
|
417
|
+
assert( !active )
|
418
|
+
}})
|
419
|
+
|
420
|
+
it("reports the error through an errback", function() { with(this) {
|
421
|
+
var error = null
|
422
|
+
client.subscribe("/meta/foo").errback(function(e) { error = e })
|
423
|
+
assertEqual( objectIncluding({code: 403, params: ["/meta/foo"], message: "Forbidden channel"}), error )
|
424
|
+
}})
|
425
|
+
}})
|
426
|
+
}})
|
427
|
+
|
428
|
+
describe("with an existing subscription", function() { with(this) {
|
429
|
+
before(function() { with(this) {
|
430
|
+
subscribe(client, "/foo/*")
|
431
|
+
}})
|
432
|
+
|
433
|
+
it("does not send another subscribe message to the server", function() { with(this) {
|
434
|
+
expect(transport, "send").given(subscribeMessage, 60).exactly(0)
|
435
|
+
client.subscribe("/foo/*")
|
436
|
+
}})
|
437
|
+
|
438
|
+
it("sets up another listener on the channel", function() { with(this) {
|
439
|
+
client.subscribe("/foo/*", function() { subsCalled += 1 })
|
440
|
+
client.receiveMessage({channel: "/foo/bar", data: "hi"})
|
441
|
+
assertEqual( 2, subsCalled )
|
442
|
+
}})
|
443
|
+
|
444
|
+
it("activates the subscription", function() { with(this) {
|
445
|
+
var active = false
|
446
|
+
client.subscribe("/foo/*").callback(function() { active = true })
|
447
|
+
assert( active )
|
448
|
+
}})
|
449
|
+
}})
|
450
|
+
}})
|
451
|
+
|
452
|
+
describe("unsubscribe", function() { with(this) {
|
453
|
+
before(function() { with(this) {
|
454
|
+
createConnectedClient()
|
455
|
+
this.unsubscribeMessage = {
|
456
|
+
channel: "/meta/unsubscribe",
|
457
|
+
clientId: "fakeid",
|
458
|
+
subscription: "/foo/*",
|
459
|
+
id: instanceOf("string")
|
460
|
+
}
|
461
|
+
}})
|
462
|
+
|
463
|
+
describe("with no subscriptions", function() { with(this) {
|
464
|
+
it("does not send an unsubscribe message to the server", function() { with(this) {
|
465
|
+
expect(transport, "send").given(unsubscribeMessage, 60).exactly(0)
|
466
|
+
client.unsubscribe("/foo/*")
|
467
|
+
}})
|
468
|
+
}})
|
469
|
+
|
470
|
+
describe("with a single subscription", function() { with(this) {
|
471
|
+
before(function() { with(this) {
|
472
|
+
this.message = null
|
473
|
+
this.listener = function(m) { message = m }
|
474
|
+
subscribe(client, "/foo/*", listener)
|
475
|
+
}})
|
476
|
+
|
477
|
+
it("sends an unsubscribe message to the server", function() { with(this) {
|
478
|
+
expect(transport, "send").given(unsubscribeMessage, 60)
|
479
|
+
client.unsubscribe("/foo/*")
|
480
|
+
}})
|
481
|
+
|
482
|
+
it("removes the listener from the channel", function() { with(this) {
|
483
|
+
client.receiveMessage({channel: "/foo/bar", data: "first"})
|
484
|
+
client.unsubscribe("/foo/*", listener)
|
485
|
+
client.receiveMessage({channel: "/foo/bar", data: "second"})
|
486
|
+
assertEqual( "first", message )
|
487
|
+
}})
|
488
|
+
}})
|
489
|
+
|
490
|
+
describe("with multiple subscriptions to the same channel", function() { with(this) {
|
491
|
+
before(function() { with(this) {
|
492
|
+
this.messages = []
|
493
|
+
this.hey = function(m) { messages.push("hey " + m.text) }
|
494
|
+
this.bye = function(m) { messages.push("bye " + m.text) }
|
495
|
+
subscribe(client, "/foo/*", hey)
|
496
|
+
subscribe(client, "/foo/*", bye)
|
497
|
+
}})
|
498
|
+
|
499
|
+
it("removes one of the listeners from the channel", function() { with(this) {
|
500
|
+
client.receiveMessage({channel: "/foo/bar", data: {text: "you"}})
|
501
|
+
client.unsubscribe("/foo/*", hey)
|
502
|
+
client.receiveMessage({channel: "/foo/bar", data: {text: "you"}})
|
503
|
+
assertEqual( ["hey you", "bye you", "bye you"], messages)
|
504
|
+
}})
|
505
|
+
|
506
|
+
it("does not send an unsubscribe message if one listener is removed", function() { with(this) {
|
507
|
+
expect(transport, "send").given(unsubscribeMessage, 60).exactly(0)
|
508
|
+
client.unsubscribe("/foo/*", bye)
|
509
|
+
}})
|
510
|
+
|
511
|
+
it("sends an unsubscribe message if each listener is removed", function() { with(this) {
|
512
|
+
expect(transport, "send").given(unsubscribeMessage, 60)
|
513
|
+
client.unsubscribe("/foo/*", bye)
|
514
|
+
client.unsubscribe("/foo/*", hey)
|
515
|
+
}})
|
516
|
+
|
517
|
+
it("sends an unsubscribe message if all listeners are removed", function() { with(this) {
|
518
|
+
expect(transport, "send").given(unsubscribeMessage, 60)
|
519
|
+
client.unsubscribe("/foo/*")
|
520
|
+
}})
|
521
|
+
}})
|
522
|
+
|
523
|
+
describe("with multiple subscriptions to different channels", function() { with(this) {
|
524
|
+
before(function() { with(this) {
|
525
|
+
subscribe(client, "/foo")
|
526
|
+
subscribe(client, "/bar")
|
527
|
+
}})
|
528
|
+
|
529
|
+
it("sends multiple unsubscribe messages if given an array", function() { with(this) {
|
530
|
+
expect(transport, "send").given({
|
531
|
+
channel: "/meta/unsubscribe",
|
532
|
+
clientId: "fakeid",
|
533
|
+
subscription: "/foo",
|
534
|
+
id: instanceOf("string")
|
535
|
+
}, 60)
|
536
|
+
expect(transport, "send").given({
|
537
|
+
channel: "/meta/unsubscribe",
|
538
|
+
clientId: "fakeid",
|
539
|
+
subscription: "/bar",
|
540
|
+
id: instanceOf("string")
|
541
|
+
}, 60)
|
542
|
+
client.unsubscribe(["/foo", "/bar"])
|
543
|
+
}})
|
544
|
+
}})
|
545
|
+
}})
|
546
|
+
|
547
|
+
describe("publish", function() { with(this) {
|
548
|
+
before(function() { this.createConnectedClient() })
|
549
|
+
|
550
|
+
it("sends the message to the server with an ID", function() { with(this) {
|
551
|
+
expect(transport, "send").given({
|
552
|
+
channel: "/messages/foo",
|
553
|
+
clientId: "fakeid",
|
554
|
+
data: {hello: "world"},
|
555
|
+
id: instanceOf("string")
|
556
|
+
}, 60)
|
557
|
+
client.publish("/messages/foo", {hello: "world"})
|
558
|
+
}})
|
559
|
+
|
560
|
+
it("throws an error when publishing to an invalid channel", function() { with(this) {
|
561
|
+
expect(transport, "send").given(objectIncluding({channel: "/messages/*"}), 60).exactly(0)
|
562
|
+
assertThrows(Error, function() { client.publish("/messages/*", {hello: "world"}) })
|
563
|
+
}})
|
564
|
+
|
565
|
+
describe("with an outgoing extension installed", function() { with(this) {
|
566
|
+
before(function() { with(this) {
|
567
|
+
var extension = {
|
568
|
+
outgoing: function(message, callback) {
|
569
|
+
message.ext = {auth: "password"}
|
570
|
+
callback(message)
|
571
|
+
}
|
572
|
+
}
|
573
|
+
client.addExtension(extension)
|
574
|
+
}})
|
575
|
+
|
576
|
+
it("passes messages through the extension", function() { with(this) {
|
577
|
+
expect(transport, "send").given({
|
578
|
+
channel: "/messages/foo",
|
579
|
+
clientId: "fakeid",
|
580
|
+
data: {hello: "world"},
|
581
|
+
id: instanceOf("string"),
|
582
|
+
ext: {auth: "password"}
|
583
|
+
}, 60)
|
584
|
+
client.publish("/messages/foo", {hello: "world"})
|
585
|
+
}})
|
586
|
+
}})
|
587
|
+
|
588
|
+
describe("with an incoming extension installed", function() { with(this) {
|
589
|
+
before(function() { with(this) {
|
590
|
+
var extension = {
|
591
|
+
incoming: function(message, callback) {
|
592
|
+
message.ext = {auth: "password"}
|
593
|
+
callback(message)
|
594
|
+
}
|
595
|
+
}
|
596
|
+
client.addExtension(extension)
|
597
|
+
}})
|
598
|
+
|
599
|
+
it("leaves the message unchanged", function() { with(this) {
|
600
|
+
expect(transport, "send").given({
|
601
|
+
channel: "/messages/foo",
|
602
|
+
clientId: "fakeid",
|
603
|
+
data: {hello: "world"},
|
604
|
+
id: instanceOf("string")
|
605
|
+
}, 60)
|
606
|
+
client.publish("/messages/foo", {hello: "world"})
|
607
|
+
}})
|
608
|
+
}})
|
609
|
+
}})
|
610
|
+
}})
|