faye 0.2.2 → 0.3.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 CHANGED
@@ -1,3 +1,14 @@
1
+ === 0.3.0 / 2010-03-01
2
+
3
+ * Add server-side clients for Node.js and Ruby environments
4
+ * Clients support both HTTP and in-process transports
5
+ * Fix ID generation in JavaScript version to 128-bit IDs
6
+ * Fix bug in interpretation of ** channel wildcard
7
+ * Users don't have to call #connect() on clients any more
8
+ * Fix timeout race conditions that were killing active connections
9
+ * Support new Node APIs from 0.1.29.
10
+
11
+
1
12
  === 0.2.2 / 2010-02-10
2
13
 
3
14
  * Kick out requests with malformed JSON as 400s
data/Manifest.txt CHANGED
@@ -14,18 +14,25 @@ examples/shared/public/robots.txt
14
14
  examples/shared/public/soapbox.js
15
15
  examples/shared/public/style.css
16
16
  examples/node/app.js
17
+ examples/node/client.js
17
18
  examples/node/faye.js
18
19
  examples/node/faye-client-min.js
19
20
  examples/rack/app.rb
21
+ examples/rack/client.rb
20
22
  examples/rack/config.ru
21
23
  lib/faye-client-min.js
22
24
  lib/faye.rb
23
25
  lib/faye/channel.rb
26
+ lib/faye/client.rb
24
27
  lib/faye/connection.rb
25
28
  lib/faye/error.rb
26
29
  lib/faye/grammar.rb
30
+ lib/faye/namespace.rb
27
31
  lib/faye/rack_adapter.rb
28
32
  lib/faye/server.rb
33
+ lib/faye/transport.rb
34
+ test/scenario.js
29
35
  test/test_channel.rb
36
+ test/test_clients.js
30
37
  test/test_grammar.rb
31
38
  test/test_server.rb
data/README.txt CHANGED
@@ -2,29 +2,45 @@
2
2
 
3
3
  * http://github.com/jcoglan/faye
4
4
 
5
- Faye provides dirt-simple Comet messaging based on the Bayeux protocol.
6
- It ships with a self-contained client for use in the browser and two
7
- message-routing backends; one for Node.js and one for Rack.
5
+ Faye is a set of tools for dirt-simple publish-subscribe messaging
6
+ between web clients. It ships with easy-to-use message routing servers
7
+ for Node.js and Rack applications, and clients that can be used on
8
+ the server and in the browser.
8
9
 
9
10
 
10
11
  == Introduction
11
12
 
12
- Comet is an umbrella term for a collection of techniques that let web
13
- servers push data to clients, allowing for low-latency data transfer.
14
- Bayeux is a protocol designed to support publish/subscribe-style messaging
15
- between web clients and servers. Clients can subscribe to named channels
16
- and publish messages, each message being distributed to all clients
17
- subscribed to its target channel. See http://svn.cometd.com/trunk/bayeux/bayeux.html
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
+ });
18
31
 
19
32
  Faye's messaging backend was originally developed in Ruby for a toy
20
33
  project of mine, but was not written to scale across multiple processes
21
- as required for scaling a Ruby server. It has since been ported to Node.js
22
- which should handle the required connection load more gracefully.
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-driver Ruby web server.
23
38
 
24
39
  The two backends are architecturally identical, using evented messaging
25
- throughout and maintaining subscription data in memory. Node.js allows
26
- for far more concurrent connections per process than a typical Ruby server,
27
- and will get you more mileage from a single process.
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 that a typical
43
+ threaded server.
28
44
 
29
45
 
30
46
  == Installation
@@ -39,17 +55,16 @@ The Rack server is distributed as a Ruby gem:
39
55
 
40
56
  == Using the client
41
57
 
42
- Both backends allow you to specify a 'mount point' that the Comet server
43
- accepts requests on. Say you set this to <tt>/comet</tt>, the client script
44
- will be available from <tt>/comet.js</tt> and should connect to <tt>/comet</tt>.
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>.
45
61
 
46
62
  You should set up the client as follows:
47
63
 
48
- <script type="text/javascript" src="/comet.js"></script>
64
+ <script type="text/javascript" src="/faye.js"></script>
49
65
 
50
66
  <script type="text/javascript">
51
- CometClient = new Faye.Client('/comet');
52
- CometClient.connect();
67
+ fayeClient = new Faye.Client('/faye');
53
68
  </script>
54
69
 
55
70
  Take care only to have one instance of the client per page; since each one
@@ -58,11 +73,11 @@ and block all other Ajax calls if you use more than one client.
58
73
 
59
74
  This client object can be used to publish and subscribe to named channels:
60
75
 
