faye 0.3.4 → 0.5.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 +13 -0
- data/Manifest.txt +16 -33
- data/README.txt +9 -274
- data/Rakefile +4 -4
- data/lib/faye-browser-min.js +1 -0
- data/lib/faye.rb +26 -9
- data/lib/faye/{rack_adapter.rb → adapters/rack_adapter.rb} +38 -25
- data/lib/faye/error.rb +0 -7
- data/lib/faye/{logging.rb → mixins/logging.rb} +0 -0
- data/lib/faye/mixins/publisher.rb +29 -0
- data/lib/faye/{timeouts.rb → mixins/timeouts.rb} +1 -0
- data/lib/faye/{transport.rb → network/transport.rb} +32 -43
- data/lib/faye/{channel.rb → protocol/channel.rb} +23 -3
- data/lib/faye/{client.rb → protocol/client.rb} +124 -90
- data/lib/faye/{connection.rb → protocol/connection.rb} +41 -23
- data/lib/faye/protocol/extensible.rb +47 -0
- data/lib/faye/{grammar.rb → protocol/grammar.rb} +0 -0
- data/lib/faye/{server.rb → protocol/server.rb} +98 -54
- data/lib/faye/protocol/subscription.rb +23 -0
- data/lib/faye/{namespace.rb → util/namespace.rb} +0 -0
- data/lib/faye/util/web_socket.rb +119 -0
- data/lib/thin_extensions.rb +86 -0
- data/test/scenario.rb +68 -14
- data/test/test_clients.rb +215 -2
- data/test/test_server.rb +10 -10
- metadata +102 -71
- data/Jakefile +0 -13
- data/build/faye-client-min.js +0 -1
- data/build/faye.js +0 -1488
- data/examples/README.rdoc +0 -41
- data/examples/node/app.js +0 -26
- data/examples/node/client.js +0 -23
- data/examples/node/faye-client-min.js +0 -1
- data/examples/node/faye.js +0 -1488
- data/examples/rack/app.rb +0 -16
- data/examples/rack/client.rb +0 -25
- data/examples/rack/config.ru +0 -8
- data/examples/shared/public/favicon.ico +0 -0
- data/examples/shared/public/index.html +0 -49
- data/examples/shared/public/jquery.js +0 -19
- data/examples/shared/public/mootools.js +0 -4329
- data/examples/shared/public/prototype.js +0 -4874
- data/examples/shared/public/robots.txt +0 -0
- data/examples/shared/public/soapbox.js +0 -100
- data/examples/shared/public/style.css +0 -43
- data/jake.yml +0 -40
- data/lib/faye-client-min.js +0 -1
- data/test/scenario.js +0 -138
- data/test/test_clients.js +0 -195
data/History.txt
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
+
=== 0.5.0 / 2010-07-17
|
2
|
+
|
3
|
+
* Handle multiple event listeners bound to a channel
|
4
|
+
* Add extension system for adding domain-specific logic to the protocol
|
5
|
+
* Improve handling of client reconnections if the server goes down
|
6
|
+
* Change default polling interval to 0 (immediate reconnect)
|
7
|
+
* Add support for WebSockets (draft75 only) as a network transport
|
8
|
+
* Remove support for Ruby servers other than Thin
|
9
|
+
* Make client and server compatible with CometD (1.x and 2.0) components
|
10
|
+
* Improve clean-up of unused server-side connections
|
11
|
+
* Change Node API for adding Faye service to an HTTP server
|
12
|
+
|
13
|
+
|
1
14
|
=== 0.3.4 / 2010-06-20
|
2
15
|
|
3
16
|
* Stop local clients going into an infinite loop if a subscription block causes a reconnect
|
data/Manifest.txt
CHANGED
@@ -2,43 +2,26 @@ History.txt
|
|
2
2
|
Manifest.txt
|
3
3
|
README.txt
|
4
4
|
Rakefile
|
5
|
-
|
6
|
-
Jakefile
|
7
|
-
build/faye.js
|
8
|
-
build/faye-client-min.js
|
9
|
-
examples/README.rdoc
|
10
|
-
examples/shared/public/index.html
|
11
|
-
examples/shared/public/favicon.ico
|
12
|
-
examples/shared/public/jquery.js
|
13
|
-
examples/shared/public/mootools.js
|
14
|
-
examples/shared/public/prototype.js
|
15
|
-
examples/shared/public/robots.txt
|
16
|
-
examples/shared/public/soapbox.js
|
17
|
-
examples/shared/public/style.css
|
18
|
-
examples/node/app.js
|
19
|
-
examples/node/client.js
|
20
|
-
examples/node/faye.js
|
21
|
-
examples/node/faye-client-min.js
|
22
|
-
examples/rack/app.rb
|
23
|
-
examples/rack/client.rb
|
24
|
-
examples/rack/config.ru
|
25
|
-
lib/faye-client-min.js
|
5
|
+
lib/faye-browser-min.js
|
26
6
|
lib/faye.rb
|
27
|
-
lib/
|
28
|
-
lib/faye/
|
29
|
-
lib/faye/
|
7
|
+
lib/thin_extensions.rb
|
8
|
+
lib/faye/adapters/rack_adapter.rb
|
9
|
+
lib/faye/mixins/logging.rb
|
10
|
+
lib/faye/mixins/publisher.rb
|
11
|
+
lib/faye/mixins/timeouts.rb
|
12
|
+
lib/faye/network/transport.rb
|
13
|
+
lib/faye/protocol/channel.rb
|
14
|
+
lib/faye/protocol/client.rb
|
15
|
+
lib/faye/protocol/connection.rb
|
16
|
+
lib/faye/protocol/extensible.rb
|
17
|
+
lib/faye/protocol/grammar.rb
|
18
|
+
lib/faye/protocol/server.rb
|
19
|
+
lib/faye/protocol/subscription.rb
|
20
|
+
lib/faye/util/namespace.rb
|
21
|
+
lib/faye/util/web_socket.rb
|
30
22
|
lib/faye/error.rb
|
31
|
-
lib/faye/grammar.rb
|
32
|
-
lib/faye/logging.rb
|
33
|
-
lib/faye/namespace.rb
|
34
|
-
lib/faye/rack_adapter.rb
|
35
|
-
lib/faye/server.rb
|
36
|
-
lib/faye/timeouts.rb
|
37
|
-
lib/faye/transport.rb
|
38
|
-
test/scenario.js
|
39
23
|
test/scenario.rb
|
40
24
|
test/test_channel.rb
|
41
|
-
test/test_clients.js
|
42
25
|
test/test_clients.rb
|
43
26
|
test/test_grammar.rb
|
44
27
|
test/test_server.rb
|
data/README.txt
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
= Faye
|
2
2
|
|
3
|
+
* http://faye.jcoglan.com
|
3
4
|
* http://github.com/jcoglan/faye
|
4
5
|
|
5
6
|
Faye is a set of tools for dirt-simple publish-subscribe messaging
|
@@ -7,292 +8,26 @@ between web clients. It ships with easy-to-use message routing servers
|
|
7
8
|
for Node.js and Rack applications, and clients that can be used on
|
8
9
|
the server and in the browser.
|
9
10
|
|
10
|
-
|
11
|
-
== Introduction
|
12
|
-
|
13
|
-
Faye is an implementation of the Bayeux prototcol (http://svn.cometd.com/trunk/bayeux/bayeux.html),
|
14
|
-
a publish-subscribe messaging protocol designed primarily to allow
|
15
|
-
client-side JavaScript programs to send messages to each other with
|
16
|
-
low latency over HTTP. It also allows for server-side clients that let
|
17
|
-
your backend applications push data to the client side.
|
18
|
-
|
19
|
-
Bayeux works by letting clients publish and subscribe to named data
|
20
|
-
channels. For example, one client may publish a message:
|
21
|
-
|
22
|
-
clientA.publish('/foo', {hello: 'world'});
|
23
|
-
|
24
|
-
And another client can subscribe to that channel to receive messages
|
25
|
-
that are published to it:
|
26
|
-
|
27
|
-
// alerts "world"
|
28
|
-
clientB.subscribe('/foo', function(message) {
|
29
|
-
alert(message.hello);
|
30
|
-
});
|
31
|
-
|
32
|
-
Faye's messaging backend was originally developed in Ruby for a toy
|
33
|
-
project of mine, but was not written to scale across multiple processes
|
34
|
-
as required for deployment under Passenger. It has since been ported to
|
35
|
-
Node.js which is better designed for handling highly concurrent traffic.
|
36
|
-
If you use the Ruby version, I recommend deploying it behind Thin, an
|
37
|
-
event-driven Ruby web server.
|
38
|
-
|
39
|
-
The two backends are architecturally identical, using evented messaging
|
40
|
-
throughout and maintaining subscription data in memory. Neither is geared
|
41
|
-
up yet for running multi-process servers, but their event-driven setup
|
42
|
-
should let them handle more concurrent connections than a typical
|
43
|
-
threaded server.
|
44
|
-
|
45
|
-
|
46
|
-
== Installation
|
47
|
-
|
48
|
-
The JavaScript client and Node.js server are in the +build+ directory. Just
|
49
|
-
copy them onto your machine and <tt>require('path/to/faye')</tt>.
|
50
|
-
|
51
|
-
The Rack server is distributed as a Ruby gem:
|
52
|
-
|
53
|
-
sudo gem install faye
|
54
|
-
|
55
|
-
|
56
|
-
== Using the client
|
57
|
-
|
58
|
-
Both backends allow you to specify a 'mount point' that the message server
|
59
|
-
accepts requests on. Say you set this to <tt>/faye</tt>, the client script
|
60
|
-
will be available from <tt>/faye.js</tt> and should connect to <tt>/faye</tt>.
|
61
|
-
|
62
|
-
You should set up the client as follows:
|
63
|
-
|
64
|
-
<script type="text/javascript" src="/faye.js"></script>
|
65
|
-
|
66
|
-
<script type="text/javascript">
|
67
|
-
fayeClient = new Faye.Client('/faye', {timeout: 120});
|
68
|
-
</script>
|
69
|
-
|
70
|
-
The client (and the Node.js and Rack server-side clients) accepts the following
|
71
|
-
options in its constructor:
|
72
|
-
|
73
|
-
* +timeout+ - the maximum time (seconds) to wait for a connection response
|
74
|
-
from the server before attempting to reconnect. This should be greater than
|
75
|
-
the timeout set up on the server side, the idea being that if the server
|
76
|
-
doesn't send a connection response within this time then there has probably
|
77
|
-
been a network failure and we should reconnect. When the client reconnects,
|
78
|
-
any existing channel subscriptions are re-established automatically.
|
79
|
-
|
80
|
-
Take care only to have one instance of the client per page; since each one
|
81
|
-
opens a long-running request you will hit the two-requests-per-host limit
|
82
|
-
and block all other Ajax calls if you use more than one client.
|
83
|
-
|
84
|
-
This client object can be used to publish and subscribe to named channels:
|
85
|
-
|
86
|
-
fayeClient.subscribe('/path/to/channel', function(message) {
|
87
|
-
// process received message object
|
88
|
-
});
|
89
|
-
|
90
|
-
fayeClient.publish('/some/other/channel', {foo: 'bar'});
|
91
|
-
|
92
|
-
You can publish arbitrary JavaScript objects to a channel, and the object will
|
93
|
-
be transmitted as the +message+ parameter to any subscribers to that channel.
|
94
|
-
Channel names must be formatted as absolute path names as shown. Channels
|
95
|
-
beginning with <tt>/meta/</tt> are reserved for use by the messaging protocol
|
96
|
-
and may not be subscribed to.
|
97
|
-
|
98
|
-
The client can also be used cross-domain if connecting to a backend that
|
99
|
-
supports callback polling (see below under 'Transports'). Just pass in the
|
100
|
-
full path to the endpoint including the domain. Faye figures out whether the
|
101
|
-
server is on the same domain and uses an appropriate transport.
|
102
|
-
|
103
|
-
fayeClient = new Faye.Client('http://example.com/faye');
|
104
|
-
|
105
|
-
|
106
|
-
=== Transports
|
107
|
-
|
108
|
-
The Bayeux spec defines several transport mechanisms for clients to establish
|
109
|
-
low-latency connections with the server, the two required types being
|
110
|
-
<tt>long-polling</tt> and <tt>callback-polling</tt>.
|
111
|
-
|
112
|
-
<tt>long-polling</tt> is where the client makes an <tt>XMLHttpRequest</tt>
|
113
|
-
to the server, and the server waits until it has new messages for that client
|
114
|
-
before it returns a response. Faye's client and server backends all support
|
115
|
-
this transport. Since it uses XHR, the server endpoint must be on the same
|
116
|
-
domain as the client page.
|
117
|
-
|
118
|
-
<tt>callback-polling</tt> involves the client using JSON-P to make the request.
|
119
|
-
The server wraps its JSON responses in a JavaScript function call that the
|
120
|
-
client then executes. This transport does not require the client and server
|
121
|
-
to be on the same domain. Faye's client supports this transport, as does the
|
122
|
-
Node.js backend. The Rack backend supports it if running under Thin.
|
123
|
-
|
124
|
-
Faye also supports an <tt>in-process</tt> transport, which is used when a
|
125
|
-
server-side client has direct in-memory access to the server without going
|
126
|
-
over HTTP.
|
127
|
-
|
128
|
-
|
129
|
-
== Using the backend
|
130
|
-
|
131
|
-
Both the Node.js and Rack backends have identical architectures and are
|
132
|
-
designed to be easily plugged into other web services. For Rack the adapter
|
133
|
-
is explicitly designed as middleware, while for Node.js the adapter is
|
134
|
-
a simple object you can manually offload requests to.
|
135
|
-
|
136
|
-
The backends provide a service for routing messages between clients; no
|
137
|
-
server-side programming is needed to control them, just start them up
|
138
|
-
and they'll sit there merrily chewing through requests. They are both
|
139
|
-
currently single-process since they hold all channel subscriptions in
|
140
|
-
memory. This means, for example, that the Rack backend will not work
|
141
|
-
under Passenger since that spawns multiple Ruby processes to serve your
|
142
|
-
site.
|
143
|
-
|
144
|
-
Faye uses async messaging internally so nothing blocks while waiting for
|
145
|
-
new messages. If running under a Ruby web server (except Thin) the server
|
146
|
-
will block while waiting for a response from Faye. Thin supports async
|
147
|
-
responses and is a better choice for long-running concurrent connections.
|
148
|
-
|
149
|
-
Both backends support the following initialization options:
|
150
|
-
|
151
|
-
* +mount+ - the path at which the Faye service is accessible. e.g. if
|
152
|
-
set to <tt>/faye</tt>, the Faye endpoint will be at <tt>http://yoursite.com/faye</tt>
|
153
|
-
and the client script at <tt>http://yoursite.com/faye.js</tt>.
|
154
|
-
* +timeout+ - the maximum time (seconds) to hold a long-running request
|
155
|
-
open before returning a response. This must be smaller than the timeout
|
156
|
-
on your frontend webserver to make sure Faye sends a response before
|
157
|
-
the server kills the connection.
|
158
|
-
|
159
|
-
Regarding the +mount+ parameter: Faye will respond to any path under the
|
160
|
-
mount point, so <tt>/faye</tt>, <tt>/faye.js</tt> and <tt>/faye/some/subpath</tt>
|
161
|
-
will all be handled by Faye rather than your application. This is so that
|
162
|
-
Faye can interoperate with clients that use different URLs for different
|
163
|
-
message types.
|
164
|
-
|
165
|
-
Usage examples and a demo app are in the +examples+ directory.
|
166
|
-
|
167
|
-
|
168
|
-
=== Node.js backend
|
169
|
-
|
170
|
-
Here's a very simple Node web server that offloads requests to the messaging
|
171
|
-
service to Faye and deals with all other requests itself. The Faye object
|
172
|
-
returns +true+ or +false+ to indicate whether it handled the request.
|
173
|
-
You'll need <tt>faye.js</tt> and <tt>faye-client-min.js</tt> in the same
|
174
|
-
directory.
|
175
|
-
|
176
|
-
var http = require('http')
|
177
|
-
faye = require('./faye');
|
178
|
-
|
179
|
-
var server = new faye.NodeAdapter({mount: '/faye', timeout: 45});
|
180
|
-
|
181
|
-
http.createServer(function(request, response) {
|
182
|
-
if (server.call(request, response)) return;
|
183
|
-
|
184
|
-
response.sendHeader(200, {'Content-Type': 'text/plain'});
|
185
|
-
response.write('Hello, non-Faye request!');
|
186
|
-
response.close();
|
187
|
-
|
188
|
-
}).listen(9292);
|
189
|
-
|
190
|
-
|
191
|
-
=== Node.js client
|
192
|
-
|
193
|
-
Faye's JavaScript client can be used server-side under Node. There are
|
194
|
-
two ways you can set a client up: either connect to as remote server
|
195
|
-
over HTTP or connect directly to the <tt>NodeAdapter</tt>. Either way,
|
196
|
-
the API for the client is exactly as it is in the browser.
|
197
|
-
|
198
|
-
// Remote client
|
199
|
-
client = new faye.Client('http://example.com/faye', {timeout: 120});
|
200
|
-
|
201
|
-
// Local client
|
202
|
-
server = new faye.NodeAdapter(options);
|
203
|
-
client = server.getClient();
|
204
|
-
|
205
|
-
Note <tt>getClient()</tt> returns the same client object every time you
|
206
|
-
call it, so any subscriptions you make with it are retained.
|
207
|
-
|
208
|
-
|
209
|
-
=== Rack backend
|
210
|
-
|
211
|
-
Faye can be installed as middleware in front of any Rack application. The
|
212
|
-
Rack backend uses EventMachine for asynchronous message distribution and
|
213
|
-
timeouts. It can run under any web server, though Thin is best placed for
|
214
|
-
handling long-running concurrent connections and supports async server
|
215
|
-
responses. Under other servers the request thread will block while waiting
|
216
|
-
for a response from Faye.
|
217
|
-
|
218
|
-
Here's a <tt>config.ru</tt> for running it with Sinatra:
|
219
|
-
|
220
|
-
require 'rubygems'
|
221
|
-
require 'faye'
|
222
|
-
require 'sinatra'
|
223
|
-
require 'path/to/sinatra/app'
|
224
|
-
|
225
|
-
use Faye::RackAdapter, :mount => '/faye',
|
226
|
-
:timeout => 25
|
227
|
-
|
228
|
-
run Sinatra::Application
|
229
|
-
|
230
|
-
This functions much the same as the Node.js example; Faye catches messaging
|
231
|
-
requests and deals with them, letting all other requests fall down the
|
232
|
-
stack of Rack middlewares.
|
233
|
-
|
234
|
-
|
235
|
-
=== Rack client
|
236
|
-
|
237
|
-
Again mirroring the Node tools, Faye has a server-side Ruby client that
|
238
|
-
can be used under EventMachine. Setup is identical:
|
239
|
-
|
240
|
-
# Remote client
|
241
|
-
client = Faye::Client.new('http://example.com/faye', :timeout => 120)
|
242
|
-
|
243
|
-
# Local client
|
244
|
-
server = Faye::RackAdapter.new(options)
|
245
|
-
client = server.get_client
|
246
|
-
|
247
|
-
Both types of client must be run inside EventMachine, and can publish and
|
248
|
-
subscribe just like the JavaScript client:
|
249
|
-
|
250
|
-
EM.run {
|
251
|
-
client.subscribe('/from/*') do |message|
|
252
|
-
# do something with message hash
|
253
|
-
end
|
254
|
-
|
255
|
-
client.publish('/from/jcoglan', 'hello' => 'world')
|
256
|
-
}
|
257
|
-
|
258
|
-
If you're using the <tt>RackAdapter</tt> as middleware and running your app
|
259
|
-
using Thin, applications further down the chain can get a client for it from
|
260
|
-
the Rack environment, for example in a Sinatra application:
|
261
|
-
|
262
|
-
get '/post' do
|
263
|
-
env['faye.client'].publish('/mentioning/*', {
|
264
|
-
'user' => 'sinatra',
|
265
|
-
'message' => params[:message]
|
266
|
-
})
|
267
|
-
params[:message]
|
268
|
-
end
|
11
|
+
See http://faye.jcoglan.com for documentation.
|
269
12
|
|
270
13
|
|
271
14
|
== Development
|
272
15
|
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
sudo gem install eventmachine em-http-request rack thin json jake hoe
|
16
|
+
To hack on Faye, you'll need Ruby and Jake, which we use to build
|
17
|
+
the JavaScript packages. Once you have Ruby installed:
|
277
18
|
|
278
|
-
|
279
|
-
directories. They are generated using Jake, which you should run after
|
280
|
-
editing the JavaScript source.
|
19
|
+
sudo gem install jake
|
281
20
|
|
282
|
-
|
21
|
+
Just run `jake` from the root of the project to build the JavaScripts.
|
283
22
|
|
284
|
-
|
285
|
-
|
23
|
+
The Ruby version depends on the following gems, which you'll need
|
24
|
+
to install:
|
286
25
|
|
287
|
-
|
288
|
-
if you update both the Ruby and JavaScript versions with equivalent
|
289
|
-
changes where appropriate.
|
26
|
+
sudo gem install hoe eventmachine em-http-request rack thin json
|
290
27
|
|
291
28
|
|
292
29
|
== To-do
|
293
30
|
|
294
|
-
* Investigate WebSockets as a possible message transport
|
295
|
-
* Support Bayeux extensions for authentication
|
296
31
|
* Let local server-side clients listen to <tt>/meta/*</tt> channels
|
297
32
|
* Provide support for user-defined <tt>/service/*</tt> channels
|
298
33
|
* Allow server to scale to multiple nodes
|
data/Rakefile
CHANGED
@@ -4,10 +4,10 @@ require 'rubygems'
|
|
4
4
|
require 'hoe'
|
5
5
|
require './lib/faye.rb'
|
6
6
|
|
7
|
-
Hoe.spec('faye') do
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
Hoe.spec('faye') do
|
8
|
+
self.developer('James Coglan', 'jcoglan@googlemail.com')
|
9
|
+
self.description = 'Simple pub/sub messaging for the web'
|
10
|
+
self.extra_deps = [
|
11
11
|
['eventmachine', '>= 0.12'],
|
12
12
|
['em-http-request', '>= 0.2'],
|
13
13
|
['rack', '>= 1.0'],
|
@@ -0,0 +1 @@
|
|
1
|
+
if(!this.Faye)Faye={};Faye.extend=function(a,b,c){if(!b)return a;for(var d in b){if(!b.hasOwnProperty(d))continue;if(a.hasOwnProperty(d)&&c===false)continue;if(a[d]!==b[d])a[d]=b[d]}return a};Faye.extend(Faye,{VERSION:'0.5.0',BAYEUX_VERSION:'1.0',ID_LENGTH:128,JSONP_CALLBACK:'jsonpcallback',CONNECTION_TYPES:['long-polling','callback-polling','websocket'],MANDATORY_CONNECTION_TYPES:['long-polling','callback-polling','in-process'],ENV:(function(){return this})(),random:function(a){a=a||this.ID_LENGTH;if(a>32){var b=Math.ceil(a/32),c='';while(b--)c+=this.random(32);return c}var d=Math.pow(2,a);return Math.floor(Math.random()*d).toString(36)},commonElement:function(a,b){for(var c=0,d=a.length;c<d;c++){if(this.indexOf(b,a[c])!==-1)return a[c]}return null},indexOf:function(a,b){for(var c=0,d=a.length;c<d;c++){if(a[c]===b)return c}return-1},each:function(a,b,c){if(a instanceof Array){for(var d=0,f=a.length;d<f;d++){if(a[d]!==undefined)b.call(c||null,a[d],d)}}else{for(var g in a){if(a.hasOwnProperty(g))b.call(c||null,g,a[g])}}},map:function(a,b,c){var d=[];this.each(a,function(){d.push(b.apply(c||null,arguments))});return d},filter:function(a,b,c){var d=[];this.each(a,function(){if(b.apply(c,arguments))d.push(arguments[0])});return d},size:function(a){var b=0;this.each(a,function(){b+=1});return b},enumEqual:function(c,d){if(d instanceof Array){if(!(c instanceof Array))return false;var f=c.length;if(f!==d.length)return false;while(f--){if(c[f]!==d[f])return false}return true}else{if(!(c instanceof Object))return false;if(this.size(d)!==this.size(c))return false;var g=true;this.each(c,function(a,b){g=g&&(d[a]===b)});return g}},toJSON:function(a){if(this.stringify)return this.stringify(a,function(key,value){return(this[key]instanceof Array)?this[key]:value});return JSON.stringify(a)},timestamp:function(){var b=new Date(),c=b.getFullYear(),d=b.getMonth()+1,f=b.getDate(),g=b.getHours(),h=b.getMinutes(),j=b.getSeconds();var i=function(a){return a<10?'0'+a:String(a)};return i(c)+'-'+i(d)+'-'+i(f)+' '+i(g)+':'+i(h)+':'+i(j)}});Faye.Class=function(a,b){if(typeof a!=='function'){b=a;a=Object}var c=function(){if(!this.initialize)return this;return this.initialize.apply(this,arguments)||this};var d=function(){};d.prototype=a.prototype;c.prototype=new d();Faye.extend(c.prototype,b);return c};Faye.Namespace=Faye.Class({initialize:function(){this._u={}},generate:function(){var a=Faye.random();while(this._u.hasOwnProperty(a))a=Faye.random();return this._u[a]=a}});Faye.Deferrable={callback:function(a,b){if(!a)return;if(this._B==='succeeded')return a.apply(b,this._v);this._9=this._9||[];this._9.push([a,b])},setDeferredStatus:function(){var b=Array.prototype.slice.call(arguments),c=b.shift();this._B=c;this._v=b;if(c!=='succeeded')return;if(!this._9)return;Faye.each(this._9,function(a){a[0].apply(a[1],this._v)},this);this._9=[]}};Faye.Publisher={countSubscribers:function(a){if(!this._2||!this._2[a])return 0;return this._2[a].length},addSubscriber:function(a,b,c){this._2=this._2||{};var d=this._2[a]=this._2[a]||[];d.push([b,c])},removeSubscriber:function(a,b,c){if(!this._2||!this._2[a])return;var d=this._2[a],f=d.length;while(f--){if(b&&d[f][0]!==b)continue;if(c&&d[f][1]!==c)continue;d.splice(f,1)}},publishEvent:function(){var b=Array.prototype.slice.call(arguments),c=b.shift();if(!this._2||!this._2[c])return;Faye.each(this._2[c],function(a){a[0].apply(a[1],b)})}};Faye.Timeouts={addTimeout:function(a,b,c,d){this._6=this._6||{};if(this._6.hasOwnProperty(a))return;var f=this;this._6[a]=setTimeout(function(){delete f._6[a];c.call(d)},1000*b)},removeTimeout:function(a){this._6=this._6||{};var b=this._6[a];if(!b)return;clearTimeout(b);delete this._6[a]}};Faye.Logging={LOG_LEVELS:{error:3,warn:2,info:1,debug:0},logLevel:'error',log:function(a,b){if(!Faye.logger)return;var c=Faye.Logging.LOG_LEVELS;if(c[Faye.Logging.logLevel]>c[b])return;var a=Array.prototype.slice.apply(a),d=' ['+b.toUpperCase()+'] [Faye',f=null,g=a.shift().replace(/\?/g,function(){try{return Faye.toJSON(a.shift())}catch(e){return'[Object]'}});for(var h in Faye){if(f)continue;if(typeof Faye[h]!=='function')continue;if(this instanceof Faye[h])f=h}if(f)d+='.'+f;d+='] ';Faye.logger(Faye.timestamp()+d+g)}};Faye.each(Faye.Logging.LOG_LEVELS,function(a,b){Faye.Logging[a]=function(){this.log(arguments,a)}});Faye.Grammar={LOWALPHA:/^[a-z]$/,UPALPHA:/^[A-Z]$/,ALPHA:/^([a-z]|[A-Z])$/,DIGIT:/^[0-9]$/,ALPHANUM:/^(([a-z]|[A-Z])|[0-9])$/,MARK:/^(\-|\_|\!|\~|\(|\)|\$|\@)$/,STRING:/^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*$/,TOKEN:/^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+$/,INTEGER:/^([0-9])+$/,CHANNEL_SEGMENT:/^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+$/,CHANNEL_SEGMENTS:/^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+(\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+)*$/,CHANNEL_NAME:/^\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+(\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+)*$/,WILD_CARD:/^\*{1,2}$/,CHANNEL_PATTERN:/^(\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+)*\/\*{1,2}$/,VERSION_ELEMENT:/^(([a-z]|[A-Z])|[0-9])(((([a-z]|[A-Z])|[0-9])|\-|\_))*$/,VERSION:/^([0-9])+(\.(([a-z]|[A-Z])|[0-9])(((([a-z]|[A-Z])|[0-9])|\-|\_))*)*$/,CLIENT_ID:/^((([a-z]|[A-Z])|[0-9]))+$/,ID:/^((([a-z]|[A-Z])|[0-9]))+$/,ERROR_MESSAGE:/^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*$/,ERROR_ARGS:/^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*(,(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*)*$/,ERROR_CODE:/^[0-9][0-9][0-9]$/,ERROR:/^([0-9][0-9][0-9]:(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*(,(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*)*:(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*|[0-9][0-9][0-9]::(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*)$/};Faye.Extensible={addExtension:function(a){this._7=this._7||[];this._7.push(a);if(a.added)a.added()},removeExtension:function(a){if(!this._7)return;var b=this._7.length;while(b--){if(this._7[b]!==a)continue;this._7.splice(b,1);if(a.removed)a.removed()}},pipeThroughExtensions:function(c,d,f,g){if(!this._7)return f.call(g,d);var h=this._7.slice();var j=function(a){if(!a)return f.call(g,a);var b=h.shift();if(!b)return f.call(g,a);if(b[c])b[c](a,j);else j(a)};j(d)}};Faye.Channel=Faye.Class({initialize:function(a){this.id=this.name=a},push:function(a){this.publishEvent('message',a)}});Faye.extend(Faye.Channel.prototype,Faye.Publisher);Faye.extend(Faye.Channel,{HANDSHAKE:'/meta/handshake',CONNECT:'/meta/connect',SUBSCRIBE:'/meta/subscribe',UNSUBSCRIBE:'/meta/unsubscribe',DISCONNECT:'/meta/disconnect',META:'meta',SERVICE:'service',isValid:function(a){return Faye.Grammar.CHANNEL_NAME.test(a)||Faye.Grammar.CHANNEL_PATTERN.test(a)},parse:function(a){if(!this.isValid(a))return null;return a.split('/').slice(1)},isMeta:function(a){var b=this.parse(a);return b?(b[0]===this.META):null},isService:function(a){var b=this.parse(a);return b?(b[0]===this.SERVICE):null},isSubscribable:function(a){if(!this.isValid(a))return null;return!this.isMeta(a)&&!this.isService(a)},Tree:Faye.Class({initialize:function(a){this._4=a;this._a={}},eachChild:function(c,d){Faye.each(this._a,function(a,b){c.call(d,a,b)})},each:function(c,d,f){this.eachChild(function(a,b){a=c.concat(a);b.each(a,d,f)});if(this._4!==undefined)d.call(f,c,this._4)},getKeys:function(){return this.map(function(a,b){return'/'+a.join('/')})},map:function(c,d){var f=[];this.each([],function(a,b){f.push(c.call(d,a,b))});return f},get:function(a){var b=this.traverse(a);return b?b._4:null},set:function(a,b){var c=this.traverse(a,true);if(c)c._4=b},traverse:function(a,b){if(typeof a==='string')a=Faye.Channel.parse(a);if(a===null)return null;if(a.length===0)return this;var c=this._a[a[0]];if(!c&&!b)return null;if(!c)c=this._a[a[0]]=new Faye.Channel.Tree();return c.traverse(a.slice(1),b)},findOrCreate:function(a){var b=this.get(a);if(b)return b;b=new Faye.Channel(a);this.set(a,b);return b},glob:function(f){if(typeof f==='string')f=Faye.Channel.parse(f);if(f===null)return[];if(f.length===0)return(this._4===undefined)?[]:[this._4];var g=[];if(Faye.enumEqual(f,['*'])){Faye.each(this._a,function(a,b){if(b._4!==undefined)g.push(b._4)});return g}if(Faye.enumEqual(f,['**'])){g=this.map(function(a,b){return b});if(this._4!==undefined)g.pop();return g}Faye.each(this._a,function(b,c){if(b!==f[0]&&b!=='*')return;var d=c.glob(f.slice(1));Faye.each(d,function(a){g.push(a)})});if(this._a['**'])g.push(this._a['**']._4);return g},subscribe:function(c,d,f){if(!d)return;Faye.each(c,function(a){var b=this.findOrCreate(a);b.addSubscriber('message',d,f)},this)},unsubscribe:function(a,b,c){var d=this.get(a);if(!d)return false;d.removeSubscriber('message',b,c);return d.countSubscribers('message')===0},distributeMessage:function(b){var c=this.glob(b.channel);Faye.each(c,function(a){a.publishEvent('message',b.data)})}})});Faye.Subscription=Faye.Class({initialize:function(a,b,c,d){this._c=a;this._b=b;this._m=c;this._d=d;this._w=false},cancel:function(){if(this._w)return;this._c.unsubscribe(this._b,this._m,this._d);this._w=true},unsubscribe:function(){this.cancel()}});Faye.Client=Faye.Class({UNCONNECTED:1,CONNECTING:2,CONNECTED:3,DISCONNECTED:4,HANDSHAKE:'handshake',RETRY:'retry',NONE:'none',CONNECTION_TIMEOUT:60.0,DEFAULT_ENDPOINT:'/bayeux',MAX_DELAY:0.1,INTERVAL:0.0,initialize:function(a,b){this.info('New client created for ?',a);this._3=a||this.DEFAULT_ENDPOINT;this._x=b||{};this._f=Faye.Transport.get(this,Faye.MANDATORY_CONNECTION_TYPES);this._1=this.UNCONNECTED;this._h=[];this._b=new Faye.Channel.Tree();this._C=new Faye.Namespace();this._n={};this._8={reconnect:this.RETRY,interval:1000*(this._x.interval||this.INTERVAL),timeout:1000*(this._x.timeout||this.CONNECTION_TIMEOUT)};if(Faye.Event)Faye.Event.on(Faye.ENV,'beforeunload',this.disconnect,this)},handshake:function(b,c){if(this._8.reconnect===this.NONE)return;if(this._1!==this.UNCONNECTED)return;this._1=this.CONNECTING;var d=this;this.info('Initiating handshake with ?',this._3);this._e({channel:Faye.Channel.HANDSHAKE,version:Faye.BAYEUX_VERSION,supportedConnectionTypes:[this._f.connectionType]},function(a){if(a.successful){this._1=this.CONNECTED;this._0=a.clientId;this._f=Faye.Transport.get(this,a.supportedConnectionTypes);this.info('Handshake successful: ?',this._0);this.subscribe(this._b.getKeys());if(b)b.call(c)}else{this.info('Handshake unsuccessful');setTimeout(function(){d.handshake(b,c)},this._8.interval);this._1=this.UNCONNECTED}},this)},connect:function(a,b){if(this._8.reconnect===this.NONE)return;if(this._1===this.DISCONNECTED)return;if(this._1===this.UNCONNECTED)return this.handshake(function(){this.connect(a,b)},this);this.callback(a,b);if(this._1!==this.CONNECTED)return;this.info('Calling deferred actions for ?',this._0);this.setDeferredStatus('succeeded');this.setDeferredStatus('deferred');if(this._o)return;this._o=true;this.info('Initiating connection for ?',this._0);this._e({channel:Faye.Channel.CONNECT,clientId:this._0,connectionType:this._f.connectionType},this._y,this)},disconnect:function(){if(this._1!==this.CONNECTED)return;this._1=this.DISCONNECTED;this.info('Disconnecting ?',this._0);this._e({channel:Faye.Channel.DISCONNECT,clientId:this._0});this.info('Clearing channel listeners for ?',this._0);this._b=new Faye.Channel.Tree()},subscribe:function(c,d,f){if(c instanceof Array)return Faye.each(c,function(channel){this.subscribe(channel,d,f)},this);this._p(c);this.connect(function(){this.info('Client ? attempting to subscribe to ?',this._0,c);this._e({channel:Faye.Channel.SUBSCRIBE,clientId:this._0,subscription:c},function(a){if(!a.successful)return;var b=[].concat(a.subscription);this.info('Subscription acknowledged for ? to ?',this._0,b);this._b.subscribe(b,d,f)},this)},this);return new Faye.Subscription(this,c,d,f)},unsubscribe:function(c,d,f){if(c instanceof Array)return Faye.each(c,function(channel){this.unsubscribe(channel,d,f)},this);this._p(c);var g=this._b.unsubscribe(c,d,f);if(!g)return;this.connect(function(){this.info('Client ? attempting to unsubscribe from ?',this._0,c);this._e({channel:Faye.Channel.UNSUBSCRIBE,clientId:this._0,subscription:c},function(a){if(!a.successful)return;var b=[].concat(a.subscription);this.info('Unsubscription acknowledged for ? from ?',this._0,b)},this)},this)},publish:function(a,b){this._p(a);this.connect(function(){this.info('Client ? queueing published message to ?: ?',this._0,a,b);this._e({channel:a,data:b,clientId:this._0})},this)},receiveMessage:function(c){this.pipeThroughExtensions('incoming',c,function(a){if(!a)return;if(a.advice)this._D(a.advice);var b=this._n[a.id];if(b){delete this._n[a.id];b[0].call(b[1],a)}this._E(a)},this)},_D:function(a){Faye.extend(this._8,a);if(this._8.reconnect===this.HANDSHAKE&&this._1!==this.DISCONNECTED){this._1=this.UNCONNECTED;this._0=null;this._y()}},_E:function(a){if(!a.channel||!a.data)return;this.info('Client ? calling listeners for ? with ?',this._0,a.channel,a.data);this._b.distributeMessage(a)},_F:function(){if(!this._o)return;this._o=null;this.info('Closed connection for ?',this._0)},_y:function(){this._F();var a=this;setTimeout(function(){a.connect()},this._8.interval)},_e:function(b,c,d){b.id=this._C.generate();if(c)this._n[b.id]=[c,d];this.pipeThroughExtensions('outgoing',b,function(a){if(!a)return;if(a.channel===Faye.Channel.HANDSHAKE)return this._f.send(a,this._8.timeout/1000);this._h.push(a);if(a.channel===Faye.Channel.CONNECT)this._q=a;this.addTimeout('publish',this.MAX_DELAY,this._G,this)},this)},_G:function(){this.removeTimeout('publish');if(this._h.length>1&&this._q)this._q.advice={timeout:0};this._q=null;this._f.send(this._h,this._8.timeout/1000);this._h=[]},_p:function(a){if(!Faye.Channel.isValid(a))throw'"'+a+'" is not a valid channel name';if(!Faye.Channel.isSubscribable(a))throw'Clients may not subscribe to channel "'+a+'"';}});Faye.extend(Faye.Client.prototype,Faye.Deferrable);Faye.extend(Faye.Client.prototype,Faye.Timeouts);Faye.extend(Faye.Client.prototype,Faye.Logging);Faye.extend(Faye.Client.prototype,Faye.Extensible);Faye.Transport=Faye.extend(Faye.Class({initialize:function(a,b){this.debug('Created new ? transport for ?',this.connectionType,b);this._c=a;this._3=b},send:function(a,b){a=[].concat(a);this.debug('Client ? sending message to ?: ?',this._c._0,this._3,a);return this.request(a,b)},receive:function(a){this.debug('Client ? received from ?: ?',this._c._0,this._3,a);Faye.each(a,this._c.receiveMessage,this._c)},retry:function(a,b){var c=this;return function(){setTimeout(function(){c.request(a,2*b)},1000*b)}}}),{get:function(d,f){var g=d._3;if(f===undefined)f=this.supportedConnectionTypes();var h=null;Faye.each(this._r,function(a){var b=a[0],c=a[1];if(Faye.indexOf(f,b)<0)return;if(h)return;if(c.isUsable(g))h=c});if(!h)throw'Could not find a usable connection type for '+g;return new h(d,g)},register:function(a,b){this._r.push([a,b]);b.prototype.connectionType=a},_r:[],supportedConnectionTypes:function(){return Faye.map(this._r,function(a){return a[0]})}});Faye.extend(Faye.Transport.prototype,Faye.Logging);Faye.Event={_i:[],on:function(a,b,c,d){var f=function(){c.call(d)};if(a.addEventListener)a.addEventListener(b,f,false);else a.attachEvent('on'+b,f);this._i.push({_j:a,_s:b,_m:c,_d:d,_z:f})},detach:function(a,b,c,d){var f=this._i.length,g;while(f--){g=this._i[f];if((a&&a!==g._j)||(b&&b!==g._s)||(c&&c!==g._m)||(d&&d!==g._d))continue;if(g._j.removeEventListener)g._j.removeEventListener(g._s,g._z,false);else g._j.detachEvent('on'+g._s,g._z);this._i.splice(f,1);g=null}}};Faye.Event.on(Faye.ENV,'unload',Faye.Event.detach,Faye.Event);Faye.URI=Faye.extend(Faye.Class({queryString:function(){var c=[],d;Faye.each(this.params,function(a,b){c.push(encodeURIComponent(a)+'='+encodeURIComponent(b))});return c.join('&')},isLocal:function(){var a=Faye.URI.parse(Faye.ENV.location.href);var b=(a.hostname!==this.hostname)||(a.port!==this.port)||(a.protocol!==this.protocol);return!b},toURL:function(){var a=this.queryString();return this.protocol+this.hostname+':'+this.port+this.pathname+(a?'?'+a:'')}}),{parse:function(d,f){if(typeof d!=='string')return d;var g=new this();var h=function(b,c){d=d.replace(c,function(a){if(a)g[b]=a;return''})};h('protocol',/^https?\:\/+/);h('hostname',/^[^\/\:]+/);h('port',/^:[0-9]+/);Faye.extend(g,{protocol:'http://',hostname:Faye.ENV.location.hostname,port:Faye.ENV.location.port},false);if(!g.port)g.port=(g.protocol==='https://')?'443':'80';g.port=g.port.replace(/\D/g,'');var j=d.split('?'),i=j.shift(),l=j.join('?'),n=l?l.split('&'):[],o=n.length,k={};while(o--){j=n[o].split('=');k[decodeURIComponent(j[0]||'')]=decodeURIComponent(j[1]||'')}if(typeof f==='object')Faye.extend(k,f);g.pathname=i;g.params=k;return g}});Faye.XHR={request:function(a,b,c,d,f){var g=new this.Request(a,b,c,d,f);g.send();return g},getXhrObject:function(){return Faye.ENV.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest()},Request:Faye.Class({initialize:function(a,b,c,d,f){this._k=a.toUpperCase();this._3=Faye.URI.parse(b,c);this._H=(typeof c==='string')?c:null;this._9=(typeof d==='function')?{success:d}:d;this._d=f||null;this._5=null},send:function(){if(this._t)return;var a=this._3.pathname,b=this._3.queryString();if(this._k==='GET')a+='?'+b;var c=(this._k==='POST')?(this._H||b):'';this._t=true;this._5=Faye.XHR.getXhrObject();this._5.open(this._k,a,true);if(this._k==='POST')this._5.setRequestHeader('Content-Type','application/json');var d=this,f=function(){if(d._5.readyState!==4)return;if(g){clearInterval(g);g=null}Faye.Event.detach(Faye.ENV,'beforeunload',d.abort,d);d._t=false;d._I();d=null};var g=setInterval(f,10);Faye.Event.on(Faye.ENV,'beforeunload',this.abort,this);this._5.send(c)},abort:function(){this._5.abort()},_I:function(){var a=this._9;if(!a)return;return this.success()?a.success&&a.success.call(this._d,this):a.failure&&a.failure.call(this._d,this)},waiting:function(){return!!this._t},complete:function(){return this._5&&!this.waiting()},success:function(){if(!this.complete())return false;var a=this._5.status;return(a>=200&&a<300)||a===304||a===1223},failure:function(){if(!this.complete())return false;return!this.success()},text:function(){if(!this.complete())return null;return this._5.responseText},status:function(){if(!this.complete())return null;return this._5.status}})};if(!this.JSON){JSON={}}(function(){function l(a){return a<10?'0'+a:a}if(typeof Date.prototype.toJSON!=='function'){Date.prototype.toJSON=function(a){return this.getUTCFullYear()+'-'+l(this.getUTCMonth()+1)+'-'+l(this.getUTCDate())+'T'+l(this.getUTCHours())+':'+l(this.getUTCMinutes())+':'+l(this.getUTCSeconds())+'Z'};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(a){return this.valueOf()}}var n=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,o=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,k,p,s={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'},m;function r(c){o.lastIndex=0;return o.test(c)?'"'+c.replace(o,function(a){var b=s[a];return typeof b==='string'?b:'\\u'+('0000'+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+c+'"'}function q(a,b){var c,d,f,g,h=k,j,i=b[a];if(i&&typeof i==='object'&&typeof i.toJSON==='function'){i=i.toJSON(a)}if(typeof m==='function'){i=m.call(b,a,i)}switch(typeof i){case'string':return r(i);case'number':return isFinite(i)?String(i):'null';case'boolean':case'null':return String(i);case'object':if(!i){return'null'}k+=p;j=[];if(Object.prototype.toString.apply(i)==='[object Array]'){g=i.length;for(c=0;c<g;c+=1){j[c]=q(c,i)||'null'}f=j.length===0?'[]':k?'[\n'+k+j.join(',\n'+k)+'\n'+h+']':'['+j.join(',')+']';k=h;return f}if(m&&typeof m==='object'){g=m.length;for(c=0;c<g;c+=1){d=m[c];if(typeof d==='string'){f=q(d,i);if(f){j.push(r(d)+(k?': ':':')+f)}}}}else{for(d in i){if(Object.hasOwnProperty.call(i,d)){f=q(d,i);if(f){j.push(r(d)+(k?': ':':')+f)}}}}f=j.length===0?'{}':k?'{\n'+k+j.join(',\n'+k)+'\n'+h+'}':'{'+j.join(',')+'}';k=h;return f}}Faye.stringify=function(a,b,c){var d;k='';p='';if(typeof c==='number'){for(d=0;d<c;d+=1){p+=' '}}else if(typeof c==='string'){p=c}m=b;if(b&&typeof b!=='function'&&(typeof b!=='object'||typeof b.length!=='number')){throw new Error('JSON.stringify');}return q('',{'':a})};if(typeof JSON.stringify!=='function'){JSON.stringify=Faye.stringify}if(typeof JSON.parse!=='function'){JSON.parse=function(g,h){var j;function i(a,b){var c,d,f=a[b];if(f&&typeof f==='object'){for(c in f){if(Object.hasOwnProperty.call(f,c)){d=i(f,c);if(d!==undefined){f[c]=d}else{delete f[c]}}}}return h.call(a,b,f)}n.lastIndex=0;if(n.test(g)){g=g.replace(n,function(a){return'\\u'+('0000'+a.charCodeAt(0).toString(16)).slice(-4)})}if(/^[\],:{}\s]*$/.test(g.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,'@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,']').replace(/(?:^|:|,)(?:\s*\[)+/g,''))){j=eval('('+g+')');return typeof h==='function'?i({'':j},''):j}throw new SyntaxError('JSON.parse');}}}());Faye.WebSocketTransport=Faye.Class(Faye.Transport,{UNCONNECTED:1,CONNECTING:2,CONNECTED:3,request:function(b,c){this._l=this._l||{};Faye.each(b,function(a){this._l[a.id]=a},this);this.withSocket(function(a){a.send(Faye.toJSON(b))})},withSocket:function(d,f){this.callback(d,f);this._1=this._1||this.UNCONNECTED;if(this._1!==this.UNCONNECTED)return;this._1=this.CONNECTING;var g=Faye.URI.parse(this._3).toURL().replace(/^https?/ig,'ws');this._g=new WebSocket(g);var h=this;this._g.onopen=function(){h._1=h.CONNECTED;h.setDeferredStatus('succeeded',h._g)};this._g.onmessage=function(b){var c=[].concat(JSON.parse(b.data));Faye.each(c,function(a){delete h._l[a.id]});h.receive(c)};this._g.onclose=function(){h.setDeferredStatus('deferred');h._1=h.UNCONNECTED;h._g=null;h.resend()}},resend:function(){var c=Faye.map(this._l,function(a,b){return b});this.request(c)}});Faye.extend(Faye.WebSocketTransport.prototype,Faye.Deferrable);Faye.WebSocketTransport.isUsable=function(a){return!!Faye.ENV.WebSocket};Faye.Transport.register('websocket',Faye.WebSocketTransport);Faye.XHRTransport=Faye.Class(Faye.Transport,{request:function(b,c){var d=this.retry(b,c);Faye.XHR.request('post',this._3,Faye.toJSON(b),{success:function(a){try{this.receive(JSON.parse(a.text()))}catch(e){d()}},failure:d},this)}});Faye.XHRTransport.isUsable=function(a){return Faye.URI.parse(a).isLocal()};Faye.Transport.register('long-polling',Faye.XHRTransport);Faye.JSONPTransport=Faye.extend(Faye.Class(Faye.Transport,{request:function(b,c){var d={message:Faye.toJSON(b)},f=document.getElementsByTagName('head')[0],g=document.createElement('script'),h=Faye.JSONPTransport.getCallbackName(),j=Faye.URI.parse(this._3,d),i=this;var l=function(){if(!g.parentNode)return false;g.parentNode.removeChild(g);return true};Faye.ENV[h]=function(a){Faye.ENV[h]=undefined;try{delete Faye.ENV[h]}catch(e){}if(!l())return;i.receive(a)};setTimeout(function(){if(!Faye.ENV[h])return;l();i.request(b,2*c)},1000*c);j.params.jsonp=h;g.type='text/javascript';g.src=j.toURL();f.appendChild(g)}}),{_A:0,getCallbackName:function(){this._A+=1;return'__jsonp'+this._A+'__'}});Faye.JSONPTransport.isUsable=function(a){return true};Faye.Transport.register('callback-polling',Faye.JSONPTransport);
|
data/lib/faye.rb
CHANGED
@@ -1,32 +1,44 @@
|
|
1
1
|
require 'forwardable'
|
2
|
-
require 'observer'
|
3
2
|
require 'set'
|
4
3
|
require 'rubygems'
|
5
4
|
require 'eventmachine'
|
6
5
|
require 'json'
|
7
6
|
|
8
7
|
module Faye
|
9
|
-
VERSION = '0.
|
8
|
+
VERSION = '0.5.0'
|
10
9
|
|
11
10
|
ROOT = File.expand_path(File.dirname(__FILE__))
|
12
11
|
|
13
12
|
BAYEUX_VERSION = '1.0'
|
14
13
|
ID_LENGTH = 128
|
15
14
|
JSONP_CALLBACK = 'jsonpcallback'
|
16
|
-
CONNECTION_TYPES = %w[long-polling callback-polling]
|
15
|
+
CONNECTION_TYPES = %w[long-polling callback-polling websocket]
|
17
16
|
|
18
|
-
%w[
|
19
|
-
|
17
|
+
MANDATORY_CONNECTION_TYPES = %w[long-polling callback-polling in-process]
|
18
|
+
|
19
|
+
%w[ mixins/publisher
|
20
|
+
mixins/timeouts
|
21
|
+
mixins/logging
|
22
|
+
util/namespace
|
23
|
+
protocol/grammar
|
24
|
+
protocol/extensible
|
25
|
+
protocol/channel
|
26
|
+
protocol/subscription
|
27
|
+
protocol/client
|
28
|
+
protocol/server
|
29
|
+
protocol/connection
|
30
|
+
network/transport
|
31
|
+
error
|
32
|
+
|
20
33
|
].each do |lib|
|
21
34
|
require File.join(ROOT, 'faye', lib)
|
22
35
|
end
|
23
36
|
|
24
|
-
autoload :RackAdapter, File.join(ROOT, 'faye', 'rack_adapter')
|
37
|
+
autoload :RackAdapter, File.join(ROOT, 'faye', 'adapters', 'rack_adapter')
|
38
|
+
autoload :WebSocket, File.join(ROOT, 'faye', 'util', 'web_socket')
|
25
39
|
|
26
40
|
def self.random(bitlength = ID_LENGTH)
|
27
|
-
|
28
|
-
strlen = bitlength / 4
|
29
|
-
("%0#{strlen}s" % rand(field).to_s(16)).gsub(' ', '0')
|
41
|
+
rand(2 ** bitlength).to_s(36)
|
30
42
|
end
|
31
43
|
|
32
44
|
def self.to_json(value)
|
@@ -36,5 +48,10 @@ module Faye
|
|
36
48
|
else value.to_s
|
37
49
|
end
|
38
50
|
end
|
51
|
+
|
52
|
+
def self.ensure_reactor_running!
|
53
|
+
Thread.new { EM.run } unless EM.reactor_running?
|
54
|
+
while not EM.reactor_running?; end
|
55
|
+
end
|
39
56
|
end
|
40
57
|
|