face-faye 0.8.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +304 -0
- data/README.rdoc +83 -0
- data/lib/faye-browser-min.js +2 -0
- data/lib/faye-browser-min.js.map +8 -0
- data/lib/faye-browser.js +2194 -0
- data/lib/faye.rb +122 -0
- data/lib/faye/adapters/rack_adapter.rb +216 -0
- data/lib/faye/adapters/static_server.rb +56 -0
- data/lib/faye/engines/connection.rb +60 -0
- data/lib/faye/engines/memory.rb +112 -0
- data/lib/faye/engines/proxy.rb +121 -0
- data/lib/faye/error.rb +49 -0
- data/lib/faye/mixins/logging.rb +47 -0
- data/lib/faye/mixins/publisher.rb +30 -0
- data/lib/faye/mixins/timeouts.rb +22 -0
- data/lib/faye/protocol/channel.rb +124 -0
- data/lib/faye/protocol/client.rb +376 -0
- data/lib/faye/protocol/extensible.rb +43 -0
- data/lib/faye/protocol/grammar.rb +58 -0
- data/lib/faye/protocol/publication.rb +5 -0
- data/lib/faye/protocol/server.rb +293 -0
- data/lib/faye/protocol/socket.rb +23 -0
- data/lib/faye/protocol/subscription.rb +24 -0
- data/lib/faye/transport/http.rb +76 -0
- data/lib/faye/transport/local.rb +22 -0
- data/lib/faye/transport/transport.rb +116 -0
- data/lib/faye/transport/web_socket.rb +92 -0
- data/lib/faye/util/namespace.rb +20 -0
- data/spec/browser.html +45 -0
- data/spec/encoding_helper.rb +7 -0
- data/spec/install.sh +78 -0
- data/spec/javascript/channel_spec.js +15 -0
- data/spec/javascript/client_spec.js +729 -0
- data/spec/javascript/engine/memory_spec.js +7 -0
- data/spec/javascript/engine_spec.js +417 -0
- data/spec/javascript/faye_spec.js +34 -0
- data/spec/javascript/grammar_spec.js +66 -0
- data/spec/javascript/node_adapter_spec.js +307 -0
- data/spec/javascript/publisher_spec.js +27 -0
- data/spec/javascript/server/connect_spec.js +168 -0
- data/spec/javascript/server/disconnect_spec.js +121 -0
- data/spec/javascript/server/extensions_spec.js +60 -0
- data/spec/javascript/server/handshake_spec.js +145 -0
- data/spec/javascript/server/integration_spec.js +131 -0
- data/spec/javascript/server/publish_spec.js +85 -0
- data/spec/javascript/server/subscribe_spec.js +247 -0
- data/spec/javascript/server/unsubscribe_spec.js +245 -0
- data/spec/javascript/server_spec.js +121 -0
- data/spec/javascript/transport_spec.js +135 -0
- data/spec/node.js +55 -0
- data/spec/phantom.js +17 -0
- data/spec/ruby/channel_spec.rb +17 -0
- data/spec/ruby/client_spec.rb +741 -0
- data/spec/ruby/engine/memory_spec.rb +7 -0
- data/spec/ruby/engine_examples.rb +427 -0
- data/spec/ruby/faye_spec.rb +30 -0
- data/spec/ruby/grammar_spec.rb +68 -0
- data/spec/ruby/publisher_spec.rb +27 -0
- data/spec/ruby/rack_adapter_spec.rb +236 -0
- data/spec/ruby/server/connect_spec.rb +170 -0
- data/spec/ruby/server/disconnect_spec.rb +120 -0
- data/spec/ruby/server/extensions_spec.rb +68 -0
- data/spec/ruby/server/handshake_spec.rb +143 -0
- data/spec/ruby/server/integration_spec.rb +133 -0
- data/spec/ruby/server/publish_spec.rb +81 -0
- data/spec/ruby/server/subscribe_spec.rb +247 -0
- data/spec/ruby/server/unsubscribe_spec.rb +247 -0
- data/spec/ruby/server_spec.rb +121 -0
- data/spec/ruby/transport_spec.rb +136 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/testswarm +42 -0
- data/spec/thin_proxy.rb +37 -0
- metadata +441 -0
@@ -0,0 +1,116 @@
|
|
1
|
+
module Faye
|
2
|
+
class Transport
|
3
|
+
|
4
|
+
include Logging
|
5
|
+
include Publisher
|
6
|
+
include Timeouts
|
7
|
+
|
8
|
+
attr_accessor :cookies, :endpoint, :headers
|
9
|
+
|
10
|
+
def initialize(client, endpoint)
|
11
|
+
@client = client
|
12
|
+
@endpoint = endpoint
|
13
|
+
@outbox = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def batching?
|
17
|
+
true
|
18
|
+
end
|
19
|
+
|
20
|
+
def close
|
21
|
+
end
|
22
|
+
|
23
|
+
def connection_type
|
24
|
+
self.class.connection_type
|
25
|
+
end
|
26
|
+
|
27
|
+
def send(message, timeout)
|
28
|
+
debug('Client ? sending message to ?: ?', @client.client_id, @endpoint, message)
|
29
|
+
|
30
|
+
return request([message], timeout) unless batching?
|
31
|
+
|
32
|
+
@outbox << message
|
33
|
+
@timeout = timeout
|
34
|
+
|
35
|
+
if message['channel'] == Channel::HANDSHAKE
|
36
|
+
return add_timeout(:publish, 0.01) { flush }
|
37
|
+
end
|
38
|
+
|
39
|
+
if message['channel'] == Channel::CONNECT
|
40
|
+
@connection_message = message
|
41
|
+
end
|
42
|
+
|
43
|
+
add_timeout(:publish, Engine::MAX_DELAY) { flush }
|
44
|
+
end
|
45
|
+
|
46
|
+
def flush
|
47
|
+
remove_timeout(:publish)
|
48
|
+
|
49
|
+
if @outbox.size > 1 and @connection_message
|
50
|
+
@connection_message['advice'] = {'timeout' => 0}
|
51
|
+
end
|
52
|
+
|
53
|
+
request(@outbox, @timeout)
|
54
|
+
|
55
|
+
@connection_message = nil
|
56
|
+
@outbox = []
|
57
|
+
end
|
58
|
+
|
59
|
+
def receive(responses)
|
60
|
+
debug('Client ? received from ?: ?', @client.client_id, @endpoint, responses)
|
61
|
+
responses.each { |response| @client.receive_message(response) }
|
62
|
+
end
|
63
|
+
|
64
|
+
def retry_block(message, timeout)
|
65
|
+
lambda do
|
66
|
+
EventMachine.add_timer(@client.retry) { request(message, timeout) }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
@transports = []
|
71
|
+
|
72
|
+
class << self
|
73
|
+
attr_accessor :connection_type
|
74
|
+
|
75
|
+
def get(client, allowed, disabled, &callback)
|
76
|
+
endpoint = client.endpoint
|
77
|
+
|
78
|
+
select = lambda do |(conn_type, klass), resume|
|
79
|
+
conn_endpoint = client.endpoints[conn_type] || endpoint
|
80
|
+
|
81
|
+
if disabled.include?(conn_type)
|
82
|
+
next resume.call
|
83
|
+
end
|
84
|
+
|
85
|
+
unless allowed.include?(conn_type)
|
86
|
+
klass.usable?(client, conn_endpoint) { |u| }
|
87
|
+
next resume.call
|
88
|
+
end
|
89
|
+
|
90
|
+
klass.usable?(client, conn_endpoint) do |is_usable|
|
91
|
+
next resume.call unless is_usable
|
92
|
+
transport = klass.respond_to?(:create) ? klass.create(client, conn_endpoint) : klass.new(client, conn_endpoint)
|
93
|
+
callback.call(transport)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
error = lambda do
|
98
|
+
raise "Could not find a usable connection type for #{ endpoint }"
|
99
|
+
end
|
100
|
+
|
101
|
+
Faye.async_each(@transports, select, error)
|
102
|
+
end
|
103
|
+
|
104
|
+
def register(type, klass)
|
105
|
+
@transports << [type, klass]
|
106
|
+
klass.connection_type = type
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
%w[local web_socket http].each do |type|
|
111
|
+
require File.join(ROOT, 'faye', 'transport', type)
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module Faye
|
2
|
+
|
3
|
+
class Transport::WebSocket < Transport
|
4
|
+
UNCONNECTED = 1
|
5
|
+
CONNECTING = 2
|
6
|
+
CONNECTED = 3
|
7
|
+
|
8
|
+
include EventMachine::Deferrable
|
9
|
+
|
10
|
+
def self.usable?(client, endpoint, &callback)
|
11
|
+
create(client, endpoint).usable?(&callback)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.create(client, endpoint)
|
15
|
+
sockets = client.transports[:websocket] ||= {}
|
16
|
+
sockets[endpoint] ||= new(client, endpoint)
|
17
|
+
end
|
18
|
+
|
19
|
+
def batching?
|
20
|
+
false
|
21
|
+
end
|
22
|
+
|
23
|
+
def usable?(&callback)
|
24
|
+
self.callback { callback.call(true) }
|
25
|
+
self.errback { callback.call(false) }
|
26
|
+
connect
|
27
|
+
end
|
28
|
+
|
29
|
+
def request(messages, timeout = nil)
|
30
|
+
return if messages.empty?
|
31
|
+
@messages ||= {}
|
32
|
+
messages.each { |message| @messages[message['id']] = message }
|
33
|
+
callback { |socket| socket.send(Faye.to_json(messages)) }
|
34
|
+
connect
|
35
|
+
end
|
36
|
+
|
37
|
+
def close
|
38
|
+
return unless @socket
|
39
|
+
@socket.onclose = @socket.onerror = nil
|
40
|
+
@socket.close
|
41
|
+
@socket = nil
|
42
|
+
set_deferred_status(:deferred)
|
43
|
+
@state = UNCONNECTED
|
44
|
+
end
|
45
|
+
|
46
|
+
def connect
|
47
|
+
@state ||= UNCONNECTED
|
48
|
+
return unless @state == UNCONNECTED
|
49
|
+
|
50
|
+
@state = CONNECTING
|
51
|
+
|
52
|
+
@socket = Faye::WebSocket::Client.new(@endpoint.gsub(/^http(s?):/, 'ws\1:'))
|
53
|
+
|
54
|
+
@socket.onopen = lambda do |*args|
|
55
|
+
@state = CONNECTED
|
56
|
+
@ever_connected = true
|
57
|
+
set_deferred_status(:succeeded, @socket)
|
58
|
+
trigger(:up)
|
59
|
+
end
|
60
|
+
|
61
|
+
@socket.onmessage = lambda do |event|
|
62
|
+
messages = MultiJson.load(event.data)
|
63
|
+
next if messages.nil?
|
64
|
+
messages = [messages].flatten
|
65
|
+
messages.each { |message| @messages.delete(message['id']) }
|
66
|
+
receive(messages)
|
67
|
+
end
|
68
|
+
|
69
|
+
@socket.onclose = @socket.onerror = lambda do |*args|
|
70
|
+
was_connected = (@state == CONNECTED)
|
71
|
+
set_deferred_status(:deferred)
|
72
|
+
@state = UNCONNECTED
|
73
|
+
|
74
|
+
close
|
75
|
+
|
76
|
+
next resend if was_connected
|
77
|
+
next set_deferred_status(:failed) unless @ever_connected
|
78
|
+
|
79
|
+
EventMachine.add_timer(@client.retry) { connect }
|
80
|
+
trigger(:down)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def resend
|
85
|
+
return unless @messages
|
86
|
+
request(@messages.values)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
Transport.register 'websocket', Transport::WebSocket
|
91
|
+
|
92
|
+
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/browser/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,729 @@
|
|
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("puts the client in the UNCONNECTED state", function() { with(this) {
|
47
|
+
stub(Faye.Transport, "get")
|
48
|
+
var client = new Faye.Client("http://localhost/")
|
49
|
+
assertEqual( "UNCONNECTED", client.getState() )
|
50
|
+
}})
|
51
|
+
}})
|
52
|
+
|
53
|
+
describe("handshake", function() { with(this) {
|
54
|
+
before(function() { this.createClient() })
|
55
|
+
|
56
|
+
it("creates a transport the server must support", function() { with(this) {
|
57
|
+
expect(Faye.Transport, "get").given(instanceOf(Faye.Client),
|
58
|
+
["long-polling", "callback-polling", "in-process"],
|
59
|
+
[])
|
60
|
+
.yielding([transport])
|
61
|
+
client.handshake()
|
62
|
+
}})
|
63
|
+
|
64
|
+
it("sends a handshake message to the server", function() { with(this) {
|
65
|
+
expect(transport, "send").given({
|
66
|
+
channel: "/meta/handshake",
|
67
|
+
version: "1.0",
|
68
|
+
supportedConnectionTypes: ["fake"],
|
69
|
+
id: instanceOf("string")
|
70
|
+
}, 60)
|
71
|
+
client.handshake()
|
72
|
+
}})
|
73
|
+
|
74
|
+
it("puts the client in the CONNECTING state", function() { with(this) {
|
75
|
+
stub(transport, "send")
|
76
|
+
client.handshake()
|
77
|
+
assertEqual( "CONNECTING", client.getState() )
|
78
|
+
}})
|
79
|
+
|
80
|
+
describe("with an outgoing extension installed", function() { with(this) {
|
81
|
+
before(function() { with(this) {
|
82
|
+
var extension = {
|
83
|
+
outgoing: function(message, callback) {
|
84
|
+
message.ext = {auth: "password"}
|
85
|
+
callback(message)
|
86
|
+
}
|
87
|
+
}
|
88
|
+
client.addExtension(extension)
|
89
|
+
}})
|
90
|
+
|
91
|
+
it("passes the handshake message through the extension", function() { with(this) {
|
92
|
+
expect(transport, "send").given({
|
93
|
+
channel: "/meta/handshake",
|
94
|
+
version: "1.0",
|
95
|
+
supportedConnectionTypes: ["fake"],
|
96
|
+
id: instanceOf("string"),
|
97
|
+
ext: {auth: "password"}
|
98
|
+
}, 60)
|
99
|
+
client.handshake()
|
100
|
+
}})
|
101
|
+
}})
|
102
|
+
|
103
|
+
describe("on successful response", function() { with(this) {
|
104
|
+
before(function() { with(this) {
|
105
|
+
stubResponse({channel: "/meta/handshake",
|
106
|
+
successful: true,
|
107
|
+
version: "1.0",
|
108
|
+
supportedConnectionTypes: ["long-polling", "websocket"],
|
109
|
+
clientId: "fakeid" })
|
110
|
+
}})
|
111
|
+
|
112
|
+
it("stores the clientId", function() { with(this) {
|
113
|
+
client.handshake()
|
114
|
+
assertEqual( "fakeid", client.getClientId() )
|
115
|
+
}})
|
116
|
+
|
117
|
+
it("puts the client in the CONNECTED state", function() { with(this) {
|
118
|
+
client.handshake()
|
119
|
+
assertEqual( "CONNECTED", client.getState() )
|
120
|
+
}})
|
121
|
+
|
122
|
+
it("registers any pre-existing subscriptions", function() { with(this) {
|
123
|
+
expect(client, "subscribe").given([], true)
|
124
|
+
client.handshake()
|
125
|
+
}})
|
126
|
+
|
127
|
+
it("selects a new transport based on what the server supports", function() { with(this) {
|
128
|
+
expect(Faye.Transport, "get").given(instanceOf(Faye.Client), ["long-polling", "websocket"], [])
|
129
|
+
.yielding([transport])
|
130
|
+
client.handshake()
|
131
|
+
}})
|
132
|
+
|
133
|
+
describe("with websocket disabled", function() { with(this) {
|
134
|
+
before(function() { this.client.disable('websocket') })
|
135
|
+
|
136
|
+
it("selects a new transport, excluding websocket", function() { with(this) {
|
137
|
+
expect(Faye.Transport, "get").given(instanceOf(Faye.Client),
|
138
|
+
["long-polling", "websocket"],
|
139
|
+
["websocket"])
|
140
|
+
.yielding([transport])
|
141
|
+
client.handshake()
|
142
|
+
}})
|
143
|
+
}})
|
144
|
+
}})
|
145
|
+
|
146
|
+
describe("on unsuccessful response", function() { with(this) {
|
147
|
+
before(function() { with(this) {
|
148
|
+
stubResponse({channel: "/meta/handshake",
|
149
|
+
successful: false,
|
150
|
+
version: "1.0",
|
151
|
+
supportedConnectionTypes: ["websocket"] })
|
152
|
+
}})
|
153
|
+
|
154
|
+
it("schedules a retry", function() { with(this) {
|
155
|
+
expect("setTimeout")
|
156
|
+
client.handshake()
|
157
|
+
}})
|
158
|
+
|
159
|
+
it("puts the client in the UNCONNECTED state", function() { with(this) {
|
160
|
+
stub("setTimeout")
|
161
|
+
client.handshake()
|
162
|
+
assertEqual( "UNCONNECTED", client.getState() )
|
163
|
+
}})
|
164
|
+
}})
|
165
|
+
|
166
|
+
describe("with existing subscriptions after a server restart", function() { with(this) {
|
167
|
+
before(function() { with(this) {
|
168
|
+
createConnectedClient()
|
169
|
+
|
170
|
+
this.message = null
|
171
|
+
subscribe(client, "/messages/foo", function(m) { message = m })
|
172
|
+
|
173
|
+
client.receiveMessage({advice: {reconnect: "handshake"}})
|
174
|
+
|
175
|
+
stubResponse({channel: "/meta/handshake",
|
176
|
+
successful: true,
|
177
|
+
version: "1.0",
|
178
|
+
supportedConnectionTypes: ["websocket"],
|
179
|
+
clientId: "reconnectid",
|
180
|
+
subscription: "/messages/foo" }) // tacked on to trigger subscribe() callback
|
181
|
+
}})
|
182
|
+
|
183
|
+
it("resends the subscriptions to the server", function() { with(this) {
|
184
|
+
expect(transport, "send").given({
|
185
|
+
channel: "/meta/subscribe",
|
186
|
+
clientId: "reconnectid",
|
187
|
+
subscription: "/messages/foo",
|
188
|
+
id: instanceOf("string")
|
189
|
+
}, 60)
|
190
|
+
client.handshake()
|
191
|
+
}})
|
192
|
+
|
193
|
+
it("retains the listeners for the subscriptions", function() { with(this) {
|
194
|
+
client.handshake()
|
195
|
+
client.receiveMessage({channel: "/messages/foo", "data": "ok"})
|
196
|
+
assertEqual( "ok", message )
|
197
|
+
}})
|
198
|
+
}})
|
199
|
+
|
200
|
+
describe("with a connected client", function() { with(this) {
|
201
|
+
before(function() { this.createConnectedClient() })
|
202
|
+
|
203
|
+
it("does not send a handshake message to the server", function() { with(this) {
|
204
|
+
expect(transport, "send").given({
|
205
|
+
channel: "/meta/handshake",
|
206
|
+
version: "1.0",
|
207
|
+
supportedConnectionTypes: ["fake"],
|
208
|
+
id: instanceOf("string")
|
209
|
+
}, 60)
|
210
|
+
.exactly(0)
|
211
|
+
|
212
|
+
client.handshake()
|
213
|
+
}})
|
214
|
+
}})
|
215
|
+
}})
|
216
|
+
|
217
|
+
describe("connect", function() { with(this) {
|
218
|
+
describe("with an unconnected client", function() { with(this) {
|
219
|
+
before(function() { with(this) {
|
220
|
+
stubResponse({channel: "/meta/handshake",
|
221
|
+
successful: true,
|
222
|
+
version: "1.0",
|
223
|
+
supportedConnectionTypes: ["websocket"],
|
224
|
+
clientId: "handshakeid" })
|
225
|
+
|
226
|
+
createClient()
|
227
|
+
}})
|
228
|
+
|
229
|
+
it("handshakes before connecting", function() { with(this) {
|
230
|
+
expect(transport, "send").given({
|
231
|
+
channel: "/meta/connect",
|
232
|
+
clientId: "handshakeid",
|
233
|
+
connectionType: "fake",
|
234
|
+
id: instanceOf("string")
|
235
|
+
}, 60)
|
236
|
+
client.connect()
|
237
|
+
}})
|
238
|
+
}})
|
239
|
+
|
240
|
+
describe("with a connected client", function() { with(this) {
|
241
|
+
before(function() { this.createConnectedClient() })
|
242
|
+
|
243
|
+
it("sends a connect message to the server", function() { with(this) {
|
244
|
+
expect(transport, "send").given({
|
245
|
+
channel: "/meta/connect",
|
246
|
+
clientId: "fakeid",
|
247
|
+
connectionType: "fake",
|
248
|
+
id: instanceOf("string")
|
249
|
+
}, 60)
|
250
|
+
client.connect()
|
251
|
+
}})
|
252
|
+
|
253
|
+
it("only opens one connect request at a time", function() { with(this) {
|
254
|
+
expect(transport, "send").given({
|
255
|
+
channel: "/meta/connect",
|
256
|
+
clientId: "fakeid",
|
257
|
+
connectionType: "fake",
|
258
|
+
id: instanceOf("string")
|
259
|
+
}, 60)
|
260
|
+
.exactly(1)
|
261
|
+
|
262
|
+
client.connect()
|
263
|
+
client.connect()
|
264
|
+
}})
|
265
|
+
}})
|
266
|
+
}})
|
267
|
+
|
268
|
+
describe("disconnect", function() { with(this) {
|
269
|
+
before(function() { this.createConnectedClient() })
|
270
|
+
|
271
|
+
it("sends a disconnect message to the server", function() { with(this) {
|
272
|
+
expect(transport, "send").given({
|
273
|
+
channel: "/meta/disconnect",
|
274
|
+
clientId: "fakeid",
|
275
|
+
id: instanceOf("string")
|
276
|
+
}, 60)
|
277
|
+
client.disconnect()
|
278
|
+
}})
|
279
|
+
|
280
|
+
it("puts the client in the DISCONNECTED state", function() { with(this) {
|
281
|
+
stub(transport, "close")
|
282
|
+
client.disconnect()
|
283
|
+
assertEqual( "DISCONNECTED", client.getState() )
|
284
|
+
}})
|
285
|
+
|
286
|
+
describe("on successful response", function() { with(this) {
|
287
|
+
before(function() { with(this) {
|
288
|
+
stubResponse({channel: "/meta/disconnect",
|
289
|
+
successful: true,
|
290
|
+
clientId: "fakeid" })
|
291
|
+
}})
|
292
|
+
|
293
|
+
it("closes the transport", function() { with(this) {
|
294
|
+
expect(transport, "close")
|
295
|
+
client.disconnect()
|
296
|
+
}})
|
297
|
+
}})
|
298
|
+
}})
|
299
|
+
|
300
|
+
describe("subscribe", function() { with(this) {
|
301
|
+
before(function() { with(this) {
|
302
|
+
createConnectedClient()
|
303
|
+
this.subscribeMessage = {
|
304
|
+
channel: "/meta/subscribe",
|
305
|
+
clientId: "fakeid",
|
306
|
+
subscription: "/foo",
|
307
|
+
id: instanceOf("string")
|
308
|
+
}
|
309
|
+
}})
|
310
|
+
|
311
|
+
describe("with no prior subscriptions", function() { with(this) {
|
312
|
+
it("sends a subscribe message to the server", function() { with(this) {
|
313
|
+
expect(transport, "send").given(subscribeMessage, 60)
|
314
|
+
client.subscribe("/foo")
|
315
|
+
}})
|
316
|
+
|
317
|
+
// The Bayeux spec says the server should accept a list of subscriptions
|
318
|
+
// in one message but the cometD server doesn't actually support this
|
319
|
+
describe("with an array of subscriptions", function() { with(this) {
|
320
|
+
it("sends multiple subscribe messages", function() { with(this) {
|
321
|
+
expect(transport, "send").given({
|
322
|
+
channel: "/meta/subscribe",
|
323
|
+
clientId: "fakeid",
|
324
|
+
subscription: "/foo",
|
325
|
+
id: instanceOf("string")
|
326
|
+
}, 60)
|
327
|
+
expect(transport, "send").given({
|
328
|
+
channel: "/meta/subscribe",
|
329
|
+
clientId: "fakeid",
|
330
|
+
subscription: "/bar",
|
331
|
+
id: instanceOf("string")
|
332
|
+
}, 60)
|
333
|
+
client.subscribe(["/foo", "/bar"])
|
334
|
+
}})
|
335
|
+
|
336
|
+
it("returns an array of subscriptions", function() { with(this) {
|
337
|
+
stub(transport, "send")
|
338
|
+
var subs = client.subscribe(["/foo", "/bar"])
|
339
|
+
assertEqual( 2, subs.length )
|
340
|
+
assertKindOf( Faye.Subscription, subs[0] )
|
341
|
+
}})
|
342
|
+
}})
|
343
|
+
|
344
|
+
describe("on successful response", function() { with(this) {
|
345
|
+
before(function() { with(this) {
|
346
|
+
stubResponse({channel: "/meta/subscribe",
|
347
|
+
successful: true,
|
348
|
+
clientId: "fakeid",
|
349
|
+
subscription: "/foo/*" })
|
350
|
+
}})
|
351
|
+
|
352
|
+
it("sets up a listener for the subscribed channel", function() { with(this) {
|
353
|
+
var message
|
354
|
+
client.subscribe("/foo/*", function(m) { message = m })
|
355
|
+
client.receiveMessage({channel: "/foo/bar", data: "hi"})
|
356
|
+
assertEqual( "hi", message )
|
357
|
+
}})
|
358
|
+
|
359
|
+
it("does not call the listener for non-matching channels", function() { with(this) {
|
360
|
+
var message
|
361
|
+
client.subscribe("/foo/*", function(m) { message = m })
|
362
|
+
client.receiveMessage({channel: "/bar", data: "hi"})
|
363
|
+
assertEqual( undefined, message )
|
364
|
+
}})
|
365
|
+
|
366
|
+
it("activates the subscription", function() { with(this) {
|
367
|
+
var active = false
|
368
|
+
client.subscribe("/foo/*").callback(function() { active = true })
|
369
|
+
assert( active )
|
370
|
+
}})
|
371
|
+
|
372
|
+
describe("with an incoming extension installed", function() { with(this) {
|
373
|
+
before(function() { with(this) {
|
374
|
+
var extension = {
|
375
|
+
incoming: function(message, callback) {
|
376
|
+
if (message.data) message.data.changed = true
|
377
|
+
callback(message)
|
378
|
+
}
|
379
|
+
}
|
380
|
+
client.addExtension(extension)
|
381
|
+
this.message = null
|
382
|
+
client.subscribe("/foo/*", function(m) { message = m })
|
383
|
+
}})
|
384
|
+
|
385
|
+
it("passes delivered messages through the extension", function() { with(this) {
|
386
|
+
client.receiveMessage({channel: "/foo/bar", data: {hello: "there"}})
|
387
|
+
assertEqual( {hello: "there", changed: true}, message )
|
388
|
+
}})
|
389
|
+
}})
|
390
|
+
|
391
|
+
describe("with an outgoing extension installed", function() { with(this) {
|
392
|
+
before(function() { with(this) {
|
393
|
+
var extension = {
|
394
|
+
outgoing: function(message, callback) {
|
395
|
+
if (message.data) message.data.changed = true
|
396
|
+
callback(message)
|
397
|
+
}
|
398
|
+
}
|
399
|
+
client.addExtension(extension)
|
400
|
+
this.message = null
|
401
|
+
client.subscribe("/foo/*", function(m) { message = m })
|
402
|
+
}})
|
403
|
+
|
404
|
+
it("leaves messages unchanged", function() { with(this) {
|
405
|
+
client.receiveMessage({channel: "/foo/bar", data: {hello: "there"}})
|
406
|
+
assertEqual( {hello: "there"}, message )
|
407
|
+
}})
|
408
|
+
}})
|
409
|
+
|
410
|
+
describe("with an incoming extension that invalidates the response", function() { with(this) {
|
411
|
+
before(function() { with(this) {
|
412
|
+
var extension = {
|
413
|
+
incoming: function(message, callback) {
|
414
|
+
if (message.channel === "/meta/subscribe") message.successful = false
|
415
|
+
callback(message)
|
416
|
+
}
|
417
|
+
}
|
418
|
+
client.addExtension(extension)
|
419
|
+
}})
|
420
|
+
|
421
|
+
it("does not set up a listener for the subscribed channel", function() { with(this) {
|
422
|
+
var message
|
423
|
+
client.subscribe("/foo/*", function(m) { message = m })
|
424
|
+
client.receiveMessage({channel: "/foo/bar", data: "hi"})
|
425
|
+
assertEqual( undefined, message )
|
426
|
+
}})
|
427
|
+
|
428
|
+
it("does not activate the subscription", function() { with(this) {
|
429
|
+
var active = false
|
430
|
+
client.subscribe("/foo/*").callback(function() { active = true })
|
431
|
+
assert( !active )
|
432
|
+
}})
|
433
|
+
}})
|
434
|
+
}})
|
435
|
+
|
436
|
+
describe("on unsuccessful response", function() { with(this) {
|
437
|
+
before(function() { with(this) {
|
438
|
+
stubResponse({channel: "/meta/subscribe",
|
439
|
+
successful: false,
|
440
|
+
error: "403:/meta/foo:Forbidden channel",
|
441
|
+
clientId: "fakeid",
|
442
|
+
subscription: "/meta/foo" })
|
443
|
+
}})
|
444
|
+
|
445
|
+
it("does not set up a listener for the subscribed channel", function() { with(this) {
|
446
|
+
var message
|
447
|
+
client.subscribe("/meta/foo", function(m) { message = m })
|
448
|
+
client.receiveMessage({channel: "/meta/foo", data: "hi"})
|
449
|
+
assertEqual( undefined, message )
|
450
|
+
}})
|
451
|
+
|
452
|
+
it("does not activate the subscription", function() { with(this) {
|
453
|
+
var active = false
|
454
|
+
client.subscribe("/meta/foo").callback(function() { active = true })
|
455
|
+
assert( !active )
|
456
|
+
}})
|
457
|
+
|
458
|
+
it("reports the error through an errback", function() { with(this) {
|
459
|
+
var error = null
|
460
|
+
client.subscribe("/meta/foo").errback(function(e) { error = e })
|
461
|
+
assertEqual( objectIncluding({code: 403, params: ["/meta/foo"], message: "Forbidden channel"}), error )
|
462
|
+
}})
|
463
|
+
}})
|
464
|
+
}})
|
465
|
+
|
466
|
+
describe("with an existing subscription", function() { with(this) {
|
467
|
+
before(function() { with(this) {
|
468
|
+
subscribe(client, "/foo/*")
|
469
|
+
}})
|
470
|
+
|
471
|
+
it("does not send another subscribe message to the server", function() { with(this) {
|
472
|
+
expect(transport, "send").given(subscribeMessage, 60).exactly(0)
|
473
|
+
client.subscribe("/foo/*")
|
474
|
+
}})
|
475
|
+
|
476
|
+
it("sets up another listener on the channel", function() { with(this) {
|
477
|
+
client.subscribe("/foo/*", function() { subsCalled += 1 })
|
478
|
+
client.receiveMessage({channel: "/foo/bar", data: "hi"})
|
479
|
+
assertEqual( 2, subsCalled )
|
480
|
+
}})
|
481
|
+
|
482
|
+
it("activates the subscription", function() { with(this) {
|
483
|
+
var active = false
|
484
|
+
client.subscribe("/foo/*").callback(function() { active = true })
|
485
|
+
assert( active )
|
486
|
+
}})
|
487
|
+
}})
|
488
|
+
}})
|
489
|
+
|
490
|
+
describe("unsubscribe", function() { with(this) {
|
491
|
+
before(function() { with(this) {
|
492
|
+
createConnectedClient()
|
493
|
+
this.unsubscribeMessage = {
|
494
|
+
channel: "/meta/unsubscribe",
|
495
|
+
clientId: "fakeid",
|
496
|
+
subscription: "/foo/*",
|
497
|
+
id: instanceOf("string")
|
498
|
+
}
|
499
|
+
}})
|
500
|
+
|
501
|
+
describe("with no subscriptions", function() { with(this) {
|
502
|
+
it("does not send an unsubscribe message to the server", function() { with(this) {
|
503
|
+
expect(transport, "send").given(unsubscribeMessage, 60).exactly(0)
|
504
|
+
client.unsubscribe("/foo/*")
|
505
|
+
}})
|
506
|
+
}})
|
507
|
+
|
508
|
+
describe("with a single subscription", function() { with(this) {
|
509
|
+
before(function() { with(this) {
|
510
|
+
this.message = null
|
511
|
+
this.listener = function(m) { message = m }
|
512
|
+
subscribe(client, "/foo/*", listener)
|
513
|
+
}})
|
514
|
+
|
515
|
+
it("sends an unsubscribe message to the server", function() { with(this) {
|
516
|
+
expect(transport, "send").given(unsubscribeMessage, 60)
|
517
|
+
client.unsubscribe("/foo/*")
|
518
|
+
}})
|
519
|
+
|
520
|
+
it("removes the listener from the channel", function() { with(this) {
|
521
|
+
client.receiveMessage({channel: "/foo/bar", data: "first"})
|
522
|
+
client.unsubscribe("/foo/*", listener)
|
523
|
+
client.receiveMessage({channel: "/foo/bar", data: "second"})
|
524
|
+
assertEqual( "first", message )
|
525
|
+
}})
|
526
|
+
}})
|
527
|
+
|
528
|
+
describe("with multiple subscriptions to the same channel", function() { with(this) {
|
529
|
+
before(function() { with(this) {
|
530
|
+
this.messages = []
|
531
|
+
this.hey = function(m) { messages.push("hey " + m.text) }
|
532
|
+
this.bye = function(m) { messages.push("bye " + m.text) }
|
533
|
+
subscribe(client, "/foo/*", hey)
|
534
|
+
subscribe(client, "/foo/*", bye)
|
535
|
+
}})
|
536
|
+
|
537
|
+
it("removes one of the listeners from the channel", function() { with(this) {
|
538
|
+
client.receiveMessage({channel: "/foo/bar", data: {text: "you"}})
|
539
|
+
client.unsubscribe("/foo/*", hey)
|
540
|
+
client.receiveMessage({channel: "/foo/bar", data: {text: "you"}})
|
541
|
+
assertEqual( ["hey you", "bye you", "bye you"], messages)
|
542
|
+
}})
|
543
|
+
|
544
|
+
it("does not send an unsubscribe message if one listener is removed", function() { with(this) {
|
545
|
+
expect(transport, "send").given(unsubscribeMessage, 60).exactly(0)
|
546
|
+
client.unsubscribe("/foo/*", bye)
|
547
|
+
}})
|
548
|
+
|
549
|
+
it("sends an unsubscribe message if each listener is removed", function() { with(this) {
|
550
|
+
expect(transport, "send").given(unsubscribeMessage, 60)
|
551
|
+
client.unsubscribe("/foo/*", bye)
|
552
|
+
client.unsubscribe("/foo/*", hey)
|
553
|
+
}})
|
554
|
+
|
555
|
+
it("sends an unsubscribe message if all listeners are removed", function() { with(this) {
|
556
|
+
expect(transport, "send").given(unsubscribeMessage, 60)
|
557
|
+
client.unsubscribe("/foo/*")
|
558
|
+
}})
|
559
|
+
}})
|
560
|
+
|
561
|
+
describe("with multiple subscriptions to different channels", function() { with(this) {
|
562
|
+
before(function() { with(this) {
|
563
|
+
subscribe(client, "/foo")
|
564
|
+
subscribe(client, "/bar")
|
565
|
+
}})
|
566
|
+
|
567
|
+
it("sends multiple unsubscribe messages if given an array", function() { with(this) {
|
568
|
+
expect(transport, "send").given({
|
569
|
+
channel: "/meta/unsubscribe",
|
570
|
+
clientId: "fakeid",
|
571
|
+
subscription: "/foo",
|
572
|
+
id: instanceOf("string")
|
573
|
+
}, 60)
|
574
|
+
expect(transport, "send").given({
|
575
|
+
channel: "/meta/unsubscribe",
|
576
|
+
clientId: "fakeid",
|
577
|
+
subscription: "/bar",
|
578
|
+
id: instanceOf("string")
|
579
|
+
}, 60)
|
580
|
+
client.unsubscribe(["/foo", "/bar"])
|
581
|
+
}})
|
582
|
+
}})
|
583
|
+
}})
|
584
|
+
|
585
|
+
describe("publish", function() { with(this) {
|
586
|
+
before(function() { this.createConnectedClient() })
|
587
|
+
|
588
|
+
it("sends the message to the server with an ID", function() { with(this) {
|
589
|
+
expect(transport, "send").given({
|
590
|
+
channel: "/messages/foo",
|
591
|
+
clientId: "fakeid",
|
592
|
+
data: {hello: "world"},
|
593
|
+
id: instanceOf("string")
|
594
|
+
}, 60)
|
595
|
+
client.publish("/messages/foo", {hello: "world"})
|
596
|
+
}})
|
597
|
+
|
598
|
+
describe("on publish failure", function() { with(this) {
|
599
|
+
before(function() { with(this) {
|
600
|
+
stubResponse({channel: "/messages/foo",
|
601
|
+
error: "407:/messages/foo:Failed to publish",
|
602
|
+
successful: false,
|
603
|
+
clientId: "fakeid" })
|
604
|
+
}})
|
605
|
+
|
606
|
+
it("should not be published", function() { with(this) {
|
607
|
+
var published = false
|
608
|
+
client.publish("/messages/foo", {text: "hi"}).callback(function() { published = true })
|
609
|
+
assert( !published )
|
610
|
+
}})
|
611
|
+
|
612
|
+
it("reports the error through an errback", function() { with(this) {
|
613
|
+
var error = null
|
614
|
+
client.publish("/messages/foo", {text: "hi"}).errback(function(e) { error = e })
|
615
|
+
assertEqual( 407, error.code )
|
616
|
+
assertEqual( ["/messages/foo"], error.params )
|
617
|
+
assertEqual( "Failed to publish", error.message )
|
618
|
+
}})
|
619
|
+
}})
|
620
|
+
|
621
|
+
describe("on receipt of the published message", function() { with(this) {
|
622
|
+
before(function() { with(this) {
|
623
|
+
stubResponse({channel: "/messages/foo",
|
624
|
+
data: {text: "hi"},
|
625
|
+
clientId: "fakeid" })
|
626
|
+
}})
|
627
|
+
|
628
|
+
it("does not trigger the callbacks", function() { with(this) {
|
629
|
+
var published = false
|
630
|
+
var publication = client.publish("/messages/foo", {text: "hi"})
|
631
|
+
publication.callback(function() { published = true })
|
632
|
+
publication.errback(function() { published = true })
|
633
|
+
assert( !published )
|
634
|
+
}})
|
635
|
+
}})
|
636
|
+
|
637
|
+
describe("with an outgoing extension installed", function() { with(this) {
|
638
|
+
before(function() { with(this) {
|
639
|
+
var extension = {
|
640
|
+
outgoing: function(message, callback) {
|
641
|
+
message.ext = {auth: "password"}
|
642
|
+
callback(message)
|
643
|
+
}
|
644
|
+
}
|
645
|
+
client.addExtension(extension)
|
646
|
+
}})
|
647
|
+
|
648
|
+
it("passes messages through the extension", function() { with(this) {
|
649
|
+
expect(transport, "send").given({
|
650
|
+
channel: "/messages/foo",
|
651
|
+
clientId: "fakeid",
|
652
|
+
data: {hello: "world"},
|
653
|
+
id: instanceOf("string"),
|
654
|
+
ext: {auth: "password"}
|
655
|
+
}, 60)
|
656
|
+
client.publish("/messages/foo", {hello: "world"})
|
657
|
+
}})
|
658
|
+
}})
|
659
|
+
|
660
|
+
describe("with an incoming extension installed", function() { with(this) {
|
661
|
+
before(function() { with(this) {
|
662
|
+
var extension = {
|
663
|
+
incoming: function(message, callback) {
|
664
|
+
message.ext = {auth: "password"}
|
665
|
+
callback(message)
|
666
|
+
}
|
667
|
+
}
|
668
|
+
client.addExtension(extension)
|
669
|
+
}})
|
670
|
+
|
671
|
+
it("leaves the message unchanged", function() { with(this) {
|
672
|
+
expect(transport, "send").given({
|
673
|
+
channel: "/messages/foo",
|
674
|
+
clientId: "fakeid",
|
675
|
+
data: {hello: "world"},
|
676
|
+
id: instanceOf("string")
|
677
|
+
}, 60)
|
678
|
+
client.publish("/messages/foo", {hello: "world"})
|
679
|
+
}})
|
680
|
+
}})
|
681
|
+
}})
|
682
|
+
|
683
|
+
describe("network notifications", function() { with(this) {
|
684
|
+
before(function() { with(this) {
|
685
|
+
createClient()
|
686
|
+
client.handshake()
|
687
|
+
}})
|
688
|
+
|
689
|
+
describe("in the default state", function() { with(this) {
|
690
|
+
it("broadcasts a down notification", function() { with(this) {
|
691
|
+
expect(client, "trigger").given("transport:down")
|
692
|
+
transport.trigger("down")
|
693
|
+
}})
|
694
|
+
|
695
|
+
it("broadcasts an up notification", function() { with(this) {
|
696
|
+
expect(client, "trigger").given("transport:up")
|
697
|
+
transport.trigger("up")
|
698
|
+
}})
|
699
|
+
}})
|
700
|
+
|
701
|
+
describe("when the transport is up", function() { with(this) {
|
702
|
+
before(function() { this.transport.trigger("up") })
|
703
|
+
|
704
|
+
it("broadcasts a down notification", function() { with(this) {
|
705
|
+
expect(client, "trigger").given("transport:down")
|
706
|
+
transport.trigger("down")
|
707
|
+
}})
|
708
|
+
|
709
|
+
it("does not broadcast an up notification", function() { with(this) {
|
710
|
+
expect(client, "trigger").exactly(0)
|
711
|
+
transport.trigger("up")
|
712
|
+
}})
|
713
|
+
}})
|
714
|
+
|
715
|
+
describe("when the transport is down", function() { with(this) {
|
716
|
+
before(function() { this.transport.trigger("down") })
|
717
|
+
|
718
|
+
it("does not broadcast a down notification", function() { with(this) {
|
719
|
+
expect(client, "trigger").exactly(0)
|
720
|
+
transport.trigger("down")
|
721
|
+
}})
|
722
|
+
|
723
|
+
it("broadcasts an up notification", function() { with(this) {
|
724
|
+
expect(client, "trigger").given("transport:up")
|
725
|
+
transport.trigger("up")
|
726
|
+
}})
|
727
|
+
}})
|
728
|
+
}})
|
729
|
+
}})
|