61
- CometClient.subscribe('/path/to/channel', function(message) {
76
+ fayeClient.subscribe('/path/to/channel', function(message) {
62
77
  // process received message object
63
78
  });
64
79
 
65
- CometClient.publish('/some/other/channel', {foo: 'bar'});
80
+ fayeClient.publish('/some/other/channel', {foo: 'bar'});
66
81
 
67
82
  You can publish arbitrary JavaScript objects to a channel, and the object will
68
83
  be transmitted as the +message+ parameter to any subscribers to that channel.
@@ -75,7 +90,7 @@ supports callback polling (see below under 'Transports'). Just pass in the
75
90
  full path to the endpoint including the domain. Faye figures out whether the
76
91
  server is on the same domain and uses an appropriate transport.
77
92
 
78
- CometClient = new Faye.Client('http://example.com/comet');
93
+ fayeClient = new Faye.Client('http://example.com/faye');
79
94
 
80
95
 
81
96
  === Transports
@@ -87,7 +102,7 @@ low-latency connections with the server, the two required types being
87
102
  <tt>long-polling</tt> is where the client makes an <tt>XMLHttpRequest</tt>
88
103
  to the server, and the server waits until it has new messages for that client
89
104
  before it returns a response. Faye's client and server backends all support
90
- this transport. Since it uses XHR, the Comet endpoint must be on the same
105
+ this transport. Since it uses XHR, the server endpoint must be on the same
91
106
  domain as the client page.
92
107
 
93
108
  <tt>callback-polling</tt> involves the client using JSON-P to make the request.
@@ -96,6 +111,10 @@ client then executes. This transport does not require the client and server
96
111
  to be on the same domain. Faye's client supports this transport, as does the
97
112
  Node.js backend. The Rack backend supports it if running under Thin.
98
113
 
114
+ Faye also supports an <tt>in-process</tt> transport, which is used when a
115
+ server-side client has direct in-memory access to the server without going
116
+ over HTTP.
117
+
99
118
 
100
119
  == Using the backend
101
120
 
@@ -119,8 +138,8 @@ responses and is a better choice for long-running concurrent connections.
119
138
 
120
139
  Both backends support the following initialization options:
121
140
 
122
- * +mount+ - the path at which the Comet service is accessible. e.g. if
123
- set to <tt>/faye</tt>, the Comet endpoint will be at <tt>http://yoursite.com/faye</tt>
141
+ * +mount+ - the path at which the Faye service is accessible. e.g. if
142
+ set to <tt>/faye</tt>, the Faye endpoint will be at <tt>http://yoursite.com/faye</tt>
124
143
  and the client script at <tt>http://yoursite.com/faye.js</tt>.
125
144
  * +timeout+ - the maximum time (seconds) to hold a long-running request
126
145
  open before returning a response. This must be smaller than the timeout
@@ -132,7 +151,7 @@ Usage examples and a demo app are in the +examples+ directory.
132
151
 
133
152
  === Node.js backend
134
153
 
135
- Here's a very simple Node web server that offloads requests to the Comet
154
+ Here's a very simple Node web server that offloads requests to the messaging
136
155
  service to Faye and deals with all other requests itself. The Faye object
137
156
  returns +true+ or +false+ to indicate whether it handled the request.
138
157
  You'll need <tt>faye.js</tt> and <tt>faye-client-min.js</tt> in the same
@@ -141,18 +160,36 @@ directory.
141
160
  var http = require('http')
142
161
  faye = require('./faye');
143
162
 
144
- var comet = new faye.NodeAdapter({mount: '/comet', timeout: 45});
163
+ var server = new faye.NodeAdapter({mount: '/faye', timeout: 45});
145
164
 
146
165
  http.createServer(function(request, response) {
147
- if (comet.call(request, response)) return;
166
+ if (server.call(request, response)) return;
148
167
 
149
168
  response.sendHeader(200, {'Content-Type': 'text/plain'});
150
- response.sendBody('Hello, non-Comet request!');
151
- response.finish();
169
+ response.write('Hello, non-Faye request!');
170
+ response.close();
152
171
 
153
172
  }).listen(9292);
154
173
 
155
174
 
175
+ === Node.js client
176
+
177
+ Faye's JavaScript client can be used server-side under Node. There are
178
+ two ways you can set a client up: either connect to as remote server
179
+ over HTTP or connect directly to the <tt>NodeAdapter</tt>. Either way,
180
+ the API for the client is exactly as it is in the browser.
181
+
182
+ // Remote client
183
+ client = new Faye.Client('http://example.com/faye');
184
+
185
+ // Local client
186
+ server = new Faye.NodeAdapter(options);
187
+ client = server.getClient();
188
+
189
+ Note <tt>getClient()</tt> returns the same client object every time you
190
+ call it, so any subscriptions you make with it are retained.
191
+
192
+
156
193
  === Rack backend
157
194
 
158
195
  Faye can be installed as middleware in front of any Rack application. The
@@ -169,21 +206,66 @@ Here's a <tt>config.ru</tt> for running it with Sinatra:
169
206
  require 'sinatra'
170
207
  require 'path/to/sinatra/app'
171
208
 
172
- use Faye::RackAdapter, :mount => '/comet',
209
+ use Faye::RackAdapter, :mount => '/faye',
173
210
  :timeout => 25
174
211
 
175
212
  run Sinatra::Application
176
213
 
177
- This functions much the same as the Node.js example; Faye catches Comet
214
+ This functions much the same as the Node.js example; Faye catches messaging
178
215
  requests and deals with them, letting all other requests fall down the
179
216
  stack of Rack middlewares.
180
217
 
181
218
 
219
+ === Rack client
220
+
221
+ Again mirroring the Node tools, Faye has a client-side Ruby client that
222
+ cna be used under EventMachine. Setup is identical:
223
+
224
+ # Remote client
225
+ client = Faye::Client.new('http://example.com/faye')
226
+
227
+ # Local client
228
+ server = Faye::RackAdapter.new(options)
229
+ client = server.get_client
230
+
231
+ Both clients must be run inside EventMachine, and can publish and
232
+ subscribe just like the JavaScript client:
233
+
234
+ EM.run {
235
+ client.subscribe('/from/*') do |message|
236
+ # do something with message hash
237
+ end
238
+
239
+ client.publish('/from/jcoglan', 'hello' => 'world')
240
+ }
241
+
242
+
243
+ == Development
244
+
245
+ If you want to hack on Faye, you'll need Node.js, Ruby and the following
246
+ gems installed:
247
+
248
+ sudo gem install eventmachine em-http-request rack thin json jake
249
+
250
+ DO NOT edit any JavaScript files outside the +javascript+ and +test+
251
+ directories. They are generated using Jake, which you should run after
252
+ editing the JavaScript source.
253
+
254
+ jake -f
255
+
256
+ Ordinarily I would keep generated files out of the repo but I'm keeping
257
+ some in here as it's currently the only place to download them.
258
+
259
+ If you want to submit patches, they're far more likely to make it in
260
+ if you update both the Ruby and JavaScript versions with equivalent
261
+ changes where appropriate.
262
+
263
+
182
264
  == To-do
183
265
 
266
+ * Investigate WebSockets as a possible message transport
184
267
  * Provide support for user-defined <tt>/service/*</tt> channels
185
268
  * Allow server to scale to multiple nodes
186
- * Provide a server-side client
187
269
 
188
270
 
189
271
  == License
data/Rakefile CHANGED
@@ -7,7 +7,12 @@ require './lib/faye.rb'
7
7
  Hoe.spec('faye') do |p|
8
8
  # p.rubyforge_name = 'fayex' # if different than lowercase project name
9
9
  p.developer('James Coglan', 'jcoglan@googlemail.com')
10
- p.extra_deps = %w[eventmachine rack json]
10
+ p.extra_deps = [
11
+ ['eventmachine', '>= 0.12'],
12
+ ['em-http-request', '>= 0.2'],
13
+ ['rack', '>= 1.0'],
14
+ ['json', '>= 1.0']
15
+ ]
11
16
  end
12
17
 
13
18
  # vim: syntax=Ruby
@@ -1 +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)&&a[d]!==b[d]){if(!a.hasOwnProperty(d)||c!==false)a[d]=b[d]}}return a};Faye.extend(Faye,{BAYEUX_VERSION:'1.0',VERSION:'0.2.2',JSONP_CALLBACK:'jsonpcallback',ID_LENGTH:128,CONNECTION_TYPES:["long-polling","callback-polling"],ENV:this,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])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*)$/},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])}}},size:function(a){var b=0;this.each(a,function(){b+=1});return b},random:function(){var a=Math.pow(2,this.ID_LENGTH);return(Math.random()*a).toString(16).replace(/0*$/,'')},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}}});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.Observable={on:function(a,b,c){this._1=this._1||{};var d=this._1[a]=this._1[a]||[];d.push([b,c])},stopObserving:function(a,b,c){if(!this._1||!this._1[a])return;if(!b){delete this._1[a];return}var d=this._1[a],f=d.length;while(f--){if(b&&d[f][0]!==b)continue;if(c&&d[f][1]!==c)continue;d.splice(f,1)}},fire:function(){var b=Array.prototype.slice.call(arguments),c=b.shift();if(!this._1||!this._1[c])return;Faye.each(this._1[c],function(a){a[0].apply(a[1],b.slice())})}};Faye.Channel=Faye.Class({initialize:function(a){this.__id=this._A=a},push:function(a){this.fire('message',a)}});Faye.extend(Faye.Channel.prototype,Faye.Observable);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._3=a;this._5={}},eachChild:function(c,d){Faye.each(this._5,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._3!==undefined)d.call(f,c,this._3)},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._3:null},set:function(a,b){var c=this.traverse(a,true);if(c)c._3=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._5[a[0]];if(!c&&!b)return null;if(!c)c=this._5[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._3===undefined)?[]:[this._3];var g=[];if(Faye.enumEqual(f,['*'])){Faye.each(this._5,function(a,b){if(b._3!==undefined)g.push(b._3)});return g}if(Faye.enumEqual(f,['**'])){g=this.map(function(a,b){return b});g.pop();return g}Faye.each(this._5,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._5['**'])g.push(this._5['**']._3);return g}})});Faye.Event={_a:[],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._a.push({_b:a,_h:b,_s:c,_c:d,_m:f})},detach:function(a,b,c,d){var f=this._a.length,g;while(f--){g=this._a[f];if((a&&a!==g._b)||(b&&b!==g._h)||(c&&c!==g._s)||(d&&d!==g._c))continue;if(g._b.removeEventListener)g._b.removeEventListener(g._h,g._m,false);else g._b.detachEvent('on'+g._h,g._m);this._a.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(){return this.protocol+this.hostname+':'+this.port+this.pathname+'?'+this.queryString()}}),{parse:function(d,f){if(typeof d!=='string')return d;var g=new this();var k=function(b,c){d=d.replace(c,function(a){if(a)g[b]=a;return''})};k('protocol',/^https?\:\/+/);k('hostname',/^[^\/\:]+/);k('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 i=d.split('?'),h=i.shift(),l=i.join('?'),n=l?l.split('&'):[],o=n.length,j={};while(o--){i=n[o].split('=');j[decodeURIComponent(i[0]||'')]=decodeURIComponent(i[1]||'')}Faye.extend(j,f);g.pathname=h;g.params=j;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._d=a.toUpperCase();this._6=Faye.URI.parse(b,c);this._t=(typeof d==='function')?{success:d}:d;this._c=f||null;this._2=null},send:function(){if(this._i)return;var a=this._6.pathname,b=this._6.queryString();if(this._d==='GET')a+='?'+b;var c=this._d==='POST'?b:'';this._i=true;this._2=Faye.XHR.getXhrObject();this._2.open(this._d,a,true);if(this._d==='POST')this._2.setRequestHeader('Content-Type','application/x-www-form-urlencoded');var d=this,f=function(){if(d._2.readyState!==4)return;if(g){clearInterval(g);g=null}Faye.Event.detach(Faye.ENV,'beforeunload',d.abort,d);d._i=false;d._u();d=null};var g=setInterval(f,10);Faye.Event.on(Faye.ENV,'beforeunload',this.abort,this);this._2.send(c)},abort:function(){this._2.abort()},_u:function(){var a=this._t;if(!a)return;return this.success()?a.success&&a.success.call(this._c,this):a.failure&&a.failure.call(this._c,this)},waiting:function(){return!!this._i},complete:function(){return this._2&&!this.waiting()},success:function(){if(!this.complete())return false;var a=this._2.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._2.responseText},status:function(){if(!this.complete())return null;return this._2.status}})};Faye.Transport=Faye.extend(Faye.Class({initialize:function(a,b){this._n=a;this._6=b},send:function(c,d,f){c={message:JSON.stringify(c)};return this.request(c,function(b){if(!d)return;Faye.each([].concat(b),function(a){d.call(f,a);if(a.advice)this._n._v(a.advice);if(a.data&&a.channel)this._n._w(a)},this)},this)}}),{get:function(a,b){var c=a._6;if(b===undefined)b=this.supportedConnectionTypes();var d=Faye.URI.parse(c).isLocal()?['long-polling','callback-polling']:['callback-polling'];var f=Faye.commonElement(d,b);if(!f)throw'Could not find a usable connection type for '+c;var g=this._j[f];return new g(a,c)},register:function(a,b){this._j[a]=b;b.prototype.connectionType=a},_j:{},supportedConnectionTypes:function(){var c=[],d;Faye.each(this._j,function(a,b){c.push(a)});return c}});Faye.XHRTransport=Faye.Class(Faye.Transport,{request:function(b,c,d){Faye.XHR.request('post',this._6,b,function(a){if(c)c.call(d,JSON.parse(a.text()))})}});Faye.Transport.register('long-polling',Faye.XHRTransport);Faye.JSONPTransport=Faye.extend(Faye.Class(Faye.Transport,{request:function(b,c,d){var f=document.getElementsByTagName('head')[0],g=document.createElement('script'),k=Faye.JSONPTransport.getCallbackName(),i=Faye.URI.parse(this._6,b);Faye.ENV[k]=function(a){Faye.ENV[k]=undefined;try{delete Faye.ENV[k]}catch(e){}f.removeChild(g);if(c)c.call(d,a)};i.params.jsonp=k;g.type='text/javascript';g.src=i.toURL();f.appendChild(g)}}),{_o:0,getCallbackName:function(){this._o+=1;return'__jsonp'+this._o+'__'}});Faye.Transport.register('callback-polling',Faye.JSONPTransport);Faye.Client=Faye.Class({_e:{},_x:{},_8:{},_y:{},_p:'handshake',_z:'retry',_q:'none',DEFAULT_ENDPOINT:'/bayeux',MAX_DELAY:0.1,INTERVAL:1000.0,initialize:function(a){this._6=a||this.DEFAULT_ENDPOINT;this._4=Faye.Transport.get(this);this._0=this._e;this._k=[];this._f=new Faye.Channel.Tree();this._7={reconnect:this._z,interval:this.INTERVAL};Faye.Event.on(Faye.ENV,'beforeunload',this.disconnect,this)},handshake:function(b,c){if(this._7.reconnect===this._q)return;if(this._0!==this._e)return;this._0=this._x;var d=this,f=this.generateId();this._4.send({channel:Faye.Channel.HANDSHAKE,version:Faye.BAYEUX_VERSION,supportedConnectionTypes:Faye.Transport.supportedConnectionTypes(),id:f},function(a){if(a.id!==f)return;if(!a.successful){setTimeout(function(){d.handshake(b,c)},this._7.interval);return this._0=this._e}this._0=this._8;this._9=a.clientId;this._4=Faye.Transport.get(this,a.supportedConnectionTypes);if(b)b.call(c)},this)},connect:function(b,c){if(this._7.reconnect===this._q)return;if(this._7.reconnect===this._p||this._0===this._e)return this.handshake(function(){this.connect(b,c)},this);if(this._0!==this._8)return;if(this._g)return;this._g=this.generateId();var d=this;this._4.send({channel:Faye.Channel.CONNECT,clientId:this._9,connectionType:this._4.connectionType,id:this._g},function(a){if(a.id!==this._g)return;delete this._g;if(a.successful)this.connect();else setTimeout(function(){d.connect()},this._7.interval)},this);if(b)b.call(c)},disconnect:function(){if(this._0!==this._8)return;this._0=this._y;this._4.send({channel:Faye.Channel.DISCONNECT,clientId:this._9});this._f=new Faye.Channel.Tree()},subscribe:function(c,d,f){if(this._0!==this._8)return;c=[].concat(c);this._l(c);var g=this.generateId();this._4.send({channel:Faye.Channel.SUBSCRIBE,clientId:this._9,subscription:c,id:g},function(b){if(b.id!==g)return;if(!b.successful)return;c=[].concat(b.subscription);Faye.each(c,function(a){this._f.set(a,[d,f])},this)},this)},unsubscribe:function(c,d,f){if(this._0!==this._8)return;c=[].concat(c);this._l(c);var g=this.generateId();this._4.send({channel:Faye.Channel.UNSUBSCRIBE,clientId:this._9,subscription:c,id:g},function(b){if(b.id!==g)return;if(!b.successful)return;c=[].concat(b.subscription);Faye.each(c,function(a){this._f.set(a,null)},this)},this)},publish:function(a,b){if(this._0!==this._8)return;this._l([a]);this.enqueue({channel:a,data:b,clientId:this._9});if(this._r)return;var c=this;this._r=setTimeout(function(){delete c._r;c.flush()},this.MAX_DELAY*1000)},generateId:function(a){a=a||32;return Math.floor(Math.pow(2,a)*Math.random()).toString(16)},enqueue:function(a){this._k.push(a)},flush:function(){this._4.send(this._k);this._k=[]},_l:function(b){Faye.each(b,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+'"';})},_v:function(a){Faye.extend(this._7,a);if(this._7.reconnect===this._p)this._9=null},_w:function(b){var c=this._f.glob(b.channel);Faye.each(c,function(a){if(!a)return;a[0].call(a[1],b.data)})}});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,j,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,k=j,i,h=b[a];if(h&&typeof h==='object'&&typeof h.toJSON==='function'){h=h.toJSON(a)}if(typeof m==='function'){h=m.call(b,a,h)}switch(typeof h){case'string':return r(h);case'number':return isFinite(h)?String(h):'null';case'boolean':case'null':return String(h);case'object':if(!h){return'null'}j+=p;i=[];if(Object.prototype.toString.apply(h)==='[object Array]'){g=h.length;for(c=0;c<g;c+=1){i[c]=q(c,h)||'null'}f=i.length===0?'[]':j?'[\n'+j+i.join(',\n'+j)+'\n'+k+']':'['+i.join(',')+']';j=k;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,h);if(f){i.push(r(d)+(j?': ':':')+f)}}}}else{for(d in h){if(Object.hasOwnProperty.call(h,d)){f=q(d,h);if(f){i.push(r(d)+(j?': ':':')+f)}}}}f=i.length===0?'{}':j?'{\n'+j+i.join(',\n'+j)+'\n'+k+'}':'{'+i.join(',')+'}';j=k;return f}}if(typeof JSON.stringify!=='function'){JSON.stringify=function(a,b,c){var d;j='';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.parse!=='function'){JSON.parse=function(g,k){var i;function h(a,b){var c,d,f=a[b];if(f&&typeof f==='object'){for(c in f){if(Object.hasOwnProperty.call(f,c)){d=h(f,c);if(d!==undefined){f[c]=d}else{delete f[c]}}}}return k.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,''))){i=eval('('+g+')');return typeof k==='function'?h({'':i},''):i}throw new SyntaxError('JSON.parse');}}}());
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.3.0',BAYEUX_VERSION:'1.0',ID_LENGTH:128,JSONP_CALLBACK:'jsonpcallback',CONNECTION_TYPES:["long-polling","callback-polling"],ENV: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(16)},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])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*)$/},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])}}},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}}});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.Observable={on:function(a,b,c){this._0=this._0||{};var d=this._0[a]=this._0[a]||[];d.push([b,c])},stopObserving:function(a,b,c){if(!this._0||!this._0[a])return;if(!b){delete this._0[a];return}var d=this._0[a],f=d.length;while(f--){if(b&&d[f][0]!==b)continue;if(c&&d[f][1]!==c)continue;d.splice(f,1)}},fire:function(){var b=Array.prototype.slice.call(arguments),c=b.shift();if(!this._0||!this._0[c])return;Faye.each(this._0[c],function(a){a[0].apply(a[1],b.slice())})}};Faye.Channel=Faye.Class({initialize:function(a){this.__id=this.name=a},push:function(a){this.fire('message',a)}});Faye.extend(Faye.Channel.prototype,Faye.Observable);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._1=a;this._5={}},eachChild:function(c,d){Faye.each(this._5,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._1!==undefined)d.call(f,c,this._1)},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._1:null},set:function(a,b){var c=this.traverse(a,true);if(c)c._1=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._5[a[0]];if(!c&&!b)return null;if(!c)c=this._5[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._1===undefined)?[]:[this._1];var g=[];if(Faye.enumEqual(f,['*'])){Faye.each(this._5,function(a,b){if(b._1!==undefined)g.push(b._1)});return g}if(Faye.enumEqual(f,['**'])){g=this.map(function(a,b){return b});if(this._1!==undefined)g.pop();return g}Faye.each(this._5,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._5['**'])g.push(this._5['**']._1);return g}})});Faye.Namespace=Faye.Class({initialize:function(){this._m={}},generate:function(){var a=Faye.random();while(this._m.hasOwnProperty(a))a=Faye.random();return this._m[a]=a}});Faye.Transport=Faye.extend(Faye.Class({initialize:function(a,b){this._f=a;this._6=b},send:function(c,d,f){if(!(c instanceof Array)&&!c.id)c.id=this._f._n.generate();this.request(c,function(b){if(!d)return;Faye.each([].concat(b),function(a){if(a.id===c.id)d.call(f,a);if(a.advice)this._f.handleAdvice(a.advice);if(a.data&&a.channel)this._f.sendToSubscribers(a)},this)},this)}}),{get:function(c,d){var f=c._6;if(d===undefined)d=this.supportedConnectionTypes();var g=null;Faye.each(this._g,function(a,b){if(Faye.indexOf(d,a)<0)return;if(g)return;if(b.isUsable(f))g=b});if(!g)throw'Could not find a usable connection type for '+f;return new g(c,f)},register:function(a,b){this._g[a]=b;b.prototype.connectionType=a},_g:{},supportedConnectionTypes:function(){var c=[],d;Faye.each(this._g,function(a,b){c.push(a)});return c}});Faye.Client=Faye.Class({UNCONNECTED:1,CONNECTING:2,CONNECTED:3,DISCONNECTED:4,HANDSHAKE:'handshake',RETRY:'retry',NONE:'none',DEFAULT_ENDPOINT:'/bayeux',MAX_DELAY:0.1,INTERVAL:1000.0,initialize:function(a){this._6=a||this.DEFAULT_ENDPOINT;this._4=Faye.Transport.get(this);this._2=this.UNCONNECTED;this._n=new Faye.Namespace();this._h=[];this._a=new Faye.Channel.Tree();this._9=[];this._7={reconnect:this.RETRY,interval:this.INTERVAL};if(!Faye.Event)return;Faye.Event.on(Faye.ENV,'beforeunload',this.disconnect,this)},handshake:function(b,c){if(this._7.reconnect===this.NONE)return;if(this._2!==this.UNCONNECTED)return;this._2=this.CONNECTING;var d=this;this._4.send({channel:Faye.Channel.HANDSHAKE,version:Faye.BAYEUX_VERSION,supportedConnectionTypes:Faye.Transport.supportedConnectionTypes()},function(a){if(!a.successful){setTimeout(function(){d.handshake(b,c)},this._7.interval);return this._2=this.UNCONNECTED}this._2=this.CONNECTED;this._8=a.clientId;this._4=Faye.Transport.get(this,a.supportedConnectionTypes);if(b)b.call(c)},this)},connect:function(b,c){if(this._7.reconnect===this.NONE)return;if(this._2===this.DISCONNECTED)return;if(this._7.reconnect===this.HANDSHAKE||this._2===this.UNCONNECTED)return this.handshake(function(){this.connect(b,c)},this);if(this._2===this.CONNECTING)return this._9.push([b,c]);if(this._2!==this.CONNECTED)return;Faye.each(this._9,function(a){a[0].call(a[1])});this._9=[];if(b)b.call(c);if(this._i)return;this._i=this._n.generate();var d=this;this._4.send({channel:Faye.Channel.CONNECT,clientId:this._8,connectionType:this._4.connectionType,id:this._i},function(a){delete this._i;setTimeout(function(){d.connect()},this._7.interval)},this)},disconnect:function(){if(this._2!==this.CONNECTED)return;this._2=this.DISCONNECTED;this._4.send({channel:Faye.Channel.DISCONNECT,clientId:this._8});this._a=new Faye.Channel.Tree()},subscribe:function(c,d,f){this.connect(function(){c=[].concat(c);this._j(c);this._4.send({channel:Faye.Channel.SUBSCRIBE,clientId:this._8,subscription:c},function(b){if(!b.successful)return;c=[].concat(b.subscription);Faye.each(c,function(a){this._a.set(a,[d,f])},this)},this)},this)},unsubscribe:function(c,d,f){this.connect(function(){c=[].concat(c);this._j(c);this._4.send({channel:Faye.Channel.UNSUBSCRIBE,clientId:this._8,subscription:c},function(b){if(!b.successful)return;c=[].concat(b.subscription);Faye.each(c,function(a){this._a.set(a,null)},this)},this)},this)},publish:function(b,c){this.connect(function(){this._j([b]);this._r({channel:b,data:c,clientId:this._8});if(this._o)return;var a=this;this._o=setTimeout(function(){delete a._o;a._s()},this.MAX_DELAY*1000)},this)},handleAdvice:function(a){Faye.extend(this._7,a);if(this._7.reconnect===this.HANDSHAKE)this._8=null},sendToSubscribers:function(b){var c=this._a.glob(b.channel);Faye.each(c,function(a){if(!a)return;a[0].call(a[1],b.data)})},_r:function(a){this._h.push(a)},_s:function(){this._4.send(this._h);this._h=[]},_j:function(b){Faye.each(b,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.Event={_b:[],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._b.push({_c:a,_k:b,_t:c,_d:d,_p:f})},detach:function(a,b,c,d){var f=this._b.length,g;while(f--){g=this._b[f];if((a&&a!==g._c)||(b&&b!==g._k)||(c&&c!==g._t)||(d&&d!==g._d))continue;if(g._c.removeEventListener)g._c.removeEventListener(g._k,g._p,false);else g._c.detachEvent('on'+g._k,g._p);this._b.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(){return this.protocol+this.hostname+':'+this.port+this.pathname+'?'+this.queryString()}}),{parse:function(d,f){if(typeof d!=='string')return d;var g=new this();var k=function(b,c){d=d.replace(c,function(a){if(a)g[b]=a;return''})};k('protocol',/^https?\:\/+/);k('hostname',/^[^\/\:]+/);k('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 i=d.split('?'),h=i.shift(),l=i.join('?'),n=l?l.split('&'):[],o=n.length,j={};while(o--){i=n[o].split('=');j[decodeURIComponent(i[0]||'')]=decodeURIComponent(i[1]||'')}Faye.extend(j,f);g.pathname=h;g.params=j;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._e=a.toUpperCase();this._6=Faye.URI.parse(b,c);this._9=(typeof d==='function')?{success:d}:d;this._d=f||null;this._3=null},send:function(){if(this._l)return;var a=this._6.pathname,b=this._6.queryString();if(this._e==='GET')a+='?'+b;var c=this._e==='POST'?b:'';this._l=true;this._3=Faye.XHR.getXhrObject();this._3.open(this._e,a,true);if(this._e==='POST')this._3.setRequestHeader('Content-Type','application/x-www-form-urlencoded');var d=this,f=function(){if(d._3.readyState!==4)return;if(g){clearInterval(g);g=null}Faye.Event.detach(Faye.ENV,'beforeunload',d.abort,d);d._l=false;d._u();d=null};var g=setInterval(f,10);Faye.Event.on(Faye.ENV,'beforeunload',this.abort,this);this._3.send(c)},abort:function(){this._3.abort()},_u: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._l},complete:function(){return this._3&&!this.waiting()},success:function(){if(!this.complete())return false;var a=this._3.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._3.responseText},status:function(){if(!this.complete())return null;return this._3.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,j,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,k=j,i,h=b[a];if(h&&typeof h==='object'&&typeof h.toJSON==='function'){h=h.toJSON(a)}if(typeof m==='function'){h=m.call(b,a,h)}switch(typeof h){case'string':return r(h);case'number':return isFinite(h)?String(h):'null';case'boolean':case'null':return String(h);case'object':if(!h){return'null'}j+=p;i=[];if(Object.prototype.toString.apply(h)==='[object Array]'){g=h.length;for(c=0;c<g;c+=1){i[c]=q(c,h)||'null'}f=i.length===0?'[]':j?'[\n'+j+i.join(',\n'+j)+'\n'+k+']':'['+i.join(',')+']';j=k;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,h);if(f){i.push(r(d)+(j?': ':':')+f)}}}}else{for(d in h){if(Object.hasOwnProperty.call(h,d)){f=q(d,h);if(f){i.push(r(d)+(j?': ':':')+f)}}}}f=i.length===0?'{}':j?'{\n'+j+i.join(',\n'+j)+'\n'+k+'}':'{'+i.join(',')+'}';j=k;return f}}if(typeof JSON.stringify!=='function'){JSON.stringify=function(a,b,c){var d;j='';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.parse!=='function'){JSON.parse=function(g,k){var i;function h(a,b){var c,d,f=a[b];if(f&&typeof f==='object'){for(c in f){if(Object.hasOwnProperty.call(f,c)){d=h(f,c);if(d!==undefined){f[c]=d}else{delete f[c]}}}}return k.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,''))){i=eval('('+g+')');return typeof k==='function'?h({'':i},''):i}throw new SyntaxError('JSON.parse');}}}());Faye.XHRTransport=Faye.Class(Faye.Transport,{request:function(b,c,d){var f={message:JSON.stringify(b)};Faye.XHR.request('post',this._6,f,function(a){if(c)c.call(d,JSON.parse(a.text()))})}});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,d){var f={message:JSON.stringify(b)},g=document.getElementsByTagName('head')[0],k=document.createElement('script'),i=Faye.JSONPTransport.getCallbackName(),h=Faye.URI.parse(this._6,f);Faye.ENV[i]=function(a){Faye.ENV[i]=undefined;try{delete Faye.ENV[i]}catch(e){}g.removeChild(k);if(c)c.call(d,a)};h.params.jsonp=i;k.type='text/javascript';k.src=h.toURL();g.appendChild(k)}}),{_q:0,getCallbackName:function(){this._q+=1;return'__jsonp'+this._q+'__'}});Faye.JSONPTransport.isUsable=function(a){return true};Faye.Transport.register('callback-polling',Faye.JSONPTransport);
data/build/faye.js CHANGED
@@ -3,23 +3,36 @@ if (!this.Faye) Faye = {};
3
3
  Faye.extend = function(dest, source, overwrite) {
4
4
  if (!source) return dest;
5
5
  for (var key in source) {
6
- if (source.hasOwnProperty(key) && dest[key] !== source[key]) {
7
- if (!dest.hasOwnProperty(key) || overwrite !== false)
8
- dest[key] = source[key];
9
- }
6
+ if (!source.hasOwnProperty(key)) continue;
7
+ if (dest.hasOwnProperty(key) && overwrite === false) continue;
8
+ if (dest[key] !== source[key])
9
+ dest[key] = source[key];
10
10
  }
11
11
  return dest;
12
12
  };
13
13
 
14
14
  Faye.extend(Faye, {
15
+ VERSION: '0.3.0',
16
+
15
17
  BAYEUX_VERSION: '1.0',
16
- VERSION: '0.2.2',
17
- JSONP_CALLBACK: 'jsonpcallback',
18
18
  ID_LENGTH: 128,
19
+ JSONP_CALLBACK: 'jsonpcallback',
19
20
  CONNECTION_TYPES: ["long-polling", "callback-polling"],
20
21
 
21
22
  ENV: this,
22
23
 
24
+ random: function(bitlength) {
25
+ bitlength = bitlength || this.ID_LENGTH;
26
+ if (bitlength > 32) {
27
+ var parts = Math.ceil(bitlength / 32),
28
+ string = '';
29
+ while (parts--) string += this.random(32);
30
+ return string;
31
+ }
32
+ var field = Math.pow(2, bitlength);
33
+ return Math.floor(Math.random() * field).toString(16);
34
+ },
35
+
23
36
  Grammar: {
24
37
 
25
38
  LOWALPHA: /^[a-z]$/,
@@ -97,17 +110,21 @@ Faye.extend(Faye, {
97
110
  }
98
111
  },
99
112
 
113
+ filter: function(array, callback, scope) {
114
+ var result = [];
115
+ this.each(array, function() {
116
+ if (callback.apply(scope, arguments))
117
+ result.push(arguments[0]);
118
+ });
119
+ return result;
120
+ },
121
+
100
122
  size: function(object) {
101
123
  var size = 0;
102
124
  this.each(object, function() { size += 1 });
103
125
  return size;
104
126
  },
105
127
 
106
- random: function() {
107
- var field = Math.pow(2, this.ID_LENGTH);
108
- return (Math.random() * field).toString(16).replace(/0*$/, '');
109
- },
110
-
111
128
  enumEqual: function(actual, expected) {
112
129
  if (expected instanceof Array) {
113
130
  if (!(actual instanceof Array)) return false;
@@ -190,7 +207,7 @@ Faye.Observable = {
190
207
 
191
208
  Faye.Channel = Faye.Class({
192
209
  initialize: function(name) {
193
- this.__id = this._name = name;
210
+ this.__id = this.name = name;
194
211
  },
195
212
 
196
213
  push: function(message) {
@@ -311,7 +328,7 @@ Faye.extend(Faye.Channel, {
311
328
 
312
329
  if (Faye.enumEqual(path, ['**'])) {
313
330
  list = this.map(function(key, value) { return value });
314
- list.pop();
331
+ if (this._value !== undefined) list.pop();
315
332
  return list;
316
333
  }
317
334
 
@@ -324,23 +341,341 @@ Faye.extend(Faye.Channel, {
324
341
  if (this._children['**']) list.push(this._children['**']._value);
325
342
  return list;
326
343
  }
344
+ })
345
+ });
346
+
347
+
348
+ Faye.Namespace = Faye.Class({
349
+ initialize: function() {
350
+ this._used = {};
351
+ },
352
+
353
+ generate: function() {
354
+ var name = Faye.random();
355
+ while (this._used.hasOwnProperty(name))
356
+ name = Faye.random();
357
+ return this._used[name] = name;
358
+ }
359
+ });
360
+
361
+
362
+ Faye.Transport = Faye.extend(Faye.Class({
363
+ initialize: function(client, endpoint) {
364
+ this._client = client;
365
+ this._endpoint = endpoint;
366
+ },
367
+
368
+ send: function(message, callback, scope) {
369
+ if (!(message instanceof Array) && !message.id)
370
+ message.id = this._client._namespace.generate();
327
371
 
328
- /**
329
- Tests
330
-
331
- glob = new Faye.Channel.Tree();
332
- list = '/foo/bar /foo/boo /foo /foobar /foo/bar/boo /foobar/boo /foo/* /foo/**'.split(' ');
372
+ this.request(message, function(responses) {
373
+ if (!callback) return;
374
+ Faye.each([].concat(responses), function(response) {
375
+
376
+ if (response.id === message.id)
377
+ callback.call(scope, response);
378
+
379
+ if (response.advice)
380
+ this._client.handleAdvice(response.advice);
381
+
382
+ if (response.data && response.channel)
383
+ this._client.sendToSubscribers(response);
384
+
385
+ }, this);
386
+ }, this);
387
+ }
388
+ }), {
389
+ get: function(client, connectionTypes) {
390
+ var endpoint = client._endpoint;
391
+ if (connectionTypes === undefined) connectionTypes = this.supportedConnectionTypes();
392
+
393
+ var candidateClass = null;
394
+ Faye.each(this._transports, function(connType, klass) {
395
+ if (Faye.indexOf(connectionTypes, connType) < 0) return;
396
+ if (candidateClass) return;
397
+ if (klass.isUsable(endpoint)) candidateClass = klass;
398
+ });
399
+
400
+ if (!candidateClass) throw 'Could not find a usable connection type for ' + endpoint;
401
+
402
+ return new candidateClass(client, endpoint);
403
+ },
404
+
405
+ register: function(type, klass) {
406
+ this._transports[type] = klass;
407
+ klass.prototype.connectionType = type;
408
+ },
409
+
410
+ _transports: {},
411
+
412
+ supportedConnectionTypes: function() {
413
+ var list = [], key;
414
+ Faye.each(this._transports, function(key, type) { list.push(key) });
415
+ return list;
416
+ }
417
+ });
333
418
 
334
- Faye.each(list, function(c, i) {
335
- glob.set(c, i + 1);
336
- });
337
419
 
338
- console.log(glob.glob('/foo/*').sort()); // 1,2,7,8
339
- console.log(glob.glob('/foo/bar').sort()); // 1,7,8
340
- console.log(glob.glob('/foo/**').sort()); // 1,2,5,7,8
341
- console.log(glob.glob('/foo/bar/boo').sort()); // 5,8
342
- **/
343
- })
420
+ Faye.Client = Faye.Class({
421
+ UNCONNECTED: 1,
422
+ CONNECTING: 2,
423
+ CONNECTED: 3,
424
+ DISCONNECTED: 4,
425
+
426
+ HANDSHAKE: 'handshake',
427
+ RETRY: 'retry',
428
+ NONE: 'none',
429
+
430
+ DEFAULT_ENDPOINT: '/bayeux',
431
+ MAX_DELAY: 0.1,
432
+ INTERVAL: 1000.0,
433
+
434
+ initialize: function(endpoint) {
435
+ this._endpoint = endpoint || this.DEFAULT_ENDPOINT;
436
+ this._transport = Faye.Transport.get(this);
437
+ this._state = this.UNCONNECTED;
438
+ this._namespace = new Faye.Namespace();
439
+ this._outbox = [];
440
+ this._channels = new Faye.Channel.Tree();
441
+ this._callbacks = [];
442
+
443
+ this._advice = {reconnect: this.RETRY, interval: this.INTERVAL};
444
+
445
+ if (!Faye.Event) return;
446
+ Faye.Event.on(Faye.ENV, 'beforeunload', this.disconnect, this);
447
+ },
448
+
449
+ // Request
450
+ // MUST include: * channel
451
+ // * version
452
+ // * supportedConnectionTypes
453
+ // MAY include: * minimumVersion
454
+ // * ext
455
+ // * id
456
+ //
457
+ // Success Response Failed Response
458
+ // MUST include: * channel MUST include: * channel
459
+ // * version * successful
460
+ // * supportedConnectionTypes * error
461
+ // * clientId MAY include: * supportedConnectionTypes
462
+ // * successful * advice
463
+ // MAY include: * minimumVersion * version
464
+ // * advice * minimumVersion
465
+ // * ext * ext
466
+ // * id * id
467
+ // * authSuccessful
468
+ handshake: function(callback, scope) {
469
+ if (this._advice.reconnect === this.NONE) return;
470
+ if (this._state !== this.UNCONNECTED) return;
471
+
472
+ this._state = this.CONNECTING;
473
+ var self = this;
474
+
475
+ this._transport.send({
476
+ channel: Faye.Channel.HANDSHAKE,
477
+ version: Faye.BAYEUX_VERSION,
478
+ supportedConnectionTypes: Faye.Transport.supportedConnectionTypes()
479
+
480
+ }, function(response) {
481
+
482
+ if (!response.successful) {
483
+ setTimeout(function() { self.handshake(callback, scope) }, this._advice.interval);
484
+ return this._state = this.UNCONNECTED;
485
+ }
486
+
487
+ this._state = this.CONNECTED;
488
+ this._clientId = response.clientId;
489
+ this._transport = Faye.Transport.get(this, response.supportedConnectionTypes);
490
+
491
+ if (callback) callback.call(scope);
492
+ }, this);
493
+ },
494
+
495
+ // Request Response
496
+ // MUST include: * channel MUST include: * channel
497
+ // * clientId * successful
498
+ // * connectionType * clientId
499
+ // MAY include: * ext MAY include: * error
500
+ // * id * advice
501
+ // * ext
502
+ // * id
503
+ // * timestamp
504
+ connect: function(callback, scope) {
505
+ if (this._advice.reconnect === this.NONE) return;
506
+ if (this._state === this.DISCONNECTED) return;
507
+
508
+ if (this._advice.reconnect === this.HANDSHAKE || this._state === this.UNCONNECTED)
509
+ return this.handshake(function() { this.connect(callback, scope) }, this);
510
+
511
+ if (this._state === this.CONNECTING)
512
+ return this._callbacks.push([callback, scope]);
513
+
514
+ if (this._state !== this.CONNECTED) return;
515
+
516
+ Faye.each(this._callbacks, function(listener) { listener[0].call(listener[1]) });
517
+ this._callbacks = [];
518
+ if (callback) callback.call(scope);
519
+
520
+ if (this._connectionId) return;
521
+ this._connectionId = this._namespace.generate();
522
+ var self = this;
523
+
524
+ this._transport.send({
525
+ channel: Faye.Channel.CONNECT,
526
+ clientId: this._clientId,
527
+ connectionType: this._transport.connectionType,
528
+ id: this._connectionId
529
+
530
+ }, function(response) {
531
+ delete this._connectionId;
532
+ setTimeout(function() { self.connect() }, this._advice.interval);
533
+ }, this);
534
+ },
535
+
536
+ // Request Response
537
+ // MUST include: * channel MUST include: * channel
538
+ // * clientId * successful
539
+ // MAY include: * ext * clientId
540
+ // * id MAY include: * error
541
+ // * ext
542
+ // * id
543
+ disconnect: function() {
544
+ if (this._state !== this.CONNECTED) return;
545
+ this._state = this.DISCONNECTED;
546
+
547
+ this._transport.send({
548
+ channel: Faye.Channel.DISCONNECT,
549
+ clientId: this._clientId
550
+ });
551
+
552
+ this._channels = new Faye.Channel.Tree();
553
+ },
554
+
555
+ // Request Response
556
+ // MUST include: * channel MUST include: * channel
557
+ // * clientId * successful
558
+ // * subscription * clientId
559
+ // MAY include: * ext * subscription
560
+ // * id MAY include: * error
561
+ // * advice
562
+ // * ext
563
+ // * id
564
+ // * timestamp
565
+ subscribe: function(channels, callback, scope) {
566
+ this.connect(function() {
567
+
568
+ channels = [].concat(channels);
569
+ this._validateChannels(channels);
570
+
571
+ this._transport.send({
572
+ channel: Faye.Channel.SUBSCRIBE,
573
+ clientId: this._clientId,
574
+ subscription: channels
575
+
576
+ }, function(response) {
577
+ if (!response.successful) return;
578
+
579
+ channels = [].concat(response.subscription);
580
+ Faye.each(channels, function(channel) {
581
+ this._channels.set(channel, [callback, scope]);
582
+ }, this);
583
+ }, this);
584
+
585
+ }, this);
586
+ },
587
+
588
+ // Request Response
589
+ // MUST include: * channel MUST include: * channel
590
+ // * clientId * successful
591
+ // * subscription * clientId
592
+ // MAY include: * ext * subscription
593
+ // * id MAY include: * error
594
+ // * advice
595
+ // * ext
596
+ // * id
597
+ // * timestamp
598
+ unsubscribe: function(channels, callback, scope) {
599
+ this.connect(function() {
600
+
601
+ channels = [].concat(channels);
602
+ this._validateChannels(channels);
603
+
604
+ this._transport.send({
605
+ channel: Faye.Channel.UNSUBSCRIBE,
606
+ clientId: this._clientId,
607
+ subscription: channels
608
+
609
+ }, function(response) {
610
+ if (!response.successful) return;
611
+
612
+ channels = [].concat(response.subscription);
613
+ Faye.each(channels, function(channel) {
614
+ this._channels.set(channel, null);
615
+ }, this);
616
+ }, this);
617
+
618
+ }, this);
619
+ },
620
+
621
+ // Request Response
622
+ // MUST include: * channel MUST include: * channel
623
+ // * data * successful
624
+ // MAY include: * clientId MAY include: * id
625
+ // * id * error
626
+ // * ext * ext
627
+ publish: function(channel, data) {
628
+ this.connect(function() {
629
+
630
+ this._validateChannels([channel]);
631
+
632
+ this._enqueue({
633
+ channel: channel,
634
+ data: data,
635
+ clientId: this._clientId
636
+ });
637
+
638
+ if (this._timeout) return;
639
+ var self = this;
640
+
641
+ this._timeout = setTimeout(function() {
642
+ delete self._timeout;
643
+ self._flush();
644
+ }, this.MAX_DELAY * 1000);
645
+
646
+ }, this);
647
+ },
648
+
649
+ handleAdvice: function(advice) {
650
+ Faye.extend(this._advice, advice);
651
+ if (this._advice.reconnect === this.HANDSHAKE) this._clientId = null;
652
+ },
653
+
654
+ sendToSubscribers: function(message) {
655
+ var channels = this._channels.glob(message.channel);
656
+ Faye.each(channels, function(callback) {
657
+ if (!callback) return;
658
+ callback[0].call(callback[1], message.data);
659
+ });
660
+ },
661
+
662
+ _enqueue: function(message) {
663
+ this._outbox.push(message);
664
+ },
665
+
666
+ _flush: function() {
667
+ this._transport.send(this._outbox);
668
+ this._outbox = [];
669
+ },
670
+
671
+ _validateChannels: function(channels) {
672
+ Faye.each(channels, function(channel) {
673
+ if (!Faye.Channel.isValid(channel))
674
+ throw '"' + channel + '" is not a valid channel name';
675
+ if (!Faye.Channel.isSubscribable(channel))
676
+ throw 'Clients may not subscribe to channel "' + channel + '"';
677
+ });
678
+ }
344
679
  });
