_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,22 @@
|
|
1
|
+
module Faye
|
2
|
+
|
3
|
+
class Transport::Local < Transport
|
4
|
+
def self.usable?(endpoint, &callback)
|
5
|
+
callback.call(endpoint.is_a?(Server))
|
6
|
+
end
|
7
|
+
|
8
|
+
def batching?
|
9
|
+
false
|
10
|
+
end
|
11
|
+
|
12
|
+
def request(message, timeout)
|
13
|
+
message = Faye.copy_object(message)
|
14
|
+
@endpoint.process(message, true) do |responses|
|
15
|
+
receive(Faye.copy_object(responses))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
Transport.register 'in-process', Transport::Local
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
module Faye
|
2
|
+
class Transport
|
3
|
+
|
4
|
+
include Logging
|
5
|
+
include Publisher
|
6
|
+
include Timeouts
|
7
|
+
|
8
|
+
attr_accessor :cookies, :headers
|
9
|
+
|
10
|
+
def initialize(client, endpoint)
|
11
|
+
debug('Created new ? transport for ?', connection_type, endpoint)
|
12
|
+
@client = client
|
13
|
+
@endpoint = endpoint
|
14
|
+
@outbox = []
|
15
|
+
end
|
16
|
+
|
17
|
+
def batching?
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
def close
|
22
|
+
end
|
23
|
+
|
24
|
+
def connection_type
|
25
|
+
self.class.connection_type
|
26
|
+
end
|
27
|
+
|
28
|
+
def send(message, timeout)
|
29
|
+
debug('Client ? sending message to ?: ?', @client.client_id, @endpoint, message)
|
30
|
+
|
31
|
+
return request([message], timeout) unless batching?
|
32
|
+
|
33
|
+
@outbox << message
|
34
|
+
@timeout = timeout
|
35
|
+
|
36
|
+
return flush if message['channel'] == Channel::HANDSHAKE
|
37
|
+
|
38
|
+
if message['channel'] == Channel::CONNECT
|
39
|
+
@connection_message = message
|
40
|
+
end
|
41
|
+
|
42
|
+
add_timeout(:publish, Engine::MAX_DELAY) { flush }
|
43
|
+
end
|
44
|
+
|
45
|
+
def flush
|
46
|
+
remove_timeout(:publish)
|
47
|
+
|
48
|
+
if @outbox.size > 1 and @connection_message
|
49
|
+
@connection_message['advice'] = {'timeout' => 0}
|
50
|
+
end
|
51
|
+
|
52
|
+
request(@outbox, @timeout)
|
53
|
+
|
54
|
+
@connection_message = nil
|
55
|
+
@outbox = []
|
56
|
+
end
|
57
|
+
|
58
|
+
def receive(responses)
|
59
|
+
debug('Client ? received from ?: ?', @client.client_id, @endpoint, responses)
|
60
|
+
responses.each { |response| @client.receive_message(response) }
|
61
|
+
end
|
62
|
+
|
63
|
+
def retry_block(message, timeout)
|
64
|
+
lambda do
|
65
|
+
EventMachine.add_timer(@client.retry) { request(message, timeout) }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
@transports = []
|
70
|
+
|
71
|
+
class << self
|
72
|
+
attr_accessor :connection_type
|
73
|
+
|
74
|
+
def get(client, connection_types = nil, &callback)
|
75
|
+
endpoint = client.endpoint
|
76
|
+
connection_types ||= supported_connection_types
|
77
|
+
|
78
|
+
select = lambda do |(conn_type, klass), resume|
|
79
|
+
if connection_types.include?(conn_type)
|
80
|
+
klass.usable?(endpoint) do |is_usable|
|
81
|
+
if is_usable
|
82
|
+
callback.call(klass.new(client, endpoint))
|
83
|
+
else
|
84
|
+
resume.call
|
85
|
+
end
|
86
|
+
end
|
87
|
+
else
|
88
|
+
resume.call
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
error = lambda do
|
93
|
+
raise "Could not find a usable connection type for #{ endpoint }"
|
94
|
+
end
|
95
|
+
|
96
|
+
Faye.async_each(@transports, select, error)
|
97
|
+
end
|
98
|
+
|
99
|
+
def register(type, klass)
|
100
|
+
@transports << [type, klass]
|
101
|
+
klass.connection_type = type
|
102
|
+
end
|
103
|
+
|
104
|
+
def supported_connection_types
|
105
|
+
@transports.map { |t| t.first }
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
%w[local web_socket http].each do |type|
|
110
|
+
require File.join(ROOT, 'faye', 'transport', type)
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Faye
|
2
|
+
|
3
|
+
class Transport::WebSocket < Transport
|
4
|
+
WEBSOCKET_TIMEOUT = 1
|
5
|
+
|
6
|
+
UNCONNECTED = 1
|
7
|
+
CONNECTING = 2
|
8
|
+
CONNECTED = 3
|
9
|
+
|
10
|
+
include EventMachine::Deferrable
|
11
|
+
|
12
|
+
def self.usable?(endpoint, &callback)
|
13
|
+
connected = false
|
14
|
+
called = false
|
15
|
+
socket_url = endpoint.gsub(/^http(s?):/, 'ws\1:')
|
16
|
+
socket = Faye::WebSocket::Client.new(socket_url)
|
17
|
+
|
18
|
+
socket.onopen = lambda do |event|
|
19
|
+
connected = true
|
20
|
+
socket.close
|
21
|
+
callback.call(true)
|
22
|
+
called = true
|
23
|
+
socket = nil
|
24
|
+
end
|
25
|
+
|
26
|
+
notconnected = lambda do |*args|
|
27
|
+
callback.call(false) unless called or connected
|
28
|
+
called = true
|
29
|
+
end
|
30
|
+
|
31
|
+
socket.onclose = socket.onerror = notconnected
|
32
|
+
EventMachine.add_timer(WEBSOCKET_TIMEOUT, ¬connected)
|
33
|
+
end
|
34
|
+
|
35
|
+
def batching?
|
36
|
+
false
|
37
|
+
end
|
38
|
+
|
39
|
+
def request(messages, timeout = nil)
|
40
|
+
return if messages.empty?
|
41
|
+
@messages ||= {}
|
42
|
+
messages.each { |message| @messages[message['id']] = message }
|
43
|
+
with_socket { |socket| socket.send(Faye.to_json(messages)) }
|
44
|
+
end
|
45
|
+
|
46
|
+
def with_socket(&resume)
|
47
|
+
callback(&resume)
|
48
|
+
connect
|
49
|
+
end
|
50
|
+
|
51
|
+
def close
|
52
|
+
return if @closed
|
53
|
+
@closed = true
|
54
|
+
@socket.close if @socket
|
55
|
+
end
|
56
|
+
|
57
|
+
def connect
|
58
|
+
return if @closed
|
59
|
+
|
60
|
+
@state ||= UNCONNECTED
|
61
|
+
return unless @state == UNCONNECTED
|
62
|
+
|
63
|
+
@state = CONNECTING
|
64
|
+
|
65
|
+
@socket = Faye::WebSocket::Client.new(@endpoint.gsub(/^http(s?):/, 'ws\1:'))
|
66
|
+
|
67
|
+
@socket.onopen = lambda do |*args|
|
68
|
+
@state = CONNECTED
|
69
|
+
set_deferred_status(:succeeded, @socket)
|
70
|
+
trigger(:up)
|
71
|
+
end
|
72
|
+
|
73
|
+
@socket.onmessage = lambda do |event|
|
74
|
+
messages = [Yajl::Parser.parse(event.data)].flatten
|
75
|
+
messages.each { |message| @messages.delete(message['id']) }
|
76
|
+
receive(messages)
|
77
|
+
end
|
78
|
+
|
79
|
+
@socket.onclose = lambda do |*args|
|
80
|
+
was_connected = (@state == CONNECTED)
|
81
|
+
set_deferred_status(:deferred)
|
82
|
+
@state = UNCONNECTED
|
83
|
+
@socket = nil
|
84
|
+
|
85
|
+
next resend if was_connected
|
86
|
+
|
87
|
+
EventMachine.add_timer(@client.retry) { connect }
|
88
|
+
trigger(:down)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def resend
|
93
|
+
request(@messages.values)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
Transport.register 'websocket', Transport::WebSocket
|
98
|
+
|
99
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Faye
|
2
|
+
class Namespace
|
3
|
+
|
4
|
+
extend Forwardable
|
5
|
+
def_delegator :@used, :delete, :release
|
6
|
+
def_delegator :@used, :has_key?, :exists?
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@used = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def generate
|
13
|
+
name = Engine.random
|
14
|
+
name = Engine.random while @used.has_key?(name)
|
15
|
+
@used[name] = name
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
data/spec/browser.html
ADDED
@@ -0,0 +1,45 @@
|
|
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="../node_modules/jsclass/min/loader.js"></script>
|
7
|
+
</head>
|
8
|
+
<body>
|
9
|
+
<script type="text/javascript">
|
10
|
+
|
11
|
+
if (typeof TestSwarm === 'undefined')
|
12
|
+
TestSwarm = {
|
13
|
+
submit: function(result) {
|
14
|
+
if (window.console) console.log(Faye.toJSON(result));
|
15
|
+
},
|
16
|
+
heartbeat: function() {}
|
17
|
+
}
|
18
|
+
|
19
|
+
JS.cacheBust = true
|
20
|
+
|
21
|
+
JS.Packages(function() { with(this) {
|
22
|
+
file('../build/faye-browser-min.js').provides('Faye')
|
23
|
+
autoload(/.*Spec/, {from: './javascript'})
|
24
|
+
}})
|
25
|
+
|
26
|
+
JS.require('Faye', 'JS.Test', 'JS.Range', function() {
|
27
|
+
JS.Test.Unit.Assertions.include({
|
28
|
+
assertYield: function(expected) {
|
29
|
+
var testcase = this
|
30
|
+
return function(actual) { testcase.assertEqual(expected, actual) }
|
31
|
+
}
|
32
|
+
})
|
33
|
+
|
34
|
+
JS.require( 'FayeSpec',
|
35
|
+
'GrammarSpec',
|
36
|
+
'ChannelSpec',
|
37
|
+
'ClientSpec',
|
38
|
+
'TransportSpec',
|
39
|
+
JS.Test.method('autorun'))
|
40
|
+
})
|
41
|
+
|
42
|
+
</script>
|
43
|
+
</body>
|
44
|
+
</html>
|
45
|
+
|
data/spec/install.sh
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
# This script installs all the necessary software to run the Ruby and
|
2
|
+
# Node versions of Faye, as well as the load testing tools AB and Tsung.
|
3
|
+
|
4
|
+
# Tested on Ubuntu 10.04 LTS 64-bit EC2 image:
|
5
|
+
# http://uec-images.ubuntu.com/releases/10.04/release/
|
6
|
+
|
7
|
+
FAYE_BRANCH=master
|
8
|
+
NODE_VERSION=0.4.10
|
9
|
+
PHANTOM_VERSION=1.2
|
10
|
+
REDIS_VERSION=2.2.12
|
11
|
+
RUBY_VERSION=1.9.2
|
12
|
+
TSUNG_VERSION=1.3.3
|
13
|
+
|
14
|
+
sudo apt-get update
|
15
|
+
sudo apt-get install build-essential g++ git-core curl wget \
|
16
|
+
openssl libcurl4-openssl-dev libreadline-dev \
|
17
|
+
apache2-utils erlang gnuplot \
|
18
|
+
libqt4-dev qt4-qmake xvfb
|
19
|
+
|
20
|
+
# Install RVM and Ruby
|
21
|
+
bash < <(curl -s https://rvm.beginrescueend.com/install/rvm)
|
22
|
+
echo "source \"\$HOME/.rvm/scripts/rvm\"" | tee -a ~/.bashrc
|
23
|
+
source ~/.rvm/scripts/rvm
|
24
|
+
rvm install $RUBY_VERSION
|
25
|
+
rvm --default use $RUBY_VERSION
|
26
|
+
echo "install: --no-rdoc --no-ri
|
27
|
+
update: --no-rdoc --no-ri" | tee ~/.gemrc
|
28
|
+
gem install rake bundler
|
29
|
+
|
30
|
+
# Install nvm and Node
|
31
|
+
cd ~
|
32
|
+
git clone git://github.com/creationix/nvm.git ~/.nvm
|
33
|
+
. ~/.nvm/nvm.sh
|
34
|
+
echo ". ~/.nvm/nvm.sh" | tee -a ~/.bashrc
|
35
|
+
nvm install v$NODE_VERSION
|
36
|
+
nvm use v$NODE_VERSION
|
37
|
+
|
38
|
+
# Install Redis from source
|
39
|
+
cd /usr/src
|
40
|
+
sudo wget http://redis.googlecode.com/files/redis-$REDIS_VERSION.tar.gz
|
41
|
+
sudo tar zxvf redis-$REDIS_VERSION.tar.gz
|
42
|
+
cd redis-$REDIS_VERSION
|
43
|
+
sudo make
|
44
|
+
sudo ln -s /usr/src/redis-$REDIS_VERSION/src/redis-server /usr/bin/redis-server
|
45
|
+
sudo ln -s /usr/src/redis-$REDIS_VERSION/src/redis-cli /usr/bin/redis-cli
|
46
|
+
|
47
|
+
# Install PhantomJS
|
48
|
+
cd /usr/src
|
49
|
+
sudo git clone git://github.com/ariya/phantomjs.git
|
50
|
+
cd phantomjs
|
51
|
+
sudo git checkout $PHANTOM_VERSION
|
52
|
+
sudo qmake-qt4
|
53
|
+
sudo make
|
54
|
+
sudo ln -s /usr/src/phantomjs/bin/phantomjs /usr/bin/phantomjs
|
55
|
+
echo "To use phantomjs, run DISPLAY=:1 Xvfb :1 -screen 0 1024x768x16"
|
56
|
+
|
57
|
+
# Install Tsung and required Perl modules
|
58
|
+
cd /usr/src
|
59
|
+
sudo wget http://tsung.erlang-projects.org/dist/tsung-$TSUNG_VERSION.tar.gz
|
60
|
+
sudo tar zxvf tsung-$TSUNG_VERSION.tar.gz
|
61
|
+
cd tsung-$TSUNG_VERSION
|
62
|
+
sudo ./configure
|
63
|
+
sudo make
|
64
|
+
sudo make install
|
65
|
+
sudo ln -s /usr/lib/tsung/bin/tsung_stats.pl /usr/bin/tsung-stats
|
66
|
+
echo "To use tsung-stats you need to 'install Template' from CPAN"
|
67
|
+
sudo perl -MCPAN -eshell
|
68
|
+
|
69
|
+
# Check out and build Faye project
|
70
|
+
cd ~
|
71
|
+
git clone git://github.com/faye/faye.git
|
72
|
+
cd faye
|
73
|
+
git checkout $FAYE_BRANCH
|
74
|
+
git submodule update --init --recursive
|
75
|
+
bundle install
|
76
|
+
npm install redis
|
77
|
+
cd vendor/js.class && jake
|
78
|
+
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,714 @@
|
|
1
|
+
JS.ENV.ClientSpec = JS.Test.describe("Client", function() { with(this) {
|
2
|
+
before(function() { with(this) {
|
3
|
+
this.transport = {connectionType: "fake", send: function() {}}
|
4
|
+
Faye.extend(transport, Faye.Publisher)
|
5
|
+
stub(Faye.Transport, "get").yields([transport])
|
6
|
+
}})
|
7
|
+
|
8
|
+
before(function() { with(this) {
|
9
|
+
stub("setTimeout")
|
10
|
+
}})
|
11
|
+
|
12
|
+
define("stubResponse", function(response) { with(this) {
|
13
|
+
stub(transport, "send", function(message) {
|
14
|
+
response.id = message.id
|
15
|
+
client.receiveMessage(response)
|
16
|
+
})
|
17
|
+
}})
|
18
|
+
|
19
|
+
define("createClient", function() { with(this) {
|
20
|
+
this.client = new Faye.Client("http://localhost/")
|
21
|
+
}})
|
22
|
+
|
23
|
+
define("createConnectedClient", function() { with(this) {
|
24
|
+
createClient()
|
25
|
+
stubResponse({channel: "/meta/handshake",
|
26
|
+
successful: true,
|
27
|
+
version: "1.0",
|
28
|
+
supportedConnectionTypes: ["websocket"],
|
29
|
+
clientId: "fakeid" })
|
30
|
+
|
31
|
+
client.handshake()
|
32
|
+
}})
|
33
|
+
|
34
|
+
define("subscribe", function(client, channel, callback) { with(this) {
|
35
|
+
stubResponse({channel: "/meta/subscribe",
|
36
|
+
successful: true,
|
37
|
+
clientId: "fakeid",
|
38
|
+
subscription: channel })
|
39
|
+
|
40
|
+
this.subsCalled = 0
|
41
|
+
callback = callback || function() { subsCalled += 1 }
|
42
|
+
client.subscribe(channel, callback)
|
43
|
+
}})
|
44
|
+
|
45
|
+
describe("initialize", function() { with(this) {
|
46
|
+
it("creates a transport the server must support", function() { with(this) {
|
47
|
+
expect(Faye.Transport, "get").given(instanceOf(Faye.Client),
|
48
|
+
["long-polling", "callback-polling", "in-process"])
|
49
|
+
.yielding([transport])
|
50
|
+
new Faye.Client("http://localhost/")
|
51
|
+
}})
|
52
|
+
|
53
|
+
it("puts the client in the UNCONNECTED state", function() { with(this) {
|
54
|
+
stub(Faye.Transport, "get")
|
55
|
+
var client = new Faye.Client("http://localhost/")
|
56
|
+
assertEqual( "UNCONNECTED", client.getState() )
|
57
|
+
}})
|
58
|
+
}})
|
59
|
+
|
60
|
+
describe("handshake", function() { with(this) {
|
61
|
+
before(function() { this.createClient() })
|
62
|
+
|
63
|
+
it("sends a handshake message to the server", function() { with(this) {
|
64
|
+
expect(transport, "send").given({
|
65
|
+
channel: "/meta/handshake",
|
66
|
+
version: "1.0",
|
67
|
+
supportedConnectionTypes: ["fake"],
|
68
|
+
id: instanceOf("string")
|
69
|
+
}, 60)
|
70
|
+
client.handshake()
|
71
|
+
}})
|
72
|
+
|
73
|
+
it("puts the client in the CONNECTING state", function() { with(this) {
|
74
|
+
stub(transport, "send")
|
75
|
+
client.handshake()
|
76
|
+
assertEqual( "CONNECTING", client.getState() )
|
77
|
+
}})
|
78
|
+
|
79
|
+
describe("with an outgoing extension installed", function() { with(this) {
|
80
|
+
before(function() { with(this) {
|
81
|
+
var extension = {
|
82
|
+
outgoing: function(message, callback) {
|
83
|
+
message.ext = {auth: "password"}
|
84
|
+
callback(message)
|
85
|
+
}
|
86
|
+
}
|
87
|
+
client.addExtension(extension)
|
88
|
+
}})
|
89
|
+
|
90
|
+
it("passes the handshake message through the extension", function() { with(this) {
|
91
|
+
expect(transport, "send").given({
|
92
|
+
channel: "/meta/handshake",
|
93
|
+
version: "1.0",
|
94
|
+
supportedConnectionTypes: ["fake"],
|
95
|
+
id: instanceOf("string"),
|
96
|
+
ext: {auth: "password"}
|
97
|
+
}, 60)
|
98
|
+
client.handshake()
|
99
|
+
}})
|
100
|
+
}})
|
101
|
+
|
102
|
+
describe("on successful response", function() { with(this) {
|
103
|
+
before(function() { with(this) {
|
104
|
+
stubResponse({channel: "/meta/handshake",
|
105
|
+
successful: true,
|
106
|
+
version: "1.0",
|
107
|
+
supportedConnectionTypes: ["long-polling", "websocket"],
|
108
|
+
clientId: "fakeid" })
|
109
|
+
}})
|
110
|
+
|
111
|
+
it("stores the clientId", function() { with(this) {
|
112
|
+
client.handshake()
|
113
|
+
assertEqual( "fakeid", client.getClientId() )
|
114
|
+
}})
|
115
|
+
|
116
|
+
it("puts the client in the CONNECTED state", function() { with(this) {
|
117
|
+
client.handshake()
|
118
|
+
assertEqual( "CONNECTED", client.getState() )
|
119
|
+
}})
|
120
|
+
|
121
|
+
it("registers any pre-existing subscriptions", function() { with(this) {
|
122
|
+
expect(client, "subscribe").given([], true)
|
123
|
+
client.handshake()
|
124
|
+
}})
|
125
|
+
|
126
|
+
it("selects a new transport based on what the server supports", function() { with(this) {
|
127
|
+
expect(Faye.Transport, "get").given(instanceOf(Faye.Client), ["long-polling", "websocket"])
|
128
|
+
.yielding([transport])
|
129
|
+
client.handshake()
|
130
|
+
}})
|
131
|
+
|
132
|
+
describe("with websocket disabled", function() { with(this) {
|
133
|
+
before(function() { this.client.disable('websocket') })
|
134
|
+
|
135
|
+
it("selects a new transport, excluding websocket", function() { with(this) {
|
136
|
+
expect(Faye.Transport, "get").given(instanceOf(Faye.Client), ["long-polling"])
|
137
|
+
.yielding([transport])
|
138
|
+
client.handshake()
|
139
|
+
}})
|
140
|
+
}})
|
141
|
+
}})
|
142
|
+
|
143
|
+
describe("on unsuccessful response", function() { with(this) {
|
144
|
+
before(function() { with(this) {
|
145
|
+
stubResponse({channel: "/meta/handshake",
|
146
|
+
successful: false,
|
147
|
+
version: "1.0",
|
148
|
+
supportedConnectionTypes: ["websocket"] })
|
149
|
+
}})
|
150
|
+
|
151
|
+
it("schedules a retry", function() { with(this) {
|
152
|
+
expect("setTimeout")
|
153
|
+
client.handshake()
|
154
|
+
}})
|
155
|
+
|
156
|
+
it("puts the client in the UNCONNECTED state", function() { with(this) {
|
157
|
+
stub("setTimeout")
|
158
|
+
client.handshake()
|
159
|
+
assertEqual( "UNCONNECTED", client.getState() )
|
160
|
+
}})
|
161
|
+
}})
|
162
|
+
|
163
|
+
describe("with existing subscriptions after a server restart", function() { with(this) {
|
164
|
+
before(function() { with(this) {
|
165
|
+
createConnectedClient()
|
166
|
+
|
167
|
+
this.message = null
|
168
|
+
subscribe(client, "/messages/foo", function(m) { message = m })
|
169
|
+
|
170
|
+
client.receiveMessage({advice: {reconnect: "handshake"}})
|
171
|
+
|
172
|
+
stubResponse({channel: "/meta/handshake",
|
173
|
+
successful: true,
|
174
|
+
version: "1.0",
|
175
|
+
supportedConnectionTypes: ["websocket"],
|
176
|
+
clientId: "reconnectid",
|
177
|
+
subscription: "/messages/foo" }) // tacked on to trigger subscribe() callback
|
178
|
+
}})
|
179
|
+
|
180
|
+
it("resends the subscriptions to the server", function() { with(this) {
|
181
|
+
expect(transport, "send").given({
|
182
|
+
channel: "/meta/subscribe",
|
183
|
+
clientId: "reconnectid",
|
184
|
+
subscription: "/messages/foo",
|
185
|
+
id: instanceOf("string")
|
186
|
+
}, 60)
|
187
|
+
client.handshake()
|
188
|
+
}})
|
189
|
+
|
190
|
+
it("retains the listeners for the subscriptions", function() { with(this) {
|
191
|
+
client.handshake()
|
192
|
+
client.receiveMessage({channel: "/messages/foo", "data": "ok"})
|
193
|
+
assertEqual( "ok", message )
|
194
|
+
}})
|
195
|
+
}})
|
196
|
+
|
197
|
+
describe("with a connected client", function() { with(this) {
|
198
|
+
before(function() { this.createConnectedClient() })
|
199
|
+
|
200
|
+
it("does not send a handshake message to the server", function() { with(this) {
|
201
|
+
expect(transport, "send").given({
|
202
|
+
channel: "/meta/handshake",
|
203
|
+
version: "1.0",
|
204
|
+
supportedConnectionTypes: ["fake"],
|
205
|
+
id: instanceOf("string")
|
206
|
+
}, 60)
|
207
|
+
.exactly(0)
|
208
|
+
|
209
|
+
client.handshake()
|
210
|
+
}})
|
211
|
+
}})
|
212
|
+
}})
|
213
|
+
|
214
|
+
describe("connect", function() { with(this) {
|
215
|
+
describe("with an unconnected client", function() { with(this) {
|
216
|
+
before(function() { with(this) {
|
217
|
+
stubResponse({channel: "/meta/handshake",
|
218
|
+
successful: true,
|
219
|
+
version: "1.0",
|
220
|
+
supportedConnectionTypes: ["websocket"],
|
221
|
+
clientId: "handshakeid" })
|
222
|
+
|
223
|
+
createClient()
|
224
|
+
}})
|
225
|
+
|
226
|
+
it("handshakes before connecting", function() { with(this) {
|
227
|
+
expect(transport, "send").given({
|
228
|
+
channel: "/meta/connect",
|
229
|
+
clientId: "handshakeid",
|
230
|
+
connectionType: "fake",
|
231
|
+
id: instanceOf("string")
|
232
|
+
}, 60)
|
233
|
+
client.connect()
|
234
|
+
}})
|
235
|
+
}})
|
236
|
+
|
237
|
+
describe("with a connected client", function() { with(this) {
|
238
|
+
before(function() { this.createConnectedClient() })
|
239
|
+
|
240
|
+
it("sends a connect message to the server", function() { with(this) {
|
241
|
+
expect(transport, "send").given({
|
242
|
+
channel: "/meta/connect",
|
243
|
+
clientId: "fakeid",
|
244
|
+
connectionType: "fake",
|
245
|
+
id: instanceOf("string")
|
246
|
+
}, 60)
|
247
|
+
client.connect()
|
248
|
+
}})
|
249
|
+
|
250
|
+
it("only opens one connect request at a time", function() { with(this) {
|
251
|
+
expect(transport, "send").given({
|
252
|
+
channel: "/meta/connect",
|
253
|
+
clientId: "fakeid",
|
254
|
+
connectionType: "fake",
|
255
|
+
id: instanceOf("string")
|
256
|
+
}, 60)
|
257
|
+
.exactly(1)
|
258
|
+
|
259
|
+
client.connect()
|
260
|
+
client.connect()
|
261
|
+
}})
|
262
|
+
}})
|
263
|
+
}})
|
264
|
+
|
265
|
+
describe("disconnect", function() { with(this) {
|
266
|
+
before(function() { this.createConnectedClient() })
|
267
|
+
|
268
|
+
it("sends a disconnect message to the server", function() { with(this) {
|
269
|
+
expect(transport, "send").given({
|
270
|
+
channel: "/meta/disconnect",
|
271
|
+
clientId: "fakeid",
|
272
|
+
id: instanceOf("string")
|
273
|
+
}, 60)
|
274
|
+
client.disconnect()
|
275
|
+
}})
|
276
|
+
|
277
|
+
it("puts the client in the DISCONNECTED state", function() { with(this) {
|
278
|
+
stub(transport, "close")
|
279
|
+
client.disconnect()
|
280
|
+
assertEqual( "DISCONNECTED", client.getState() )
|
281
|
+
}})
|
282
|
+
|
283
|
+
describe("on successful response", function() { with(this) {
|
284
|
+
before(function() { with(this) {
|
285
|
+
stubResponse({channel: "/meta/disconnect",
|
286
|
+
successful: true,
|
287
|
+
clientId: "fakeid" })
|
288
|
+
}})
|
289
|
+
|
290
|
+
it("closes the transport", function() { with(this) {
|
291
|
+
expect(transport, "close")
|
292
|
+
client.disconnect()
|
293
|
+
}})
|
294
|
+
}})
|
295
|
+
}})
|
296
|
+
|
297
|
+
describe("subscribe", function() { with(this) {
|
298
|
+
before(function() { with(this) {
|
299
|
+
createConnectedClient()
|
300
|
+
this.subscribeMessage = {
|
301
|
+
channel: "/meta/subscribe",
|
302
|
+
clientId: "fakeid",
|
303
|
+
subscription: "/foo",
|
304
|
+
id: instanceOf("string")
|
305
|
+
}
|
306
|
+
}})
|
307
|
+
|
308
|
+
describe("with no prior subscriptions", function() { with(this) {
|
309
|
+
it("sends a subscribe message to the server", function() { with(this) {
|
310
|
+
expect(transport, "send").given(subscribeMessage, 60)
|
311
|
+
client.subscribe("/foo")
|
312
|
+
}})
|
313
|
+
|
314
|
+
// The Bayeux spec says the server should accept a list of subscriptions
|
315
|
+
// in one message but the cometD server doesn't actually support this
|
316
|
+
it("sends multiple subscribe messages if given an array", function() { with(this) {
|
317
|
+
expect(transport, "send").given({
|
318
|
+
channel: "/meta/subscribe",
|
319
|
+
clientId: "fakeid",
|
320
|
+
subscription: "/foo",
|
321
|
+
id: instanceOf("string")
|
322
|
+
}, 60)
|
323
|
+
expect(transport, "send").given({
|
324
|
+
channel: "/meta/subscribe",
|
325
|
+
clientId: "fakeid",
|
326
|
+
subscription: "/bar",
|
327
|
+
id: instanceOf("string")
|
328
|
+
}, 60)
|
329
|
+
client.subscribe(["/foo", "/bar"])
|
330
|
+
}})
|
331
|
+
|
332
|
+
describe("on successful response", function() { with(this) {
|
333
|
+
before(function() { with(this) {
|
334
|
+
stubResponse({channel: "/meta/subscribe",
|
335
|
+
successful: true,
|
336
|
+
clientId: "fakeid",
|
337
|
+
subscription: "/foo/*" })
|
338
|
+
}})
|
339
|
+
|
340
|
+
it("sets up a listener for the subscribed channel", function() { with(this) {
|
341
|
+
var message
|
342
|
+
client.subscribe("/foo/*", function(m) { message = m })
|
343
|
+
client.receiveMessage({channel: "/foo/bar", data: "hi"})
|
344
|
+
assertEqual( "hi", message )
|
345
|
+
}})
|
346
|
+
|
347
|
+
it("does not call the listener for non-matching channels", function() { with(this) {
|
348
|
+
var message
|
349
|
+
client.subscribe("/foo/*", function(m) { message = m })
|
350
|
+
client.receiveMessage({channel: "/bar", data: "hi"})
|
351
|
+
assertEqual( undefined, message )
|
352
|
+
}})
|
353
|
+
|
354
|
+
it("activates the subscription", function() { with(this) {
|
355
|
+
var active = false
|
356
|
+
client.subscribe("/foo/*").callback(function() { active = true })
|
357
|
+
assert( active )
|
358
|
+
}})
|
359
|
+
|
360
|
+
describe("with an incoming extension installed", function() { with(this) {
|
361
|
+
before(function() { with(this) {
|
362
|
+
var extension = {
|
363
|
+
incoming: function(message, callback) {
|
364
|
+
if (message.data) message.data.changed = true
|
365
|
+
callback(message)
|
366
|
+
}
|
367
|
+
}
|
368
|
+
client.addExtension(extension)
|
369
|
+
this.message = null
|
370
|
+
client.subscribe("/foo/*", function(m) { message = m })
|
371
|
+
}})
|
372
|
+
|
373
|
+
it("passes delivered messages through the extension", function() { with(this) {
|
374
|
+
client.receiveMessage({channel: "/foo/bar", data: {hello: "there"}})
|
375
|
+
assertEqual( {hello: "there", changed: true}, message )
|
376
|
+
}})
|
377
|
+
}})
|
378
|
+
|
379
|
+
describe("with an outgoing extension installed", function() { with(this) {
|
380
|
+
before(function() { with(this) {
|
381
|
+
var extension = {
|
382
|
+
outgoing: function(message, callback) {
|
383
|
+
if (message.data) message.data.changed = true
|
384
|
+
callback(message)
|
385
|
+
}
|
386
|
+
}
|
387
|
+
client.addExtension(extension)
|
388
|
+
this.message = null
|
389
|
+
client.subscribe("/foo/*", function(m) { message = m })
|
390
|
+
}})
|
391
|
+
|
392
|
+
it("leaves messages unchanged", function() { with(this) {
|
393
|
+
client.receiveMessage({channel: "/foo/bar", data: {hello: "there"}})
|
394
|
+
assertEqual( {hello: "there"}, message )
|
395
|
+
}})
|
396
|
+
}})
|
397
|
+
|
398
|
+
describe("with an incoming extension that invalidates the response", function() { with(this) {
|
399
|
+
before(function() { with(this) {
|
400
|
+
var extension = {
|
401
|
+
incoming: function(message, callback) {
|
402
|
+
if (message.channel === "/meta/subscribe") message.successful = false
|
403
|
+
callback(message)
|
404
|
+
}
|
405
|
+
}
|
406
|
+
client.addExtension(extension)
|
407
|
+
}})
|
408
|
+
|
409
|
+
it("does not set up a listener for the subscribed channel", function() { with(this) {
|
410
|
+
var message
|
411
|
+
client.subscribe("/foo/*", function(m) { message = m })
|
412
|
+
client.receiveMessage({channel: "/foo/bar", data: "hi"})
|
413
|
+
assertEqual( undefined, message )
|
414
|
+
}})
|
415
|
+
|
416
|
+
it("does not activate the subscription", function() { with(this) {
|
417
|
+
var active = false
|
418
|
+
client.subscribe("/foo/*").callback(function() { active = true })
|
419
|
+
assert( !active )
|
420
|
+
}})
|
421
|
+
}})
|
422
|
+
}})
|
423
|
+
|
424
|
+
describe("on unsuccessful response", function() { with(this) {
|
425
|
+
before(function() { with(this) {
|
426
|
+
stubResponse({channel: "/meta/subscribe",
|
427
|
+
successful: false,
|
428
|
+
error: "403:/meta/foo:Forbidden channel",
|
429
|
+
clientId: "fakeid",
|
430
|
+
subscription: "/meta/foo" })
|
431
|
+
}})
|
432
|
+
|
433
|
+
it("does not set up a listener for the subscribed channel", function() { with(this) {
|
434
|
+
var message
|
435
|
+
client.subscribe("/meta/foo", function(m) { message = m })
|
436
|
+
client.receiveMessage({channel: "/meta/foo", data: "hi"})
|
437
|
+
assertEqual( undefined, message )
|
438
|
+
}})
|
439
|
+
|
440
|
+
it("does not activate the subscription", function() { with(this) {
|
441
|
+
var active = false
|
442
|
+
client.subscribe("/meta/foo").callback(function() { active = true })
|
443
|
+
assert( !active )
|
444
|
+
}})
|
445
|
+
|
446
|
+
it("reports the error through an errback", function() { with(this) {
|
447
|
+
var error = null
|
448
|
+
client.subscribe("/meta/foo").errback(function(e) { error = e })
|
449
|
+
assertEqual( objectIncluding({code: 403, params: ["/meta/foo"], message: "Forbidden channel"}), error )
|
450
|
+
}})
|
451
|
+
}})
|
452
|
+
}})
|
453
|
+
|
454
|
+
describe("with an existing subscription", function() { with(this) {
|
455
|
+
before(function() { with(this) {
|
456
|
+
subscribe(client, "/foo/*")
|
457
|
+
}})
|
458
|
+
|
459
|
+
it("does not send another subscribe message to the server", function() { with(this) {
|
460
|
+
expect(transport, "send").given(subscribeMessage, 60).exactly(0)
|
461
|
+
client.subscribe("/foo/*")
|
462
|
+
}})
|
463
|
+
|
464
|
+
it("sets up another listener on the channel", function() { with(this) {
|
465
|
+
client.subscribe("/foo/*", function() { subsCalled += 1 })
|
466
|
+
client.receiveMessage({channel: "/foo/bar", data: "hi"})
|
467
|
+
assertEqual( 2, subsCalled )
|
468
|
+
}})
|
469
|
+
|
470
|
+
it("activates the subscription", function() { with(this) {
|
471
|
+
var active = false
|
472
|
+
client.subscribe("/foo/*").callback(function() { active = true })
|
473
|
+
assert( active )
|
474
|
+
}})
|
475
|
+
}})
|
476
|
+
}})
|
477
|
+
|
478
|
+
describe("unsubscribe", function() { with(this) {
|
479
|
+
before(function() { with(this) {
|
480
|
+
createConnectedClient()
|
481
|
+
this.unsubscribeMessage = {
|
482
|
+
channel: "/meta/unsubscribe",
|
483
|
+
clientId: "fakeid",
|
484
|
+
subscription: "/foo/*",
|
485
|
+
id: instanceOf("string")
|
486
|
+
}
|
487
|
+
}})
|
488
|
+
|
489
|
+
describe("with no subscriptions", function() { with(this) {
|
490
|
+
it("does not send an unsubscribe message to the server", function() { with(this) {
|
491
|
+
expect(transport, "send").given(unsubscribeMessage, 60).exactly(0)
|
492
|
+
client.unsubscribe("/foo/*")
|
493
|
+
}})
|
494
|
+
}})
|
495
|
+
|
496
|
+
describe("with a single subscription", function() { with(this) {
|
497
|
+
before(function() { with(this) {
|
498
|
+
this.message = null
|
499
|
+
this.listener = function(m) { message = m }
|
500
|
+
subscribe(client, "/foo/*", listener)
|
501
|
+
}})
|
502
|
+
|
503
|
+
it("sends an unsubscribe message to the server", function() { with(this) {
|
504
|
+
expect(transport, "send").given(unsubscribeMessage, 60)
|
505
|
+
client.unsubscribe("/foo/*")
|
506
|
+
}})
|
507
|
+
|
508
|
+
it("removes the listener from the channel", function() { with(this) {
|
509
|
+
client.receiveMessage({channel: "/foo/bar", data: "first"})
|
510
|
+
client.unsubscribe("/foo/*", listener)
|
511
|
+
client.receiveMessage({channel: "/foo/bar", data: "second"})
|
512
|
+
assertEqual( "first", message )
|
513
|
+
}})
|
514
|
+
}})
|
515
|
+
|
516
|
+
describe("with multiple subscriptions to the same channel", function() { with(this) {
|
517
|
+
before(function() { with(this) {
|
518
|
+
this.messages = []
|
519
|
+
this.hey = function(m) { messages.push("hey " + m.text) }
|
520
|
+
this.bye = function(m) { messages.push("bye " + m.text) }
|
521
|
+
subscribe(client, "/foo/*", hey)
|
522
|
+
subscribe(client, "/foo/*", bye)
|
523
|
+
}})
|
524
|
+
|
525
|
+
it("removes one of the listeners from the channel", function() { with(this) {
|
526
|
+
client.receiveMessage({channel: "/foo/bar", data: {text: "you"}})
|
527
|
+
client.unsubscribe("/foo/*", hey)
|
528
|
+
client.receiveMessage({channel: "/foo/bar", data: {text: "you"}})
|
529
|
+
assertEqual( ["hey you", "bye you", "bye you"], messages)
|
530
|
+
}})
|
531
|
+
|
532
|
+
it("does not send an unsubscribe message if one listener is removed", function() { with(this) {
|
533
|
+
expect(transport, "send").given(unsubscribeMessage, 60).exactly(0)
|
534
|
+
client.unsubscribe("/foo/*", bye)
|
535
|
+
}})
|
536
|
+
|
537
|
+
it("sends an unsubscribe message if each listener is removed", function() { with(this) {
|
538
|
+
expect(transport, "send").given(unsubscribeMessage, 60)
|
539
|
+
client.unsubscribe("/foo/*", bye)
|
540
|
+
client.unsubscribe("/foo/*", hey)
|
541
|
+
}})
|
542
|
+
|
543
|
+
it("sends an unsubscribe message if all listeners are removed", function() { with(this) {
|
544
|
+
expect(transport, "send").given(unsubscribeMessage, 60)
|
545
|
+
client.unsubscribe("/foo/*")
|
546
|
+
}})
|
547
|
+
}})
|
548
|
+
|
549
|
+
describe("with multiple subscriptions to different channels", function() { with(this) {
|
550
|
+
before(function() { with(this) {
|
551
|
+
subscribe(client, "/foo")
|
552
|
+
subscribe(client, "/bar")
|
553
|
+
}})
|
554
|
+
|
555
|
+
it("sends multiple unsubscribe messages if given an array", function() { with(this) {
|
556
|
+
expect(transport, "send").given({
|
557
|
+
channel: "/meta/unsubscribe",
|
558
|
+
clientId: "fakeid",
|
559
|
+
subscription: "/foo",
|
560
|
+
id: instanceOf("string")
|
561
|
+
}, 60)
|
562
|
+
expect(transport, "send").given({
|
563
|
+
channel: "/meta/unsubscribe",
|
564
|
+
clientId: "fakeid",
|
565
|
+
subscription: "/bar",
|
566
|
+
id: instanceOf("string")
|
567
|
+
}, 60)
|
568
|
+
client.unsubscribe(["/foo", "/bar"])
|
569
|
+
}})
|
570
|
+
}})
|
571
|
+
}})
|
572
|
+
|
573
|
+
describe("publish", function() { with(this) {
|
574
|
+
before(function() { this.createConnectedClient() })
|
575
|
+
|
576
|
+
it("sends the message to the server with an ID", function() { with(this) {
|
577
|
+
expect(transport, "send").given({
|
578
|
+
channel: "/messages/foo",
|
579
|
+
clientId: "fakeid",
|
580
|
+
data: {hello: "world"},
|
581
|
+
id: instanceOf("string")
|
582
|
+
}, 60)
|
583
|
+
client.publish("/messages/foo", {hello: "world"})
|
584
|
+
}})
|
585
|
+
|
586
|
+
describe("on publish failure", function() { with(this) {
|
587
|
+
before(function() { with(this) {
|
588
|
+
stubResponse({channel: "/messages/foo",
|
589
|
+
error: "407:/messages/foo:Failed to publish",
|
590
|
+
successful: false,
|
591
|
+
clientId: "fakeid" })
|
592
|
+
}})
|
593
|
+
|
594
|
+
it("should not be published", function() { with(this) {
|
595
|
+
var published = false
|
596
|
+
client.publish("/messages/foo", {text: "hi"}).callback(function() { published = true })
|
597
|
+
assert( !published )
|
598
|
+
}})
|
599
|
+
|
600
|
+
it("reports the error through an errback", function() { with(this) {
|
601
|
+
var error = null
|
602
|
+
client.publish("/messages/foo", {text: "hi"}).errback(function(e) { error = e })
|
603
|
+
assertEqual( 407, error.code )
|
604
|
+
assertEqual( ["/messages/foo"], error.params )
|
605
|
+
assertEqual( "Failed to publish", error.message )
|
606
|
+
}})
|
607
|
+
}})
|
608
|
+
|
609
|
+
describe("on receipt of the published message", function() { with(this) {
|
610
|
+
before(function() { with(this) {
|
611
|
+
stubResponse({channel: "/messages/foo",
|
612
|
+
data: {text: "hi"},
|
613
|
+
clientId: "fakeid" })
|
614
|
+
}})
|
615
|
+
|
616
|
+
it("does not trigger the callbacks", function() { with(this) {
|
617
|
+
var published = false
|
618
|
+
var publication = client.publish("/messages/foo", {text: "hi"})
|
619
|
+
publication.callback(function() { published = true })
|
620
|
+
publication.errback(function() { published = true })
|
621
|
+
assert( !published )
|
622
|
+
}})
|
623
|
+
}})
|
624
|
+
|
625
|
+
describe("with an outgoing extension installed", function() { with(this) {
|
626
|
+
before(function() { with(this) {
|
627
|
+
var extension = {
|
628
|
+
outgoing: function(message, callback) {
|
629
|
+
message.ext = {auth: "password"}
|
630
|
+
callback(message)
|
631
|
+
}
|
632
|
+
}
|
633
|
+
client.addExtension(extension)
|
634
|
+
}})
|
635
|
+
|
636
|
+
it("passes messages through the extension", function() { with(this) {
|
637
|
+
expect(transport, "send").given({
|
638
|
+
channel: "/messages/foo",
|
639
|
+
clientId: "fakeid",
|
640
|
+
data: {hello: "world"},
|
641
|
+
id: instanceOf("string"),
|
642
|
+
ext: {auth: "password"}
|
643
|
+
}, 60)
|
644
|
+
client.publish("/messages/foo", {hello: "world"})
|
645
|
+
}})
|
646
|
+
}})
|
647
|
+
|
648
|
+
describe("with an incoming extension installed", function() { with(this) {
|
649
|
+
before(function() { with(this) {
|
650
|
+
var extension = {
|
651
|
+
incoming: function(message, callback) {
|
652
|
+
message.ext = {auth: "password"}
|
653
|
+
callback(message)
|
654
|
+
}
|
655
|
+
}
|
656
|
+
client.addExtension(extension)
|
657
|
+
}})
|
658
|
+
|
659
|
+
it("leaves the message unchanged", function() { with(this) {
|
660
|
+
expect(transport, "send").given({
|
661
|
+
channel: "/messages/foo",
|
662
|
+
clientId: "fakeid",
|
663
|
+
data: {hello: "world"},
|
664
|
+
id: instanceOf("string")
|
665
|
+
}, 60)
|
666
|
+
client.publish("/messages/foo", {hello: "world"})
|
667
|
+
}})
|
668
|
+
}})
|
669
|
+
}})
|
670
|
+
|
671
|
+
describe("network notifications", function() { with(this) {
|
672
|
+
before(function() { this.createClient() })
|
673
|
+
|
674
|
+
describe("in the default state", function() { with(this) {
|
675
|
+
it("broadcasts a down notification", function() { with(this) {
|
676
|
+
expect(client, "trigger").given("transport:down")
|
677
|
+
transport.trigger("down")
|
678
|
+
}})
|
679
|
+
|
680
|
+
it("broadcasts an up notification", function() { with(this) {
|
681
|
+
expect(client, "trigger").given("transport:up")
|
682
|
+
transport.trigger("up")
|
683
|
+
}})
|
684
|
+
}})
|
685
|
+
|
686
|
+
describe("when the transport is up", function() { with(this) {
|
687
|
+
before(function() { this.transport.trigger("up") })
|
688
|
+
|
689
|
+
it("broadcasts a down notification", function() { with(this) {
|
690
|
+
expect(client, "trigger").given("transport:down")
|
691
|
+
transport.trigger("down")
|
692
|
+
}})
|
693
|
+
|
694
|
+
it("does not broadcast an up notification", function() { with(this) {
|
695
|
+
expect(client, "trigger").exactly(0)
|
696
|
+
transport.trigger("up")
|
697
|
+
}})
|
698
|
+
}})
|
699
|
+
|
700
|
+
describe("when the transport is down", function() { with(this) {
|
701
|
+
before(function() { this.transport.trigger("down") })
|
702
|
+
|
703
|
+
it("does not broadcast a down notification", function() { with(this) {
|
704
|
+
expect(client, "trigger").exactly(0)
|
705
|
+
transport.trigger("down")
|
706
|
+
}})
|
707
|
+
|
708
|
+
it("broadcasts an up notification", function() { with(this) {
|
709
|
+
expect(client, "trigger").given("transport:up")
|
710
|
+
transport.trigger("up")
|
711
|
+
}})
|
712
|
+
}})
|
713
|
+
}})
|
714
|
+
}})
|