faye 0.3.1 → 0.3.2

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.

@@ -1,3 +1,15 @@
1
+ === 0.3.2 / 2010-04-04
2
+
3
+ * Fix problems with JSON serialization when Prototype, MooTools present
4
+ * Make the client reconnect if it doesn't hear from the server after a timeout
5
+ * Stop JavaScript server returning NaN for advice.interval
6
+ * Make Ruby server return an integer for advice.interval
7
+ * Ensure EventMachine is running before handling messages
8
+ * Handle 'data' and 'end' events properly in Node HTTP API
9
+ * Switch to application/json for content types and stop using querystring format in POST bodies
10
+ * Respond to any URL path under the mount point, not just the exact match
11
+
12
+
1
13
  === 0.3.1 / 2010-03-09
2
14
 
3
15
  * Pass client down through Rack stack as env['faye.client']
@@ -10,6 +10,8 @@ examples/README.rdoc
10
10
  examples/shared/public/index.html
11
11
  examples/shared/public/favicon.ico
12
12
  examples/shared/public/jquery.js
13
+ examples/shared/public/mootools.js
14
+ examples/shared/public/prototype.js
13
15
  examples/shared/public/robots.txt
14
16
  examples/shared/public/soapbox.js
15
17
  examples/shared/public/style.css
@@ -30,9 +32,12 @@ lib/faye/grammar.rb
30
32
  lib/faye/namespace.rb
31
33
  lib/faye/rack_adapter.rb
32
34
  lib/faye/server.rb
35
+ lib/faye/timeouts.rb
33
36
  lib/faye/transport.rb
34
37
  test/scenario.js
38
+ test/scenario.rb
35
39
  test/test_channel.rb
36
40
  test/test_clients.js
41
+ test/test_clients.rb
37
42
  test/test_grammar.rb
38
43
  test/test_server.rb
data/README.txt CHANGED
@@ -34,7 +34,7 @@ project of mine, but was not written to scale across multiple processes
34
34
  as required for deployment under Passenger. It has since been ported to
35
35
  Node.js which is better designed for handling highly concurrent traffic.
36
36
  If you use the Ruby version, I recommend deploying it behind Thin, an
37
- event-driver Ruby web server.
37
+ event-driven Ruby web server.
38
38
 
39
39
  The two backends are architecturally identical, using evented messaging
40
40
  throughout and maintaining subscription data in memory. Neither is geared
@@ -64,9 +64,19 @@ You should set up the client as follows:
64
64
  <script type="text/javascript" src="/faye.js"></script>
65
65
 
66
66
  <script type="text/javascript">
67
- fayeClient = new Faye.Client('/faye');
67
+ fayeClient = new Faye.Client('/faye', {timeout: 120});
68
68
  </script>
69
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
+
70
80
  Take care only to have one instance of the client per page; since each one
71
81
  opens a long-running request you will hit the two-requests-per-host limit
72
82
  and block all other Ajax calls if you use more than one client.
@@ -146,6 +156,12 @@ Both backends support the following initialization options:
146
156
  on your frontend webserver to make sure Faye sends a response before
147
157
  the server kills the connection.
148
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
+
149
165
  Usage examples and a demo app are in the +examples+ directory.
150
166
 
151
167
 
@@ -180,10 +196,10 @@ over HTTP or connect directly to the <tt>NodeAdapter</tt>. Either way,
180
196
  the API for the client is exactly as it is in the browser.
181
197
 
182
198
  // Remote client
183
- client = new Faye.Client('http://example.com/faye');
199
+ client = new faye.Client('http://example.com/faye', {timeout: 120});
184
200
 
185
201
  // Local client
186
- server = new Faye.NodeAdapter(options);
202
+ server = new faye.NodeAdapter(options);
187
203
  client = server.getClient();
188
204
 
189
205
  Note <tt>getClient()</tt> returns the same client object every time you
@@ -218,11 +234,11 @@ stack of Rack middlewares.
218
234
 
219
235
  === Rack client
220
236
 
221
- Again mirroring the Node tools, Faye has a client-side Ruby client that
237
+ Again mirroring the Node tools, Faye has a server-side Ruby client that
222
238
  can be used under EventMachine. Setup is identical:
223
239
 
224
240
  # Remote client
225
- client = Faye::Client.new('http://example.com/faye')
241
+ client = Faye::Client.new('http://example.com/faye', :timeout => 120)
226
242
 
227
243
  # Local client
228
244
  server = Faye::RackAdapter.new(options)
@@ -257,7 +273,7 @@ the Rack environment, for example in a Sinatra application:
257
273
  If you want to hack on Faye, you'll need Node.js, Ruby and the following
258
274
  gems installed:
259
275
 
260
- sudo gem install eventmachine em-http-request rack thin json jake
276
+ sudo gem install eventmachine em-http-request rack thin json jake hoe
261
277
 
262
278
  DO NOT edit any JavaScript files outside the +javascript+ and +test+
263
279
  directories. They are generated using Jake, which you should run after
@@ -276,6 +292,8 @@ changes where appropriate.
276
292
  == To-do
277
293
 
278
294
  * Investigate WebSockets as a possible message transport