345
680
 
346
681
 
@@ -392,9 +727,10 @@ Faye.Set = Faye.Class({
392
727
 
393
728
  Faye.Server = Faye.Class({
394
729
  initialize: function(options) {
395
- this._options = options || {};
396
- this._channels = new Faye.Channel.Tree();
397
- this._clients = {};
730
+ this._options = options || {};
731
+ this._channels = new Faye.Channel.Tree();
732
+ this._clients = {};
733
+ this._namespace = new Faye.Namespace();
398
734
  },
399
735
 
400
736
  clientIds: function() {
@@ -404,7 +740,7 @@ Faye.Server = Faye.Class({
404
740
  },
405
741
 
406
742
  process: function(messages, local, callback) {
407
- messages = (messages instanceof Array) ? messages : [messages];
743
+ messages = [].concat(messages);
408
744
  var processed = 0, responses = [];
409
745
 
410
746
  Faye.each(messages, function(message) {
@@ -417,29 +753,23 @@ Faye.Server = Faye.Class({
417
753
  },
418
754
 
419
755
  flushConnection: function(messages) {
420
- messages = (messages instanceof Array) ? messages : [messages];
756
+ messages = [].concat(messages);
421
757
  Faye.each(messages, function(message) {
422
758
  var client = this._clients[message.clientId];
423
759
  if (client) client.flush();
424
760
  }, this);
425
761
  },
426
762
 
427
- _generateId: function() {
428
- var id = Faye.random();
429
- while (this._clients.hasOwnProperty(id)) id = Faye.random();
430
- return this._connection(id).id;
431
- },
432
-
433
763
  _connection: function(id) {
434
764
  if (this._clients.hasOwnProperty(id)) return this._clients[id];
435
765
  var client = new Faye.Connection(id, this._options);
436
- client.on('stale', this._destroyClient, this);
766
+ client.on('staleClient', this._destroyClient, this);
437
767
  return this._clients[id] = client;
438
768
  },
439
769
 
440
770
  _destroyClient: function(client) {
441
771
  client.disconnect();
442
- client.stopObserving('stale', this._destroyClient, this);
772
+ client.stopObserving('staleClient', this._destroyClient, this);
443
773
  delete this._clients[client.id];
444
774
  },
445
775
 
@@ -448,6 +778,9 @@ Faye.Server = Faye.Class({
448
778
  channel = message.channel,
449
779
  response;
450
780
 
781
+ message.__id = Faye.random();
782
+ Faye.each(this._channels.glob(channel), function(c) { c.push(message) });
783
+
451
784
  if (Faye.Channel.isMeta(channel)) {
452
785
  response = this[Faye.Channel.parse(channel)[1]](message, local);
453
786
 
@@ -473,18 +806,19 @@ Faye.Server = Faye.Class({
473
806
  if (!message.clientId || Faye.Channel.isService(channel))
474
807
  return callback([]);
475
808
 
476
- message.__id = Faye.random();
477
- Faye.each(this._channels.glob(channel), function(c) { c.push(message) });
478
-
479
809
  callback( { channel: channel,
480
810
  successful: true,
481
811
  id: message.id } );
482
812
  },
483
813
 
814
+ // MUST contain * version
815
+ // * supportedConnectionTypes
816
+ // MAY contain * minimumVersion
817
+ // * ext
818
+ // * id
484
819
  handshake: function(message, local) {
485
820
  var response = { channel: Faye.Channel.HANDSHAKE,
486
821
  version: Faye.BAYEUX_VERSION,
487
- supportedConnectionTypes: Faye.CONNECTION_TYPES,
488
822
  id: message.id };
489
823
 
490
824
  if (!message.version)
@@ -493,23 +827,32 @@ Faye.Server = Faye.Class({
493
827
  var clientConns = message.supportedConnectionTypes,
494
828
  commonConns;
495
829
 
496
- if (clientConns) {
497
- commonConns = clientConns.filter(function(conn) {
498
- return Faye.CONNECTION_TYPES.indexOf(conn) !== -1;
499
- });
500
- if (commonConns.length === 0)
501
- response.error = Faye.Error.conntypeMismatch(clientConns);
502
- } else {
503
- response.error = Faye.Error.parameterMissing('supportedConnectionTypes');
830
+ if (!local) {
831
+ response.supportedConnectionTypes = Faye.CONNECTION_TYPES;
832
+
833
+ if (clientConns) {
834
+ commonConns = Faye.filter(clientConns, function(conn) {
835
+ return Faye.indexOf(Faye.CONNECTION_TYPES, conn) !== -1;
836
+ });
837
+ if (commonConns.length === 0)
838
+ response.error = Faye.Error.conntypeMismatch(clientConns);
839
+ } else {
840
+ response.error = Faye.Error.parameterMissing('supportedConnectionTypes');
841
+ }
504
842
  }
505
843
 
506
844
  response.successful = !response.error;
507
845
  if (!response.successful) return response;
508
846
 
509
- response.clientId = this._generateId();
847
+ var clientId = this._namespace.generate();
848
+ response.clientId = this._connection(clientId).id;
510
849
  return response;
511
850
  },
512
851
 
852
+ // MUST contain * clientId
853
+ // * connectionType
854
+ // MAY contain * ext
855
+ // * id
513
856
  connect: function(message, local) {
514
857
  var response = { channel: Faye.Channel.CONNECT,
515
858
  id: message.id };
@@ -529,6 +872,9 @@ Faye.Server = Faye.Class({
529
872
  return response;
530
873
  },
531
874
 
875
+ // MUST contain * clientId
876
+ // MAY contain * ext
877
+ // * id
532
878
  disconnect: function(message, local) {
533
879
  var response = { channel: Faye.Channel.DISCONNECT,
534
880
  id: message.id };
@@ -548,6 +894,10 @@ Faye.Server = Faye.Class({
548
894
  return response;
549
895
  },
550
896
 
897
+ // MUST contain * clientId
898
+ // * subscription
899
+ // MAY contain * ext
900
+ // * id
551
901
  subscribe: function(message, local) {
552
902
  var response = { channel: Faye.Channel.SUBSCRIBE,
553
903
  clientId: message.clientId,
@@ -557,7 +907,7 @@ Faye.Server = Faye.Class({
557
907
  client = clientId ? this._clients[clientId] : null,
558
908
  subscription = message.subscription;
559
909
 
560
- subscription = (subscription instanceof Array) ? subscription : [subscription];
910
+ subscription = [].concat(subscription);
561
911
 
562
912
  if (!client) response.error = Faye.Error.clientUnknown(clientId);
563
913
  if (!clientId) response.error = Faye.Error.parameterMissing('clientId');
@@ -567,8 +917,8 @@ Faye.Server = Faye.Class({
567
917
 
568
918
  Faye.each(subscription, function(channel) {
569
919
  if (response.error) return;
570
- if (!Faye.Channel.isSubscribable(channel)) response.error = Faye.Error.channelForbidden(channel);
571
- if (!Faye.Channel.isValid(channel)) response.error = Faye.Error.channelInvalid(channel);
920
+ if (!local && !Faye.Channel.isSubscribable(channel)) response.error = Faye.Error.channelForbidden(channel);
921
+ if (!Faye.Channel.isValid(channel)) response.error = Faye.Error.channelInvalid(channel);
572
922
 
573
923
  if (response.error) return;
574
924
  channel = this._channels.findOrCreate(channel);
@@ -579,6 +929,10 @@ Faye.Server = Faye.Class({
579
929
  return response;
580
930
  },
581
931
 
932
+ // MUST contain * clientId
933
+ // * subscription
934
+ // MAY contain * ext
935
+ // * id
582
936
  unsubscribe: function(message, local) {
583
937
  var response = { channel: Faye.Channel.UNSUBSCRIBE,
584
938
  clientId: message.clientId,
@@ -588,7 +942,7 @@ Faye.Server = Faye.Class({
588
942
  client = clientId ? this._clients[clientId] : null,
589
943
  subscription = message.subscription;
590
944
 
591
- subscription = (subscription instanceof Array) ? subscription : [subscription];
945
+ subscription = [].concat(subscription);
592
946
 
593
947
  if (!client) response.error = Faye.Error.clientUnknown(clientId);
594
948
  if (!clientId) response.error = Faye.Error.parameterMissing('clientId');
@@ -611,16 +965,19 @@ Faye.Server = Faye.Class({
611
965
 
612
966
 
613
967
  Faye.Connection = Faye.Class({
968
+ MAX_DELAY: 0.1,
969
+ INTERVAL: 1.0,
970
+ TIMEOUT: 60.0,
971
+
614
972
  initialize: function(id, options) {
615
973
  this.id = id;
616
974
  this._options = options;
617
- this._observers = {};
618
975
  this._channels = new Faye.Set();
619
976
  this._inbox = new Faye.Set();
620
977
  },
621
978
 
622
- timeout: function() {
623
- return this._options.timeout || Faye.Connection.TIMEOUT;
979
+ getTimeout: function() {
980
+ return this._options.timeout || this.TIMEOUT;
624
981
  },
625
982
 
626
983
  _onMessage: function(event) {
@@ -644,10 +1001,14 @@ Faye.Connection = Faye.Class({
644
1001
  this.on('flush', callback);
645
1002
  if (this._connected) return;
646
1003
 
647
- this._markForDeletion = false;
648
- this._connected = true;
1004
+ this._connected = true;
1005
+
1006
+ if (this._deletionTimeout) {
1007
+ clearTimeout(this._deletionTimeout);
1008
+ delete this._deletionTimeout;
1009
+ }
649
1010
 
650
- if (!this._inbox.isEmpty()) this._beginDeliveryTimeout();
1011
+ this._beginDeliveryTimeout();
651
1012
  this._beginConnectionTimeout();
652
1013
  },
653
1014
 
@@ -673,7 +1034,7 @@ Faye.Connection = Faye.Class({
673
1034
 
674
1035
  var self = this;
675
1036
  this._deliveryTimeout = setTimeout(function () { self.flush() },
676
- Faye.Connection.MAX_DELAY * 1000);
1037
+ this.MAX_DELAY * 1000);
677
1038
  },
678
1039
 
679
1040
  _beginConnectionTimeout: function() {
@@ -682,7 +1043,7 @@ Faye.Connection = Faye.Class({
682
1043
 
683
1044
  var self = this;
684
1045
  this._connectionTimeout = setTimeout(function() { self.flush() },
685
- this.timeout() * 1000);
1046
+ this.getTimeout() * 1000);
686
1047
  },
687
1048
 
688
1049
  _releaseConnection: function() {
@@ -701,25 +1062,17 @@ Faye.Connection = Faye.Class({
701
1062
  },
702
1063
 
703
1064
  _scheduleForDeletion: function() {
704
- if (this._markForDeletion) return;
705
- this._markForDeletion = true;
1065
+ if (this._deletionTimeout) return;
706
1066
  var self = this;
707
1067
 
708
- setTimeout(function() {
709
- if (!self._markForDeletion) return;
710
- self.fire('stale', self);
711
- }, 10000 * Faye.Connection.INTERVAL);
1068
+ this._deletionTimeout = setTimeout(function() {
1069
+ self.fire('staleClient', self);
1070
+ }, 10 * 1000 * this.INTERVAL);
712
1071
  }
713
1072
  });
714
1073
 
715
1074
  Faye.extend(Faye.Connection.prototype, Faye.Observable);
716
1075
 
717
- Faye.extend(Faye.Connection, {
718
- MAX_DELAY: 0.1,
719
- INTERVAL: 1.0,
720
- TIMEOUT: 60.0
721
- });
722
-
723
1076
 
724
1077
  Faye.Error = Faye.Class({
725
1078
  initialize: function(code, args, message) {
@@ -786,33 +1139,100 @@ Faye.Error.serverError = function() {
786
1139
 
787
1140
 
788
1141
 
1142
+ Faye.NodeHttpTransport = Faye.Class(Faye.Transport, {
1143
+ request: function(message, callback, scope) {
1144
+ var params = {message: JSON.stringify(message)},
1145
+ request = this.createRequest();
1146
+
1147
+ request.write(querystring.stringify(params));
1148
+
1149
+ request.addListener('response', function(response) {
1150
+ if (!callback) return;
1151
+ response.addListener('data', function(chunk) {
1152
+ callback.call(scope, JSON.parse(chunk));
1153
+ });
1154
+ });
1155
+ request.close();
1156
+ },
1157
+
1158
+ createRequest: function() {
1159
+ var uri = url.parse(this._endpoint),
1160
+ client = http.createClient(uri.port, uri.hostname);
1161
+
1162
+ return client.request('POST', uri.pathname, {
1163
+ 'Content-Type': 'application/x-www-form-urlencoded'
1164
+ });
1165
+ }
1166
+ });
1167
+
1168
+ Faye.NodeHttpTransport.isUsable = function(endpoint) {
1169
+ return typeof endpoint === 'string';
1170
+ };
1171
+
1172
+ Faye.Transport.register('long-polling', Faye.NodeHttpTransport);
1173
+
1174
+ Faye.NodeLocalTransport = Faye.Class(Faye.Transport, {
1175
+ request: function(message, callback, scope) {
1176
+ this._endpoint.process(message, true, function(response) {
1177
+ callback.call(scope, response);
1178
+ });
1179
+ }
1180
+ });
1181
+
1182
+ Faye.NodeLocalTransport.isUsable = function(endpoint) {
1183
+ return endpoint instanceof Faye.Server;
1184
+ };
1185
+
1186
+ Faye.Transport.register('in-process', Faye.NodeLocalTransport);
1187
+
1188
+
789
1189
  var path = require('path'),
790
- posix = require('posix'),
1190
+ fs = require('fs'),
791
1191
  sys = require('sys'),
792
1192
  url = require('url'),
1193
+ http = require('http'),
793
1194
  querystring = require('querystring');
794
1195
 
795
1196
  Faye.NodeAdapter = Faye.Class({
1197
+ DEFAULT_ENDPOINT: '/bayeux',
1198
+ SCRIPT_PATH: path.dirname(__filename) + '/faye-client-min.js',
1199
+
1200
+ TYPE_JSON: {'Content-Type': 'text/json'},
1201
+ TYPE_SCRIPT: {'Content-Type': 'text/javascript'},
1202
+ TYPE_TEXT: {'Content-Type': 'text/plain'},
1203
+
796
1204
  initialize: function(options) {
797
1205
  this._options = options || {};
798
- this._endpoint = this._options.mount || Faye.NodeAdapter.DEFAULT_ENDPOINT;
1206
+ this._endpoint = this._options.mount || this.DEFAULT_ENDPOINT;
799
1207
  this._script = this._endpoint + '.js';
800
1208
  this._server = new Faye.Server(this._options);
801
1209
  },
802
1210
 
1211
+ getClient: function() {
1212
+ return this._client = this._client || new Faye.Client(this._server);
1213
+ },
1214
+
1215
+ run: function(port) {
1216
+ var self = this;
1217
+ http.createServer(function(request, response) {
1218
+ self.call(request, response);
1219
+ }).listen(Number(port));
1220
+ },
1221
+
803
1222
  call: function(request, response) {
804
- var requestUrl = url.parse(request.url, true);
1223
+ var requestUrl = url.parse(request.url, true),
1224
+ self = this;
1225
+
805
1226
  switch (requestUrl.pathname) {
806
1227
 
807
1228
  case this._endpoint:
808
- var isGet = (request.method === 'GET'),
809
- self = this;
1229
+ var isGet = (request.method === 'GET');
810
1230
 
811
1231
  if (isGet)
812
1232
  this._callWithParams(request, response, requestUrl.query);
813
1233
 
814
1234
  else
815
- request.addListener('body', function(chunk) {
1235
+ request.addListener('data', function(chunk) {
816
1236
  self._callWithParams(request, response, querystring.parse(chunk));
817
1237
  });
818
1238
 
@@ -820,10 +1240,10 @@ Faye.NodeAdapter = Faye.Class({
820
1240
  break;
821
1241
 
822
1242
  case this._script:
823
- posix.cat(Faye.NodeAdapter.SCRIPT_PATH).addCallback(function(content) {
824
- response.sendHeader(200, Faye.NodeAdapter.TYPE_SCRIPT);
825
- response.sendBody(content);
826
- response.finish();
1243
+ fs.readFile(this.SCRIPT_PATH, function(err, content) {
1244
+ response.sendHeader(200, self.TYPE_SCRIPT);
1245
+ response.write(content);
1246
+ response.close();
827
1247
  });
828
1248
  return true;
829
1249
  break;
@@ -837,7 +1257,7 @@ Faye.NodeAdapter = Faye.Class({
837
1257
  var message = JSON.parse(params.message),
838
1258
  jsonp = params.jsonp || Faye.JSONP_CALLBACK,
839
1259
  isGet = (request.method === 'GET'),
840
- type = isGet ? Faye.NodeAdapter.TYPE_SCRIPT : Faye.NodeAdapter.TYPE_JSON;
1260
+ type = isGet ? this.TYPE_SCRIPT : this.TYPE_JSON;
841
1261
 
842
1262
  if (isGet) this._server.flushConnection(message);
843
1263
 
@@ -845,24 +1265,16 @@ Faye.NodeAdapter = Faye.Class({
845
1265
  var body = JSON.stringify(replies);
846
1266
  if (isGet) body = jsonp + '(' + body + ');';
847
1267
  response.sendHeader(200, type);
848
- response.sendBody(body);
849
- response.finish();
1268
+ response.write(body);
1269
+ response.close();
850
1270
  });
851
1271
  } catch (e) {
852
- response.sendHeader(400, {'Content-Type': 'text/plain'});
853
- response.sendBody('Bad request');
854
- response.finish();
1272
+ response.sendHeader(400, this.TYPE_TEXT);
1273
+ response.write('Bad request');
1274
+ response.close();
855
1275
  }
856
1276
  }
857
1277
  });
858
1278
 
859
- Faye.extend(Faye.NodeAdapter, {
860
- DEFAULT_ENDPOINT: '/bayeux',
861
- SCRIPT_PATH: path.dirname(__filename) + '/faye-client-min.js',
862
-
863
- TYPE_JSON: {'Content-Type': 'text/json'},
864
- TYPE_SCRIPT: {'Content-Type': 'text/javascript'},
865
- TYPE_TEXT: {'Content-Type': 'text/plain'}
866
- });
867
-
868
- exports.NodeAdapter = Faye.NodeAdapter;
1279
+ exports.NodeAdapter = Faye.NodeAdapter;
1280
+ exports.Client = Faye.Client;