295
+ * Support Bayeux extensions for authentication
296
+ * Let local server-side clients listen to <tt>/meta/*</tt> channels
279
297
  * Provide support for user-defined <tt>/service/*</tt> channels
280
298
  * Allow server to scale to multiple nodes
281
299
 
data/Rakefile CHANGED
@@ -11,6 +11,7 @@ Hoe.spec('faye') do |p|
11
11
  ['eventmachine', '>= 0.12'],
12
12
  ['em-http-request', '>= 0.2'],
13
13
  ['rack', '>= 1.0'],
14
+ ['thin', '>= 1.2'],
14
15
  ['json', '>= 1.0']
15
16
  ]
16
17
  end
@@ -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))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.1',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.Deferrable={callback:function(a,b){if(this._t==='succeeded')return a.apply(b,this._m);this._9=this._9||[];this._9.push([a,b])},setDeferredStatus:function(){var b=Array.prototype.slice.call(arguments),c=b.shift();this._t=c;this._m=b;if(c!=='succeeded')return;if(!this._9)return;Faye.each(this._9,function(a){a[0].apply(a[1],this._m)},this);this._9=[]}};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._n={}},generate:function(){var a=Faye.random();while(this._n.hasOwnProperty(a))a=Faye.random();return this._n[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._o.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._o=new Faye.Namespace();this._h=[];this._a=new Faye.Channel.Tree();this._p=[];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.callback(b,c);if(this._2!==this.CONNECTED)return;this.setDeferredStatus('succeeded');this.setDeferredStatus('deferred');if(b)b.call(c);if(this._i)return;this._i=this._o.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._u({channel:b,data:c,clientId:this._8});if(this._q)return;var a=this;this._q=setTimeout(function(){delete a._q;a._v()},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)})},_u:function(a){this._h.push(a)},_v: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.extend(Faye.Client.prototype,Faye.Deferrable);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,_w:c,_d:d,_r: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._w)||(d&&d!==g._d))continue;if(g._c.removeEventListener)g._c.removeEventListener(g._k,g._r,false);else g._c.detachEvent('on'+g._k,g._r);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._p=(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._x();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()},_x:function(){var a=this._p;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)}}),{_s:0,getCallbackName:function(){this._s+=1;return'__jsonp'+this._s+'__'}});Faye.JSONPTransport.isUsable=function(a){return true};Faye.Transport.register('callback-polling',Faye.JSONPTransport);
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.2',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}},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(),j=b.getMinutes(),i=b.getSeconds();var h=function(a){return a<10?'0'+a:String(a)};return h(c)+'-'+h(d)+'-'+h(f)+' '+h(g)+':'+h(j)+':'+h(i)}});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.Deferrable={callback:function(a,b){if(this._w==='succeeded')return a.apply(b,this._o);this._a=this._a||[];this._a.push([a,b])},setDeferredStatus:function(){var b=Array.prototype.slice.call(arguments),c=b.shift();this._w=c;this._o=b;if(c!=='succeeded')return;if(!this._a)return;Faye.each(this._a,function(a){a[0].apply(a[1],this._o)},this);this._a=[]}};Faye.Observable={on:function(a,b,c){this._2=this._2||{};var d=this._2[a]=this._2[a]||[];d.push([b,c])},stopObserving:function(a,b,c){if(!this._2||!this._2[a])return;if(!b){delete 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)}},fire: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.slice())})}};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 d='['+b.toUpperCase()+'] [Faye',f=null;for(var g in Faye){if(f)continue;if(typeof Faye[g]!=='function')continue;if(this instanceof Faye[g])f=g}if(f)d+='.'+f;d+='] ';Faye.logger(Faye.timestamp()+' '+d+a)},error:function(a){this.log(a,'error')},warn:function(a){this.log(a,'warn')},info:function(a){this.log(a,'info')},debug:function(a){this.log(a,'debug')}};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.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._3=a;this._8={}},eachChild:function(c,d){Faye.each(this._8,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)},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._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._8[a[0]];if(!c&&!b)return null;if(!c)c=this._8[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._8,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});if(this._3!==undefined)g.pop();return g}Faye.each(this._8,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._8['**'])g.push(this._8['**']._3);return g}})});Faye.Namespace=Faye.Class({initialize:function(){this._p={}},generate:function(){var a=Faye.random();while(this._p.hasOwnProperty(a))a=Faye.random();return this._p[a]=a}});Faye.Transport=Faye.extend(Faye.Class({initialize:function(a,b){this.debug('Created new transport for '+b);this._b=a;this._4=b},send:function(f,g,j){if(!(f instanceof Array)&&!f.id)f.id=this._b._q.generate();this.debug('Client '+this._b._0+' sending message to '+this._4+': '+Faye.toJSON(f));this.request(f,function(b){this.debug('Client '+this._b._0+' received from '+this._4+': '+Faye.toJSON(b));if(!g)return;var c=[],d=true;Faye.each([].concat(b),function(a){if(a.id===f.id){if(g.call(j,a)===false)d=false}if(a.advice)this._b.handleAdvice(a.advice);if(a.data&&a.channel)c.push(a)},this);if(d)this._b.deliverMessages(c)},this)}}),{get:function(c,d){var f=c._4;if(d===undefined)d=this.supportedConnectionTypes();var g=null;Faye.each(this._i,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._i[a]=b;b.prototype.connectionType=a},_i:{},supportedConnectionTypes:function(){var c=[],d;Faye.each(this._i,function(a,b){c.push(a)});return c}});Faye.extend(Faye.Transport.prototype,Faye.Logging);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:1000.0,initialize:function(a,b){this.info('New client created for '+a);this._4=a||this.DEFAULT_ENDPOINT;this._x=b||{};this._r=this._x.timeout||this.CONNECTION_TIMEOUT;this._7=Faye.Transport.get(this);this._1=this.UNCONNECTED;this._q=new Faye.Namespace();this._j=[];this._c=new Faye.Channel.Tree();this._s=[];this._9={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._9.reconnect===this.NONE)return;if(this._1!==this.UNCONNECTED)return;this._1=this.CONNECTING;var d=this;this.info('Initiating handshake with '+this._4);this._7.send({channel:Faye.Channel.HANDSHAKE,version:Faye.BAYEUX_VERSION,supportedConnectionTypes:Faye.Transport.supportedConnectionTypes()},function(a){if(!a.successful){this.info('Handshake unsuccessful');setTimeout(function(){d.handshake(b,c)},this._9.interval);return this._1=this.UNCONNECTED}this._1=this.CONNECTED;this._0=a.clientId;this._7=Faye.Transport.get(this,a.supportedConnectionTypes);this.info('Handshake successful: '+this._0);if(b)b.call(c)},this)},connect:function(b,c){if(this._9.reconnect===this.NONE)return;if(this._1===this.DISCONNECTED)return;if(this._9.reconnect===this.HANDSHAKE||this._1===this.UNCONNECTED){this._t();return this.handshake(function(){this.connect(b,c)},this)}if(this._1===this.CONNECTING)return this.callback(b,c);if(this._1!==this.CONNECTED)return;this.info('Calling deferred actions for '+this._0);this.setDeferredStatus('succeeded');this.setDeferredStatus('deferred');if(b)b.call(c);if(this._d)return;this._d=this._q.generate();var d=this;this.info('Initiating connection for '+this._0);this._7.send({channel:Faye.Channel.CONNECT,clientId:this._0,connectionType:this._7.connectionType,id:this._d},this._k(function(a){delete this._d;this.removeTimeout('reconnect');this.info('Closed connection for '+this._0);setTimeout(function(){d.connect()},this._9.interval)}));this._t()},disconnect:function(){if(this._1!==this.CONNECTED)return;this._1=this.DISCONNECTED;this.info('Disconnecting '+this._0);this._7.send({channel:Faye.Channel.DISCONNECT,clientId:this._0});this.info('Clearing channel listeners for '+this._0);this._c=new Faye.Channel.Tree()},subscribe:function(c,d,f){this.connect(function(){c=[].concat(c);this._l(c);this.info('Client '+this._0+' attempting to subscribe to ['+c.join(', ')+']');this._7.send({channel:Faye.Channel.SUBSCRIBE,clientId:this._0,subscription:c},this._k(function(b){if(!b.successful||!d)return;this.info('Subscription acknowledged for '+this._0+' to ['+c.join(', ')+']');c=[].concat(b.subscription);Faye.each(c,function(a){this._c.set(a,[d,f])},this)}))},this)},unsubscribe:function(c,d,f){this.connect(function(){c=[].concat(c);this._l(c);this.info('Client '+this._0+' attempting to unsubscribe from ['+c.join(', ')+']');this._7.send({channel:Faye.Channel.UNSUBSCRIBE,clientId:this._0,subscription:c},this._k(function(b){if(!b.successful)return;this.info('Unsubscription acknowledged for '+this._0+' from ['+c.join(', ')+']');c=[].concat(b.subscription);Faye.each(c,function(a){this._c.set(a,null)},this)}))},this)},publish:function(a,b){this.connect(function(){this._l([a]);this.info('Client '+this._0+' queueing published message to '+a+': '+Faye.toJSON(b));this._y({channel:a,data:b,clientId:this._0});this.addTimeout('publish',this.MAX_DELAY,this._z,this)},this)},handleAdvice:function(a){Faye.extend(this._9,a);if(this._9.reconnect===this.HANDSHAKE)this._0=null},deliverMessages:function(d){Faye.each(d,function(b){this.info('Client '+this._0+' calling listeners for '+b.channel+' with '+Faye.toJSON(b.data));var c=this._c.glob(b.channel);Faye.each(c,function(a){if(!a)return;a[0].call(a[1],b.data)})},this)},_t:function(){this.addTimeout('reconnect',this._r,function(){delete this._d;delete this._0;this._1=this.UNCONNECTED;this.info('Server took >'+this._r+'s to reply to connection for '+this._0+': attempting to reconnect');this.subscribe(this._c.getKeys())},this)},_y:function(a){this._j.push(a)},_z:function(){this._7.send(this._j);this._j=[]},_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+'"';})},_k:function(b){var c=this;return function(a){if(a.clientId!==c._0)return false;b.call(c,a);return true}}});Faye.extend(Faye.Client.prototype,Faye.Deferrable);Faye.extend(Faye.Client.prototype,Faye.Timeouts);Faye.extend(Faye.Client.prototype,Faye.Logging);Faye.Event={_e:[],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._e.push({_f:a,_m:b,_A:c,_g:d,_u:f})},detach:function(a,b,c,d){var f=this._e.length,g;while(f--){g=this._e[f];if((a&&a!==g._f)||(b&&b!==g._m)||(c&&c!==g._A)||(d&&d!==g._g))continue;if(g._f.removeEventListener)g._f.removeEventListener(g._m,g._u,false);else g._f.detachEvent('on'+g._m,g._u);this._e.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 j=function(b,c){d=d.replace(c,function(a){if(a)g[b]=a;return''})};j('protocol',/^https?\:\/+/);j('hostname',/^[^\/\:]+/);j('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,k={};while(o--){i=n[o].split('=');k[decodeURIComponent(i[0]||'')]=decodeURIComponent(i[1]||'')}if(typeof f==='object')Faye.extend(k,f);g.pathname=h;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._h=a.toUpperCase();this._4=Faye.URI.parse(b,c);this._B=(typeof c==='string')?c:null;this._s=(typeof d==='function')?{success:d}:d;this._g=f||null;this._5=null},send:function(){if(this._n)return;var a=this._4.pathname,b=this._4.queryString();if(this._h==='GET')a+='?'+b;var c=(this._h==='POST')?(this._B||b):'';this._n=true;this._5=Faye.XHR.getXhrObject();this._5.open(this._h,a,true);if(this._h==='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._n=false;d._C();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()},_C:function(){var a=this._s;if(!a)return;return this.success()?a.success&&a.success.call(this._g,this):a.failure&&a.failure.call(this._g,this)},waiting:function(){return!!this._n},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,j=k,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'}k+=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?'[]':k?'[\n'+k+i.join(',\n'+k)+'\n'+j+']':'['+i.join(',')+']';k=j;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)+(k?': ':':')+f)}}}}else{for(d in h){if(Object.hasOwnProperty.call(h,d)){f=q(d,h);if(f){i.push(r(d)+(k?': ':':')+f)}}}}f=i.length===0?'{}':k?'{\n'+k+i.join(',\n'+k)+'\n'+j+'}':'{'+i.join(',')+'}';k=j;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,j){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 j.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 j==='function'?h({'':i},''):i}throw new SyntaxError('JSON.parse');}}}());Faye.XHRTransport=Faye.Class(Faye.Transport,{request:function(b,c,d){Faye.XHR.request('post',this._4,Faye.toJSON(b),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:Faye.toJSON(b)},g=document.getElementsByTagName('head')[0],j=document.createElement('script'),i=Faye.JSONPTransport.getCallbackName(),h=Faye.URI.parse(this._4,f);Faye.ENV[i]=function(a){Faye.ENV[i]=undefined;try{delete Faye.ENV[i]}catch(e){}g.removeChild(j);if(c)c.call(d,a)};h.params.jsonp=i;j.type='text/javascript';j.src=h.toURL();g.appendChild(j)}}),{_v:0,getCallbackName:function(){this._v+=1;return'__jsonp'+this._v+'__'}});Faye.JSONPTransport.isUsable=function(a){return true};Faye.Transport.register('callback-polling',Faye.JSONPTransport);
@@ -12,7 +12,7 @@ Faye.extend = function(dest, source, overwrite) {
12
12
  };
13
13
 
14
14
  Faye.extend(Faye, {
15
- VERSION: '0.3.1',
15
+ VERSION: '0.3.2',
16
16
 
17
17
  BAYEUX_VERSION: '1.0',
18
18
  ID_LENGTH: 128,
@@ -143,6 +143,35 @@ Faye.extend(Faye, {
143
143
  });
144
144
  return result;
145
145
  }
146
+ },
147
+
148
+ // http://assanka.net/content/tech/2009/09/02/json2-js-vs-prototype/
149
+ toJSON: function(object) {
150
+ if (this.stringify)
151
+ return this.stringify(object, function(key, value) {
152
+ return (this[key] instanceof Array)
153
+ ? this[key]
154
+ : value;
155
+ });
156
+
157
+ return JSON.stringify(object);
158
+ },
159
+
160
+ timestamp: function() {
161
+ var date = new Date(),
162
+ year = date.getFullYear(),
163
+ month = date.getMonth() + 1,
164
+ day = date.getDate(),
165
+ hour = date.getHours(),
166
+ minute = date.getMinutes(),
167
+ second = date.getSeconds();
168
+
169
+ var pad = function(n) {
170
+ return n < 10 ? '0' + n : String(n);
171
+ };
172
+
173
+ return pad(year) + '-' + pad(month) + '-' + pad(day) + ' ' +
174
+ pad(hour) + ':' + pad(minute) + ':' + pad(second);
146
175
  }
147
176
  });
148
177
 
@@ -233,6 +262,72 @@ Faye.Observable = {
233
262
  };
234
263
 
235
264
 
265
+ Faye.Logging = {
266
+ LOG_LEVELS: {
267
+ error: 3,
268
+ warn: 2,
269
+ info: 1,
270
+ debug: 0
271
+ },
272
+
273
+ logLevel: 'error',
274
+
275
+ log: function(message, level) {
276
+ if (!Faye.logger) return;
277
+
278
+ var levels = Faye.Logging.LOG_LEVELS;
279
+ if (levels[Faye.Logging.logLevel] > levels[level]) return;
280
+
281
+ var banner = '[' + level.toUpperCase() + '] [Faye',
282
+ klass = null;
283
+
284
+ for (var key in Faye) {
285
+ if (klass) continue;
286
+ if (typeof Faye[key] !== 'function') continue;
287
+ if (this instanceof Faye[key]) klass = key;
288
+ }
289
+ if (klass) banner += '.' + klass;
290
+ banner += '] ';
291
+
292
+ Faye.logger(Faye.timestamp() + ' ' + banner + message);
293
+ },
294
+
295
+ error: function(message) {
296
+ this.log(message, 'error');
297
+ },
298
+ warn: function(message) {
299
+ this.log(message, 'warn');
300
+ },
301
+ info: function(message) {
302
+ this.log(message, 'info');
303
+ },
304
+ debug: function(message) {
305
+ this.log(message, 'debug');
306
+ }
307
+ };
308
+
309
+
310
+ Faye.Timeouts = {
311
+ addTimeout: function(name, delay, callback, scope) {
312
+ this._timeouts = this._timeouts || {};
313
+ if (this._timeouts.hasOwnProperty(name)) return;
314
+ var self = this;
315
+ this._timeouts[name] = setTimeout(function() {
316
+ delete self._timeouts[name];
317
+ callback.call(scope);
318
+ }, 1000 * delay);
319
+ },
320
+
321
+ removeTimeout: function(name) {
322
+ this._timeouts = this._timeouts || {};
323
+ var timeout = this._timeouts[name];
324
+ if (!timeout) return;
325
+ clearTimeout(timeout);
326
+ delete this._timeouts[name];
327
+ }
328
+ };
329
+
330
+
236
331
  Faye.Channel = Faye.Class({
237
332
  initialize: function(name) {
238
333
  this.__id = this.name = name;
@@ -300,6 +395,10 @@ Faye.extend(Faye.Channel, {
300
395
  if (this._value !== undefined) block.call(context, prefix, this._value);
301
396
  },
302
397
 
398
+ getKeys: function() {
399
+ return this.map(function(key, value) { return '/' + key.join('/') });
400
+ },
401
+
303
402
  map: function(block, context) {
304
403
  var result = [];
305
404
  this.each([], function(path, value) {
@@ -389,6 +488,7 @@ Faye.Namespace = Faye.Class({
389
488
 
390
489
  Faye.Transport = Faye.extend(Faye.Class({
391
490
  initialize: function(client, endpoint) {
491
+ this.debug('Created new transport for ' + endpoint);
392
492
  this._client = client;
393
493
  this._endpoint = endpoint;
394
494
  },
@@ -397,20 +497,34 @@ Faye.Transport = Faye.extend(Faye.Class({
397
497
  if (!(message instanceof Array) && !message.id)
398
498
  message.id = this._client._namespace.generate();
399
499
 
500
+ this.debug('Client ' + this._client._clientId +
501
+ ' sending message to ' + this._endpoint + ': ' +
502
+ Faye.toJSON(message));
503
+
400
504
  this.request(message, function(responses) {
505
+ this.debug('Client ' + this._client._clientId +
506
+ ' received from ' + this._endpoint + ': ' +
507
+ Faye.toJSON(responses));
508
+
401
509
  if (!callback) return;
510
+
511
+ var messages = [], deliverable = true;
402
512
  Faye.each([].concat(responses), function(response) {
403
-
404
- if (response.id === message.id)
405
- callback.call(scope, response);
513
+
514
+ if (response.id === message.id) {
515
+ if (callback.call(scope, response) === false)
516
+ deliverable = false;
517
+ }
406
518
 
407
519
  if (response.advice)
408
520
  this._client.handleAdvice(response.advice);
409
521
 
410
522
  if (response.data && response.channel)
411
- this._client.sendToSubscribers(response);
523
+ messages.push(response);
412
524
 
413
525
  }, this);
526
+
527
+ if (deliverable) this._client.deliverMessages(messages);
414
528
  }, this);
415
529
  }
416
530
  }), {
@@ -444,23 +558,32 @@ Faye.Transport = Faye.extend(Faye.Class({
444
558
  }
445
559
  });
446
560
 
561
+ Faye.extend(Faye.Transport.prototype, Faye.Logging);
562
+
447
563
 
448
564
  Faye.Client = Faye.Class({
449
- UNCONNECTED: 1,
450
- CONNECTING: 2,
451
- CONNECTED: 3,
452
- DISCONNECTED: 4,
565
+ UNCONNECTED: 1,
566
+ CONNECTING: 2,
567
+ CONNECTED: 3,
568
+ DISCONNECTED: 4,
569
+
570
+ HANDSHAKE: 'handshake',
571
+ RETRY: 'retry',
572
+ NONE: 'none',
453
573
 
454
- HANDSHAKE: 'handshake',
455
- RETRY: 'retry',
456
- NONE: 'none',
574
+ CONNECTION_TIMEOUT: 60.0,
457
575
 
458
- DEFAULT_ENDPOINT: '/bayeux',
459
- MAX_DELAY: 0.1,
460
- INTERVAL: 1000.0,
576
+ DEFAULT_ENDPOINT: '/bayeux',
577
+ MAX_DELAY: 0.1,
578
+ INTERVAL: 1000.0,
461
579
 
462
- initialize: function(endpoint) {
580
+ initialize: function(endpoint, options) {
581
+ this.info('New client created for ' + endpoint);
582
+
463
583
  this._endpoint = endpoint || this.DEFAULT_ENDPOINT;
584
+ this._options = options || {};
585
+ this._timeout = this._options.timeout || this.CONNECTION_TIMEOUT;
586
+
464
587
  this._transport = Faye.Transport.get(this);
465
588
  this._state = this.UNCONNECTED;
466
589
  this._namespace = new Faye.Namespace();
@@ -500,6 +623,8 @@ Faye.Client = Faye.Class({
500
623
  this._state = this.CONNECTING;
501
624
  var self = this;
502
625
 
626
+ this.info('Initiating handshake with ' + this._endpoint);
627
+
503
628
  this._transport.send({
504
629
  channel: Faye.Channel.HANDSHAKE,
505
630
  version: Faye.BAYEUX_VERSION,
@@ -508,6 +633,7 @@ Faye.Client = Faye.Class({
508
633
  }, function(response) {
509
634
 
510
635
  if (!response.successful) {
636
+ this.info('Handshake unsuccessful');
511
637
  setTimeout(function() { self.handshake(callback, scope) }, this._advice.interval);
512
638
  return this._state = this.UNCONNECTED;
513
639
  }
@@ -516,6 +642,7 @@ Faye.Client = Faye.Class({
516
642
  this._clientId = response.clientId;
517
643
  this._transport = Faye.Transport.get(this, response.supportedConnectionTypes);
518
644
 
645
+ this.info('Handshake successful: ' + this._clientId);
519
646
  if (callback) callback.call(scope);
520
647
  }, this);
521
648
  },
@@ -533,14 +660,17 @@ Faye.Client = Faye.Class({
533
660
  if (this._advice.reconnect === this.NONE) return;
534
661
  if (this._state === this.DISCONNECTED) return;
535
662
 
536
- if (this._advice.reconnect === this.HANDSHAKE || this._state === this.UNCONNECTED)
663
+ if (this._advice.reconnect === this.HANDSHAKE || this._state === this.UNCONNECTED) {
664
+ this._beginReconnectTimeout();
537
665
  return this.handshake(function() { this.connect(callback, scope) }, this);
666
+ }
538
667
 
539
668
  if (this._state === this.CONNECTING)
540
669
  return this.callback(callback, scope);
541
670
 
542
671
  if (this._state !== this.CONNECTED) return;
543
672
 
673
+ this.info('Calling deferred actions for ' + this._clientId);
544
674
  this.setDeferredStatus('succeeded');
545
675
  this.setDeferredStatus('deferred');
546
676
  if (callback) callback.call(scope);
@@ -548,6 +678,7 @@ Faye.Client = Faye.Class({
548
678
  if (this._connectionId) return;
549
679
  this._connectionId = this._namespace.generate();
550
680
  var self = this;
681
+ this.info('Initiating connection for ' + this._clientId);
551
682
 
552
683
  this._transport.send({
553
684
  channel: Faye.Channel.CONNECT,
@@ -555,10 +686,15 @@ Faye.Client = Faye.Class({
555
686
  connectionType: this._transport.connectionType,
556
687
  id: this._connectionId
557
688
 
558
- }, function(response) {
689
+ }, this._verifyClientId(function(response) {
559
690
  delete this._connectionId;
691
+ this.removeTimeout('reconnect');
692
+
693
+ this.info('Closed connection for ' + this._clientId);
560
694
  setTimeout(function() { self.connect() }, this._advice.interval);
561
- }, this);
695
+ }));
696
+
697
+ this._beginReconnectTimeout();
562
698
  },
563
699
 
564
700
  // Request Response
@@ -572,11 +708,14 @@ Faye.Client = Faye.Class({
572
708
  if (this._state !== this.CONNECTED) return;
573
709
  this._state = this.DISCONNECTED;
574
710
 
711
+ this.info('Disconnecting ' + this._clientId);
712
+
575
713
  this._transport.send({
576
714
  channel: Faye.Channel.DISCONNECT,
577
715
  clientId: this._clientId
578
716
  });
579
717
 
718
+ this.info('Clearing channel listeners for ' + this._clientId);
580
719
  this._channels = new Faye.Channel.Tree();
581
720
  },
582
721
 
@@ -596,19 +735,25 @@ Faye.Client = Faye.Class({
596
735
  channels = [].concat(channels);
597
736
  this._validateChannels(channels);
598
737
 
738
+ this.info('Client ' + this._clientId + ' attempting to subscribe to [' +
739
+ channels.join(', ') + ']');
740
+
599
741
  this._transport.send({
600
742
  channel: Faye.Channel.SUBSCRIBE,
601
743
  clientId: this._clientId,
602
744
  subscription: channels
603
745
 
604
- }, function(response) {
605
- if (!response.successful) return;
746
+ }, this._verifyClientId(function(response) {
747
+ if (!response.successful || !callback) return;
606
748
 
749
+ this.info('Subscription acknowledged for ' + this._clientId + ' to [' +
750
+ channels.join(', ') + ']');
751
+
607
752
  channels = [].concat(response.subscription);
608
753
  Faye.each(channels, function(channel) {
609
754
  this._channels.set(channel, [callback, scope]);
610
755
  }, this);
611
- }, this);
756
+ }));
612
757
 
613
758
  }, this);
614
759
  },
@@ -629,19 +774,25 @@ Faye.Client = Faye.Class({
629
774
  channels = [].concat(channels);
630
775
  this._validateChannels(channels);
631
776
 
777
+ this.info('Client ' + this._clientId + ' attempting to unsubscribe from [' +
778
+ channels.join(', ') + ']');
779
+
632
780
  this._transport.send({
633
781
  channel: Faye.Channel.UNSUBSCRIBE,
634
782
  clientId: this._clientId,
635
783
  subscription: channels
636
784
 
637
- }, function(response) {
785
+ }, this._verifyClientId(function(response) {
638
786
  if (!response.successful) return;
639
787
 
788
+ this.info('Unsubscription acknowledged for ' + this._clientId + ' from [' +
789
+ channels.join(', ') + ']');
790
+
640
791
  channels = [].concat(response.subscription);
641
792
  Faye.each(channels, function(channel) {
642
793
  this._channels.set(channel, null);
643
794
  }, this);
644
- }, this);
795
+ }));
645
796
 
646
797
  }, this);
647
798
  },
@@ -657,19 +808,16 @@ Faye.Client = Faye.Class({
657
808
 
658
809
  this._validateChannels([channel]);
659
810
 
811
+ this.info('Client ' + this._clientId + ' queueing published message to ' +
812
+ channel + ': ' + Faye.toJSON(data));
813
+
660
814
  this._enqueue({
661
815
  channel: channel,
662
816
  data: data,
663
817
  clientId: this._clientId
664
818
  });
665
819
 
666
- if (this._timeout) return;
667
- var self = this;
668
-
669
- this._timeout = setTimeout(function() {
670
- delete self._timeout;
671
- self._flush();
672
- }, this.MAX_DELAY * 1000);
820
+ this.addTimeout('publish', this.MAX_DELAY, this._flush, this);
673
821
 
674
822
  }, this);
675
823
  },
@@ -679,12 +827,30 @@ Faye.Client = Faye.Class({
679
827
  if (this._advice.reconnect === this.HANDSHAKE) this._clientId = null;
680
828
  },
681
829
 
682
- sendToSubscribers: function(message) {
683
- var channels = this._channels.glob(message.channel);
684
- Faye.each(channels, function(callback) {
685
- if (!callback) return;
686
- callback[0].call(callback[1], message.data);
687
- });
830
+ deliverMessages: function(messages) {
831
+ Faye.each(messages, function(message) {
832
+ this.info('Client ' + this._clientId + ' calling listeners for ' +
833
+ message.channel + ' with ' + Faye.toJSON(message.data));
834
+
835
+ var channels = this._channels.glob(message.channel);
836
+ Faye.each(channels, function(callback) {
837
+ if (!callback) return;
838
+ callback[0].call(callback[1], message.data);
839
+ });
840
+ }, this);
841
+ },
842
+
843
+ _beginReconnectTimeout: function() {
844
+ this.addTimeout('reconnect', this._timeout, function() {
845
+ delete this._connectionId;
846
+ delete this._clientId;
847
+ this._state = this.UNCONNECTED;
848
+
849
+ this.info('Server took >' + this._timeout + 's to reply to connection for ' +
850
+ this._clientId + ': attempting to reconnect');
851
+
852
+ this.subscribe(this._channels.getKeys());
853
+ }, this);
688
854
  },
689
855
 
690
856
  _enqueue: function(message) {
@@ -703,10 +869,21 @@ Faye.Client = Faye.Class({
703
869
  if (!Faye.Channel.isSubscribable(channel))
704
870
  throw 'Clients may not subscribe to channel "' + channel + '"';
705
871
  });
872
+ },
873
+
874
+ _verifyClientId: function(callback) {
875
+ var self = this;
876
+ return function(response) {
877
+ if (response.clientId !== self._clientId) return false;
878
+ callback.call(self, response);
879
+ return true;
880
+ };
706
881
  }
707
882
  });
708
883
 
709
884
  Faye.extend(Faye.Client.prototype, Faye.Deferrable);
885
+ Faye.extend(Faye.Client.prototype, Faye.Timeouts);
886
+ Faye.extend(Faye.Client.prototype, Faye.Logging);
710
887
 
711
888
 
712
889
  Faye.Set = Faye.Class({
@@ -757,6 +934,7 @@ Faye.Set = Faye.Class({
757
934
 
758
935
  Faye.Server = Faye.Class({
759
936
  initialize: function(options) {
937
+ this.info('New server created');
760
938
  this._options = options || {};
761
939
  this._channels = new Faye.Channel.Tree();
762
940
  this._clients = {};
@@ -770,6 +948,8 @@ Faye.Server = Faye.Class({
770
948
  },
771
949
 
772
950
  process: function(messages, local, callback) {
951
+ this.debug('Processing messages from ' + (local ? 'LOCAL' : 'REMOTE') + ' client');
952
+
773
953
  messages = [].concat(messages);
774
954
  var processed = 0, responses = [];
775
955
 
@@ -777,8 +957,9 @@ Faye.Server = Faye.Class({
777
957
  this._handle(message, local, function(reply) {
778
958
  responses = responses.concat(reply);
779
959
  processed += 1;
780
- if (processed === messages.length) callback(responses);
781
- });
960
+ if (processed < messages.length) return;
961
+ callback(responses);
962
+ }, this);
782
963
  }, this);
783
964
  },
784
965
 
@@ -803,42 +984,54 @@ Faye.Server = Faye.Class({
803
984
  delete this._clients[client.id];
804
985
  },
805
986
 
806
- _handle: function(message, local, callback) {
807
- var clientId = message.clientId,
808
- channel = message.channel,
987
+ _handle: function(message, local, callback, scope) {
988
+ var channel = message.channel,
809
989
  response;
810
990
 
811
991
  message.__id = Faye.random();
812
- Faye.each(this._channels.glob(channel), function(c) { c.push(message) });
992
+ Faye.each(this._channels.glob(channel), function(c) {
993
+ c.push(message);
994
+ this.info('Publishing message ' + Faye.toJSON(message.data) +
995
+ ' from client ' + clientId + ' to ' + c.name);
996
+ }, this);
813
997
 
814
998
  if (Faye.Channel.isMeta(channel)) {
815
999
  response = this[Faye.Channel.parse(channel)[1]](message, local);
816
1000
 
817
- clientId = clientId || response.clientId;
1001
+ var clientId = response.clientId;
818
1002
  response.advice = response.advice || {};
819
1003
  Faye.extend(response.advice, {
820
1004
  reconnect: this._clients.hasOwnProperty(clientId) ? 'retry' : 'handshake',
821
- interval: Faye.Connection.INTERVAL * 1000
1005
+ interval: Math.floor(Faye.Connection.prototype.INTERVAL * 1000)
822
1006
  }, false);
823
1007
 
824
- response.id = message.id;
825
-
826
1008
  if (response.channel !== Faye.Channel.CONNECT ||
827
1009
  response.successful !== true)
828
- return callback(response);
1010
+ return callback.call(scope, response);
829
1011
 
1012
+ this.info('Accepting connection from ' + response.clientId);
830
1013
  return this._connection(response.clientId).connect(function(events) {
1014
+ this.info('Sending event messages to ' + response.clientId);
1015
+ this.debug('Events for ' + response.clientId + ': ' + Faye.toJSON(events));
831
1016
  Faye.each(events, function(e) { delete e.__id });
832
- callback([response].concat(events));
833
- });
1017
+ callback.call(scope, [response].concat(events));
1018
+ }, this);
834
1019
  }
835
1020
 
836
1021
  if (!message.clientId || Faye.Channel.isService(channel))
837
1022
  return callback([]);
838
1023
 
839
- callback( { channel: channel,
840
- successful: true,
841
- id: message.id } );
1024
+ response = this._makeResponse(message);
1025
+ response.successful = true;
1026
+ callback(response);
1027
+ },
1028
+
1029
+ _makeResponse: function(message) {
1030
+ var response = {};
1031
+ Faye.each(['id', 'clientId', 'channel'], function(field) {
1032
+ if (message[field]) response[field] = message[field];
1033
+ });
1034
+ return response;
842
1035
  },
843
1036
 
844
1037
  // MUST contain * version
@@ -847,9 +1040,8 @@ Faye.Server = Faye.Class({
847
1040
  // * ext
848
1041
  // * id
849
1042
  handshake: function(message, local) {
850
- var response = { channel: Faye.Channel.HANDSHAKE,
851
- version: Faye.BAYEUX_VERSION,
852
- id: message.id };
1043
+ var response = this._makeResponse(message);
1044
+ response.version = Faye.BAYEUX_VERSION;
853
1045
 
854
1046
  if (!message.version)
855
1047
  response.error = Faye.Error.parameterMissing('version');
@@ -876,6 +1068,7 @@ Faye.Server = Faye.Class({
876
1068
 
877
1069
  var clientId = this._namespace.generate();
878
1070
  response.clientId = this._connection(clientId).id;
1071
+ this.info('Accepting handshake from client ' + response.clientId);
879
1072
  return response;
880
1073
  },
881
1074
 
@@ -884,8 +1077,7 @@ Faye.Server = Faye.Class({
884
1077
  // MAY contain * ext
885
1078
  // * id
886
1079
  connect: function(message, local) {
887
- var response = { channel: Faye.Channel.CONNECT,
888
- id: message.id };
1080
+ var response = this._makeResponse(message);
889
1081
 
890
1082
  var clientId = message.clientId,
891
1083
  client = clientId ? this._clients[clientId] : null,
@@ -896,6 +1088,7 @@ Faye.Server = Faye.Class({
896
1088
  if (!connectionType) response.error = Faye.Error.parameterMissing('connectionType');
897
1089
 
898
1090
  response.successful = !response.error;
1091
+ if (!response.successful) delete response.clientId;
899
1092
  if (!response.successful) return response;
900
1093
 
901
1094
  response.clientId = client.id;
@@ -906,8 +1099,7 @@ Faye.Server = Faye.Class({
906
1099
  // MAY contain * ext
907
1100
  // * id
908
1101
  disconnect: function(message, local) {
909
- var response = { channel: Faye.Channel.DISCONNECT,
910
- id: message.id };
1102
+ var response = this._makeResponse(message);
911
1103
 
912
1104
  var clientId = message.clientId,
913
1105
  client = clientId ? this._clients[clientId] : null;
@@ -916,10 +1108,12 @@ Faye.Server = Faye.Class({
916
1108
  if (!clientId) response.error = Faye.Error.parameterMissing('clientId');
917
1109
 
918
1110
  response.successful = !response.error;
1111
+ if (!response.successful) delete response.clientId;
919
1112
  if (!response.successful) return response;
920
1113
 
921
1114
  this._destroyClient(client);
922
1115
 
1116
+ this.info('Disconnected client: ' + clientId);
923
1117
  response.clientId = clientId;
924
1118
  return response;
925
1119
  },
@@ -929,9 +1123,7 @@ Faye.Server = Faye.Class({
929
1123
  // MAY contain * ext
930
1124
  // * id
931
1125
  subscribe: function(message, local) {
932
- var response = { channel: Faye.Channel.SUBSCRIBE,
933
- clientId: message.clientId,
934
- id: message.id };
1126
+ var response = this._makeResponse(message);
935
1127
 
936
1128
  var clientId = message.clientId,
937
1129
  client = clientId ? this._clients[clientId] : null,
@@ -952,6 +1144,8 @@ Faye.Server = Faye.Class({
952
1144
 
953
1145
  if (response.error) return;
954
1146
  channel = this._channels.findOrCreate(channel);
1147
+
1148
+ this.info('Subscribing client ' + clientId + ' to ' + channel.name);
955
1149
  client.subscribe(channel);
956
1150
  }, this);
957
1151
 
@@ -964,9 +1158,7 @@ Faye.Server = Faye.Class({
964
1158
  // MAY contain * ext
965
1159
  // * id
966
1160
  unsubscribe: function(message, local) {
967
- var response = { channel: Faye.Channel.UNSUBSCRIBE,
968
- clientId: message.clientId,
969
- id: message.id };
1161
+ var response = this._makeResponse(message);
970
1162
 
971
1163
  var clientId = message.clientId,
972
1164
  client = clientId ? this._clients[clientId] : null,
@@ -985,7 +1177,10 @@ Faye.Server = Faye.Class({
985
1177
  return response.error = Faye.Error.channelInvalid(channel);
986
1178
 
987
1179
  channel = this._channels.get(channel);
988
- if (channel) client.unsubscribe(channel);
1180
+ if (!channel) return;
1181
+
1182
+ this.info('Unsubscribing client ' + clientId + ' from ' + channel.name);
1183
+ client.unsubscribe(channel);
989
1184
  }, this);
990
1185
 
991
1186
  response.successful = !response.error;
@@ -993,6 +1188,8 @@ Faye.Server = Faye.Class({
993
1188
  }
994
1189
  });
995
1190
 
1191
+ Faye.extend(Faye.Server.prototype, Faye.Logging);
1192
+
996
1193
 
997
1194
  Faye.Connection = Faye.Class({
998
1195
  MAX_DELAY: 0.1,
@@ -1002,12 +1199,10 @@ Faye.Connection = Faye.Class({
1002
1199
  initialize: function(id, options) {
1003
1200
  this.id = id;
1004
1201
  this._options = options;
1202
+ this._timeout = this._options.timeout || this.TIMEOUT;
1005
1203
  this._channels = new Faye.Set();
1006
1204
  this._inbox = new Faye.Set();
1007
- },
1008
-
1009
- getTimeout: function() {
1010
- return this._options.timeout || this.TIMEOUT;
1205
+ this._connected = false
1011
1206
  },
1012
1207
 
1013
1208
  _onMessage: function(event) {
@@ -1027,16 +1222,12 @@ Faye.Connection = Faye.Class({
1027
1222
  channel.stopObserving('message', this._onMessage, this);
1028
1223
  },
1029
1224
 
1030
- connect: function(callback) {
1031
- this.callback(callback);
1225
+ connect: function(callback, scope) {
1226
+ this.callback(callback, scope);
1032
1227
  if (this._connected) return;
1033
1228
 
1034
1229
  this._connected = true;
1035
-
1036
- if (this._deletionTimeout) {
1037
- clearTimeout(this._deletionTimeout);
1038
- delete this._deletionTimeout;
1039
- }
1230
+ this.removeTimeout('deletion');
1040
1231
 
1041
1232
  this._beginDeliveryTimeout();
1042
1233
  this._beginConnectionTimeout();
@@ -1059,50 +1250,29 @@ Faye.Connection = Faye.Class({
1059
1250
  },
1060
1251
 
1061
1252
  _beginDeliveryTimeout: function() {
1062
- if (this._deliveryTimeout || !this._connected || this._inbox.isEmpty())
1063
- return;
1064
-
1065
- var self = this;
1066
- this._deliveryTimeout = setTimeout(function () { self.flush() },
1067
- this.MAX_DELAY * 1000);
1253
+ if (!this._connected || this._inbox.isEmpty()) return;
1254
+ this.addTimeout('delivery', this.MAX_DELAY, this.flush, this);
1068
1255
  },
1069
1256
 
1070
1257
  _beginConnectionTimeout: function() {
1071
- if (this._connectionTimeout || !this._connected)
1072
- return;
1073
-
1074
- var self = this;
1075
- this._connectionTimeout = setTimeout(function() { self.flush() },
1076
- this.getTimeout() * 1000);
1258
+ if (!this._connected) return;
1259
+ this.addTimeout('connection', this._timeout, this.flush, this);
1077
1260
  },
1078
1261
 
1079
1262
  _releaseConnection: function() {
1080
- if (this._connectionTimeout) {
1081
- clearTimeout(this._connectionTimeout);
1082
- delete this._connectionTimeout;
1083
- }
1084
-
1085
- if (this._deliveryTimeout) {
1086
- clearTimeout(this._deliveryTimeout);
1087
- delete this._deliveryTimeout;
1088
- }
1089
-
1263
+ this.removeTimeout('connection');
1264
+ this.removeTimeout('delivery');
1090
1265
  this._connected = false;
1091
- this._scheduleForDeletion();
1092
- },
1093
-
1094
- _scheduleForDeletion: function() {
1095
- if (this._deletionTimeout) return;
1096
- var self = this;
1097
1266
 
1098
- this._deletionTimeout = setTimeout(function() {
1099
- self.fire('staleClient', self);
1100
- }, 10 * 1000 * this.INTERVAL);
1267
+ this.addTimeout('deletion', 10 * this.INTERVAL, function() {
1268
+ this.fire('staleClient', this);
1269
+ }, this);
1101
1270
  }
1102
1271
  });
1103
1272
 
1104
1273
  Faye.extend(Faye.Connection.prototype, Faye.Deferrable);
1105
1274
  Faye.extend(Faye.Connection.prototype, Faye.Observable);
1275
+ Faye.extend(Faye.Connection.prototype, Faye.Timeouts);
1106
1276
 
1107
1277
 
1108
1278
  Faye.Error = Faye.Class({
@@ -1170,53 +1340,6 @@ Faye.Error.serverError = function() {
1170
1340
 
1171
1341
 
1172
1342
 
1173
- Faye.NodeHttpTransport = Faye.Class(Faye.Transport, {
1174
- request: function(message, callback, scope) {
1175
- var params = {message: JSON.stringify(message)},
1176
- request = this.createRequest();
1177
-
1178
- request.write(querystring.stringify(params));
1179
-
1180
- request.addListener('response', function(response) {
1181
- if (!callback) return;
1182
- response.addListener('data', function(chunk) {
1183
- callback.call(scope, JSON.parse(chunk));
1184
- });
1185
- });
1186
- request.close();
1187
- },
1188
-
1189
- createRequest: function() {
1190
- var uri = url.parse(this._endpoint),
1191
- client = http.createClient(uri.port, uri.hostname);
1192
-
1193
- return client.request('POST', uri.pathname, {
1194
- 'Content-Type': 'application/x-www-form-urlencoded'
1195
- });
1196
- }
1197
- });
1198
-
1199
- Faye.NodeHttpTransport.isUsable = function(endpoint) {
1200
- return typeof endpoint === 'string';
1201
- };
1202
-
1203
- Faye.Transport.register('long-polling', Faye.NodeHttpTransport);
1204
-
1205
- Faye.NodeLocalTransport = Faye.Class(Faye.Transport, {
1206
- request: function(message, callback, scope) {
1207
- this._endpoint.process(message, true, function(response) {
1208
- callback.call(scope, response);
1209
- });
1210
- }
1211
- });
1212
-
1213
- Faye.NodeLocalTransport.isUsable = function(endpoint) {
1214
- return endpoint instanceof Faye.Server;
1215
- };
1216
-
1217
- Faye.Transport.register('in-process', Faye.NodeLocalTransport);
1218
-
1219
-
1220
1343
  var path = require('path'),
1221
1344
  fs = require('fs'),
1222
1345
  sys = require('sys'),
@@ -1224,19 +1347,31 @@ var path = require('path'),
1224
1347
  http = require('http'),
1225
1348
  querystring = require('querystring');
1226
1349
 
1350
+ Faye.logger = function(message) {
1351
+ sys.puts(message);
1352
+ };
1353
+
1354
+ Faye.withDataFor = function(transport, callback, scope) {
1355
+ var data = '';
1356
+ transport.addListener('data', function(chunk) { data += chunk });
1357
+ transport.addListener('end', function() {
1358
+ callback.call(scope, data);
1359
+ });
1360
+ };
1361
+
1227
1362
  Faye.NodeAdapter = Faye.Class({
1228
1363
  DEFAULT_ENDPOINT: '/bayeux',
1229
1364
  SCRIPT_PATH: path.dirname(__filename) + '/faye-client-min.js',
1230
1365
 
1231
- TYPE_JSON: {'Content-Type': 'text/json'},
1366
+ TYPE_JSON: {'Content-Type': 'application/json'},
1232
1367
  TYPE_SCRIPT: {'Content-Type': 'text/javascript'},
1233
1368
  TYPE_TEXT: {'Content-Type': 'text/plain'},
1234
1369
 
1235
1370
  initialize: function(options) {
1236
- this._options = options || {};
1237
- this._endpoint = this._options.mount || this.DEFAULT_ENDPOINT;
1238
- this._script = this._endpoint + '.js';
1239
- this._server = new Faye.Server(this._options);
1371
+ this._options = options || {};
1372
+ this._endpoint = this._options.mount || this.DEFAULT_ENDPOINT;
1373
+ this._endpointRe = new RegExp('^' + this._endpoint + '(/[^/]+)*(\\.js)?$');
1374
+ this._server = new Faye.Server(this._options);
1240
1375
  },
1241
1376
 
1242
1377
  getClient: function() {
@@ -1252,35 +1387,30 @@ Faye.NodeAdapter = Faye.Class({
1252
1387
 
1253
1388
  call: function(request, response) {
1254
1389
  var requestUrl = url.parse(request.url, true),
1255
- self = this;
1390
+ self = this, data;
1391
+
1392
+ if (!this._endpointRe.test(requestUrl.pathname))
1393
+ return false;
1256
1394
 
1257
- switch (requestUrl.pathname) {
1395
+ if (/\.js$/.test(requestUrl.pathname)) {
1396
+ fs.readFile(this.SCRIPT_PATH, function(err, content) {
1397
+ response.sendHeader(200, self.TYPE_SCRIPT);
1398
+ response.write(content);
1399
+ response.close();
1400
+ });
1258
1401
 
1259
- case this._endpoint:
1260
- var isGet = (request.method === 'GET');
1261
-
1262
- if (isGet)
1263
- this._callWithParams(request, response, requestUrl.query);
1264
-
1265
- else
1266
- request.addListener('data', function(chunk) {
1267
- self._callWithParams(request, response, querystring.parse(chunk));
1268
- });
1269
-
1270
- return true;
1271
- break;
1402
+ } else {
1403
+ var isGet = (request.method === 'GET');
1272
1404
 
1273
- case this._script:
1274
- fs.readFile(this.SCRIPT_PATH, function(err, content) {
1275
- response.sendHeader(200, self.TYPE_SCRIPT);
1276
- response.write(content);
1277
- response.close();
1278
- });
1279
- return true;
1280
- break;
1405
+ if (isGet)
1406
+ this._callWithParams(request, response, requestUrl.query);
1281
1407
 
1282
- default: return false;
1408
+ else
1409
+ Faye.withDataFor(request, function(data) {
1410
+ self._callWithParams(request, response, {message: data});
1411
+ });
1283
1412
  }
1413
+ return true;
1284
1414
  },
1285
1415
 
1286
1416
  _callWithParams: function(request, response, params) {
@@ -1308,4 +1438,55 @@ Faye.NodeAdapter = Faye.Class({
1308
1438
  });
1309
1439
 
1310
1440
  exports.NodeAdapter = Faye.NodeAdapter;
1311
- exports.Client = Faye.Client;
1441
+ exports.Client = Faye.Client;
1442
+
1443
+
1444
+ Faye.NodeHttpTransport = Faye.Class(Faye.Transport, {
1445
+ request: function(message, callback, scope) {
1446
+ var request = this.createRequestForMessage(message);
1447
+
1448
+ request.addListener('response', function(response) {
1449
+ if (!callback) return;
1450
+ Faye.withDataFor(response, function(data) {
1451
+ callback.call(scope, JSON.parse(data));
1452
+ });
1453
+ });
1454
+ request.close();
1455
+ },
1456
+
1457
+ createRequestForMessage: function(message) {
1458
+ var content = JSON.stringify(message),
1459
+ uri = url.parse(this._endpoint),
1460
+ client = http.createClient(uri.port, uri.hostname);
1461
+
1462
+ if (parseInt(uri.port) === 443) client.setSecure('X509_PEM');
1463
+
1464
+ var request = client.request('POST', uri.pathname, {
1465
+ 'Content-Type': 'application/json',
1466
+ 'host': uri.hostname,
1467
+ 'Content-Length': content.length
1468
+ });
1469
+ request.write(content);
1470
+ return request;
1471
+ }
1472
+ });
1473
+
1474
+ Faye.NodeHttpTransport.isUsable = function(endpoint) {
1475
+ return typeof endpoint === 'string';
1476
+ };
1477
+
1478
+ Faye.Transport.register('long-polling', Faye.NodeHttpTransport);
1479
+
1480
+ Faye.NodeLocalTransport = Faye.Class(Faye.Transport, {
1481
+ request: function(message, callback, scope) {
1482
+ this._endpoint.process(message, true, function(response) {
1483
+ callback.call(scope, response);
1484
+ });
1485
+ }
1486
+ });
1487
+
1488
+ Faye.NodeLocalTransport.isUsable = function(endpoint) {
1489
+ return endpoint instanceof Faye.Server;
1490
+ };
1491
+
1492
+ Faye.Transport.register('in-process', Faye.NodeLocalTransport);