faye 0.6.2 → 0.6.3

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,16 @@
1
+ === 0.6.3 / 2011-07-10
2
+ TestSwarm build: http://swarm.jcoglan.com/job/69/
3
+
4
+ * Use sequential message IDs to reduce memory usage on the client side
5
+ * Only send advice with handshake and connect responses
6
+ * Stop trying to publish /meta/* messages - no-one is listening and it breaks /**
7
+ * Fix bug causing invalid listeners to appear after a client reconnection
8
+ * Stop loading 'rubygems' within our library code
9
+ * Make sure we only queue a message for each client once in the Redis engine
10
+ * Use lists instead of sets for message queues in Redis
11
+ * Improve clean-up of expired clients in Redis engine
12
+
13
+
1
14
  === 0.6.2 / 2011-06-19
2
15
 
3
16
  * Add authentication, database selection and namespacing to Redis engine
data/README.rdoc CHANGED
@@ -46,13 +46,12 @@ should get you up and running:
46
46
  bundle exec rspec -c spec/
47
47
  node spec/node.js
48
48
 
49
- # Build Ruby gem
49
+ # Install Ruby gem
50
50
  gem build faye.gemspec
51
- gem install faye-0.6.0.gem
51
+ gem install faye-x.x.x.gem
52
52
 
53
- # Build NPM package
54
- tar zcvf faye-0.6.0.tgz build/
55
- npm install ./faye-0.6.0.tgz
53
+ # Install NPM package
54
+ npm install build
56
55
 
57
56
 
58
57
  == To-do
@@ -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.6.2',BAYEUX_VERSION:'1.0',ID_LENGTH:128,JSONP_CALLBACK:'jsonpcallback',CONNECTION_TYPES:['long-polling','cross-origin-long-polling','callback-polling','websocket','in-process'],MANDATORY_CONNECTION_TYPES:['long-polling','callback-polling','in-process'],ENV:(function(){return this})(),random:function(a){a=a||this.ID_LENGTH;if(a>32){var b=Math.ceil(a/32),c='';while(b--)c+=this.random(32);return c}var d=Math.pow(2,a)-1,f=d.toString(36).length,c=Math.floor(Math.random()*d).toString(36);while(c.length<f)c='0'+c;return c},commonElement:function(a,b){for(var c=0,d=a.length;c<d;c++){if(this.indexOf(b,a[c])!==-1)return a[c]}return null},indexOf:function(a,b){for(var c=0,d=a.length;c<d;c++){if(a[c]===b)return c}return-1},each:function(a,b,c){if(a instanceof Array){for(var d=0,f=a.length;d<f;d++){if(a[d]!==undefined)b.call(c||null,a[d],d)}}else{for(var g in a){if(a.hasOwnProperty(g))b.call(c||null,g,a[g])}}},map:function(a,b,c){var d=[];this.each(a,function(){d.push(b.apply(c||null,arguments))});return d},filter:function(a,b,c){var d=[];this.each(a,function(){if(b.apply(c,arguments))d.push(arguments[0])});return d},size:function(a){var b=0;this.each(a,function(){b+=1});return b},enumEqual:function(c,d){if(d instanceof Array){if(!(c instanceof Array))return false;var f=c.length;if(f!==d.length)return false;while(f--){if(c[f]!==d[f])return false}return true}else{if(!(c instanceof Object))return false;if(this.size(d)!==this.size(c))return false;var g=true;this.each(c,function(a,b){g=g&&(d[a]===b)});return g}},asyncEach:function(a,b,c,d){var f=a.length,g=-1,h=0,i=false;var j=function(){h-=1;g+=1;if(g===f)return c&&c.call(d);b(a[g],m)};var k=function(){if(i)return;i=true;while(h>0)j();i=false};var m=function(){h+=1;k()};m()},toJSON:function(a){if(this.stringify)return this.stringify(a,function(key,value){return(this[key]instanceof Array)?this[key]:value});return JSON.stringify(a)},timestamp:function(){var b=new Date(),c=b.getFullYear(),d=b.getMonth()+1,f=b.getDate(),g=b.getHours(),h=b.getMinutes(),i=b.getSeconds();var j=function(a){return a<10?'0'+a:String(a)};return j(c)+'-'+j(d)+'-'+j(f)+' '+j(g)+':'+j(h)+':'+j(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.Namespace=Faye.Class({initialize:function(){this._c={}},exists:function(a){return this._c.hasOwnProperty(a)},generate:function(){var a=Faye.random();while(this._c.hasOwnProperty(a))a=Faye.random();return this._c[a]=a},release:function(a){delete this._c[a]}});Faye.Error=Faye.Class({initialize:function(a,b,c){this.code=a;this.params=Array.prototype.slice.call(b);this.message=c},toString:function(){return this.code+':'+this.params.join(',')+':'+this.message}});Faye.Error.parse=function(a){a=a||'';if(!Faye.Grammar.ERROR.test(a))return new this(null,[],a);var b=a.split(':'),c=parseInt(b[0]),d=b[1].split(','),a=b[2];return new this(c,d,a)};Faye.Error.versionMismatch=function(){return new this(300,arguments,"Version mismatch").toString()};Faye.Error.conntypeMismatch=function(){return new this(301,arguments,"Connection types not supported").toString()};Faye.Error.extMismatch=function(){return new this(302,arguments,"Extension mismatch").toString()};Faye.Error.badRequest=function(){return new this(400,arguments,"Bad request").toString()};Faye.Error.clientUnknown=function(){return new this(401,arguments,"Unknown client").toString()};Faye.Error.parameterMissing=function(){return new this(402,arguments,"Missing required parameter").toString()};Faye.Error.channelForbidden=function(){return new this(403,arguments,"Forbidden channel").toString()};Faye.Error.channelUnknown=function(){return new this(404,arguments,"Unknown channel").toString()};Faye.Error.channelInvalid=function(){return new this(405,arguments,"Invalid channel").toString()};Faye.Error.extUnknown=function(){return new this(406,arguments,"Unknown extension").toString()};Faye.Error.publishFailed=function(){return new this(407,arguments,"Failed to publish").toString()};Faye.Error.serverError=function(){return new this(500,arguments,"Internal server error").toString()};Faye.Deferrable={callback:function(a,b){if(!a)return;if(this._s==='succeeded')return a.apply(b,this._i);this._j=this._j||[];this._j.push([a,b])},errback:function(a,b){if(!a)return;if(this._s==='failed')return a.apply(b,this._i);this._k=this._k||[];this._k.push([a,b])},setDeferredStatus:function(){var a=Array.prototype.slice.call(arguments),b=a.shift(),c;this._s=b;this._i=a;if(b==='succeeded')c=this._j;else if(b==='failed')c=this._k;if(!c)return;var d;while(d=c.shift())d[0].apply(d[1],this._i)}};Faye.Publisher={countSubscribers:function(a){if(!this._3||!this._3[a])return 0;return this._3[a].length},addSubscriber:function(a,b,c){this._3=this._3||{};var d=this._3[a]=this._3[a]||[];d.push([b,c])},removeSubscriber:function(a,b,c){if(!this._3||!this._3[a])return;if(!b){delete this._3[a];return}var d=this._3[a],f=d.length;while(f--){if(b!==d[f][0])continue;if(c&&d[f][1]!==c)continue;d.splice(f,1)}},removeSubscribers:function(){this._3={}},publishEvent:function(){var b=Array.prototype.slice.call(arguments),c=b.shift();if(!this._3||!this._3[c])return;Faye.each(this._3[c],function(a){a[0].apply(a[1],b)})}};Faye.Timeouts={addTimeout:function(a,b,c,d){this._4=this._4||{};if(this._4.hasOwnProperty(a))return;var f=this;this._4[a]=Faye.ENV.setTimeout(function(){delete f._4[a];c.call(d)},1000*b)},removeTimeout:function(a){this._4=this._4||{};var b=this._4[a];if(!b)return;clearTimeout(b);delete this._4[a]}};Faye.Logging={LOG_LEVELS:{error:3,warn:2,info:1,debug:0},logLevel:'error',log:function(a,b){if(!Faye.logger)return;var c=Faye.Logging.LOG_LEVELS;if(c[Faye.Logging.logLevel]>c[b])return;var a=Array.prototype.slice.apply(a),d=' ['+b.toUpperCase()+'] [Faye',f=this.className,g=a.shift().replace(/\?/g,function(){try{return Faye.toJSON(a.shift())}catch(e){return'[Object]'}});for(var h in Faye){if(f)continue;if(typeof Faye[h]!=='function')continue;if(this instanceof Faye[h])f=h}if(f)d+='.'+f;d+='] ';Faye.logger(Faye.timestamp()+d+g)}};Faye.each(Faye.Logging.LOG_LEVELS,function(a,b){Faye.Logging[a]=function(){this.log(arguments,a)}});Faye.Grammar={LOWALPHA:/^[a-z]$/,UPALPHA:/^[A-Z]$/,ALPHA:/^([a-z]|[A-Z])$/,DIGIT:/^[0-9]$/,ALPHANUM:/^(([a-z]|[A-Z])|[0-9])$/,MARK:/^(\-|\_|\!|\~|\(|\)|\$|\@)$/,STRING:/^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*$/,TOKEN:/^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+$/,INTEGER:/^([0-9])+$/,CHANNEL_SEGMENT:/^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+$/,CHANNEL_SEGMENTS:/^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+(\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+)*$/,CHANNEL_NAME:/^\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+(\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+)*$/,WILD_CARD:/^\*{1,2}$/,CHANNEL_PATTERN:/^(\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+)*\/\*{1,2}$/,VERSION_ELEMENT:/^(([a-z]|[A-Z])|[0-9])(((([a-z]|[A-Z])|[0-9])|\-|\_))*$/,VERSION:/^([0-9])+(\.(([a-z]|[A-Z])|[0-9])(((([a-z]|[A-Z])|[0-9])|\-|\_))*)*$/,CLIENT_ID:/^((([a-z]|[A-Z])|[0-9]))+$/,ID:/^((([a-z]|[A-Z])|[0-9]))+$/,ERROR_MESSAGE:/^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*$/,ERROR_ARGS:/^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*(,(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*)*$/,ERROR_CODE:/^[0-9][0-9][0-9]$/,ERROR:/^([0-9][0-9][0-9]:(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*(,(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*)*:(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*|[0-9][0-9][0-9]::(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*)$/};Faye.Extensible={addExtension:function(a){this._5=this._5||[];this._5.push(a);if(a.added)a.added()},removeExtension:function(a){if(!this._5)return;var b=this._5.length;while(b--){if(this._5[b]!==a)continue;this._5.splice(b,1);if(a.removed)a.removed()}},pipeThroughExtensions:function(c,d,f,g){this.debug('Passing through ? extensions: ?',c,d);if(!this._5)return f.call(g,d);var h=this._5.slice();var i=function(a){if(!a)return f.call(g,a);var b=h.shift();if(!b)return f.call(g,a);if(b[c])b[c](a,i);else i(a)};i(d)}};Faye.extend(Faye.Extensible,Faye.Logging);Faye.Channel=Faye.Class({initialize:function(a){this.id=this.name=a},push:function(a){this.publishEvent('message',a)},isUnused:function(){return this.countSubscribers('message')===0}});Faye.extend(Faye.Channel.prototype,Faye.Publisher);Faye.extend(Faye.Channel,{HANDSHAKE:'/meta/handshake',CONNECT:'/meta/connect',SUBSCRIBE:'/meta/subscribe',UNSUBSCRIBE:'/meta/unsubscribe',DISCONNECT:'/meta/disconnect',META:'meta',SERVICE:'service',expand:function(a){var b=this.parse(a),c=['/**',a];var d=b.slice();d[d.length-1]='*';c.push(this.unparse(d));for(var f=1,g=b.length;f<g;f++){d=b.slice(0,f);d.push('**');c.push(this.unparse(d))}return c},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)},unparse:function(a){return'/'+a.join('/')},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)},Set:Faye.Class({initialize:function(){this._2={}},getKeys:function(){var c=[];Faye.each(this._2,function(a,b){c.push(a)});return c},remove:function(a){delete this._2[a]},hasSubscription:function(a){return this._2.hasOwnProperty(a)},subscribe:function(c,d,f){if(!d)return;Faye.each(c,function(a){var b=this._2[a]=this._2[a]||new Faye.Channel(a);b.addSubscriber('message',d,f)},this)},unsubscribe:function(a,b,c){var d=this._2[a];if(!d)return false;d.removeSubscriber('message',b,c);if(d.isUnused()){this.remove(a);return true}else{return false}},distributeMessage:function(c){var d=Faye.Channel.expand(c.channel);Faye.each(d,function(a){var b=this._2[a];if(b)b.publishEvent('message',c.data)},this)}})});Faye.Subscription=Faye.Class({initialize:function(a,b,c,d){this._8=a;this._2=b;this._l=c;this._m=d;this._t=false},cancel:function(){if(this._t)return;this._8.unsubscribe(this._2,this._l,this._m);this._t=true},unsubscribe:function(){this.cancel()}});Faye.extend(Faye.Subscription.prototype,Faye.Deferrable);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',INTERVAL:0.0,initialize:function(b,c){this.info('New client created for ?',b);this.endpoint=b||this.DEFAULT_ENDPOINT;this._u=c||{};Faye.Transport.get(this,Faye.MANDATORY_CONNECTION_TYPES,function(a){this._d=a},this);this._1=this.UNCONNECTED;this._2=new Faye.Channel.Set();this._y=new Faye.Namespace();this._n={};this._6={reconnect:this.RETRY,interval:1000*(this._u.interval||this.INTERVAL),timeout:1000*(this._u.timeout||this.CONNECTION_TIMEOUT)};if(Faye.Event)Faye.Event.on(Faye.ENV,'beforeunload',this.disconnect,this)},getClientId:function(){return this._0},getState:function(){switch(this._1){case this.UNCONNECTED:return'UNCONNECTED';case this.CONNECTING:return'CONNECTING';case this.CONNECTED:return'CONNECTED';case this.DISCONNECTED:return'DISCONNECTED'}},handshake:function(c,d){if(this._6.reconnect===this.NONE)return;if(this._1!==this.UNCONNECTED)return;this._1=this.CONNECTING;var f=this;this.info('Initiating handshake with ?',this.endpoint);this._9({channel:Faye.Channel.HANDSHAKE,version:Faye.BAYEUX_VERSION,supportedConnectionTypes:[this._d.connectionType]},function(b){if(b.successful){this._1=this.CONNECTED;this._0=b.clientId;Faye.Transport.get(this,b.supportedConnectionTypes,function(a){this._d=a},this);this.info('Handshake successful: ?',this._0);this.subscribe(this._2.getKeys(),true);if(c)c.call(d)}else{this.info('Handshake unsuccessful');Faye.ENV.setTimeout(function(){f.handshake(c,d)},this._6.interval);this._1=this.UNCONNECTED}},this)},connect:function(a,b){if(this._6.reconnect===this.NONE)return;if(this._1===this.DISCONNECTED)return;if(this._1===this.UNCONNECTED)return this.handshake(function(){this.connect(a,b)},this);this.callback(a,b);if(this._1!==this.CONNECTED)return;this.info('Calling deferred actions for ?',this._0);this.setDeferredStatus('succeeded');this.setDeferredStatus('deferred');if(this._o)return;this._o=true;this.info('Initiating connection for ?',this._0);this._9({channel:Faye.Channel.CONNECT,clientId:this._0,connectionType:this._d.connectionType},this._v,this)},disconnect:function(){if(this._1!==this.CONNECTED)return;this._1=this.DISCONNECTED;this.info('Disconnecting ?',this._0);this._9({channel:Faye.Channel.DISCONNECT,clientId:this._0});this.info('Clearing channel listeners for ?',this._0);this._2=new Faye.Channel.Set()},subscribe:function(c,d,f){if(c instanceof Array)return Faye.each(c,function(channel){this.subscribe(channel,d,f)},this);var g=new Faye.Subscription(this,c,d,f);var h=(d===true);if(!h&&this._2.hasSubscription(c)){this._2.subscribe([c],d,f);g.setDeferredStatus('succeeded');return g}this.connect(function(){this.info('Client ? attempting to subscribe to ?',this._0,c);this._9({channel:Faye.Channel.SUBSCRIBE,clientId:this._0,subscription:c},function(a){if(!a.successful)return g.setDeferredStatus('failed',Faye.Error.parse(a.error));var b=[].concat(a.subscription);this.info('Subscription acknowledged for ? to ?',this._0,b);this._2.subscribe(b,d,f);g.setDeferredStatus('succeeded')},this)},this);return g},unsubscribe:function(c,d,f){if(c instanceof Array)return Faye.each(c,function(channel){this.unsubscribe(channel,d,f)},this);var g=this._2.unsubscribe(c,d,f);if(!g)return;this.connect(function(){this.info('Client ? attempting to unsubscribe from ?',this._0,c);this._9({channel:Faye.Channel.UNSUBSCRIBE,clientId:this._0,subscription:c},function(a){if(!a.successful)return;var b=[].concat(a.subscription);this.info('Unsubscription acknowledged for ? from ?',this._0,b)},this)},this)},publish:function(a,b){if(!Faye.Grammar.CHANNEL_NAME.test(a))throw new Error("Cannot publish: '"+a+"' is not a valid channel name");this.connect(function(){this.info('Client ? queueing published message to ?: ?',this._0,a,b);this._9({channel:a,data:b,clientId:this._0})},this)},receiveMessage:function(c){this.pipeThroughExtensions('incoming',c,function(a){if(!a)return;if(a.advice)this._z(a.advice);var b=this._n[a.id];if(b){delete this._n[a.id];b[0].call(b[1],a)}this._A(a)},this)},_9:function(b,c,d){b.id=this._y.generate();if(c)this._n[b.id]=[c,d];this.pipeThroughExtensions('outgoing',b,function(a){if(!a)return;this._d.send(a,this._6.timeout/1000)},this)},_z:function(a){Faye.extend(this._6,a);if(this._6.reconnect===this.HANDSHAKE&&this._1!==this.DISCONNECTED){this._1=this.UNCONNECTED;this._0=null;this._v()}},_A:function(a){if(!a.channel||!a.data)return;this.info('Client ? calling listeners for ? with ?',this._0,a.channel,a.data);this._2.distributeMessage(a)},_B:function(){if(!this._o)return;this._o=null;this.info('Closed connection for ?',this._0)},_v:function(){this._B();var a=this;Faye.ENV.setTimeout(function(){a.connect()},this._6.interval)}});Faye.extend(Faye.Client.prototype,Faye.Deferrable);Faye.extend(Faye.Client.prototype,Faye.Logging);Faye.extend(Faye.Client.prototype,Faye.Extensible);Faye.Transport=Faye.extend(Faye.Class({MAX_DELAY:0.0,batching:true,initialize:function(a,b){this.debug('Created new ? transport for ?',this.connectionType,b);this._8=a;this._a=b;this._e=[]},send:function(a,b){this.debug('Client ? sending message to ?: ?',this._8._0,this._a,a);if(!this.batching)return this.request([a],b);this._e.push(a);this._7=b;if(a.channel===Faye.Channel.HANDSHAKE)return this.flush();if(a.channel===Faye.Channel.CONNECT)this._p=a;this.addTimeout('publish',this.MAX_DELAY,this.flush,this)},flush:function(){this.removeTimeout('publish');if(this._e.length>1&&this._p)this._p.advice={timeout:0};this.request(this._e,this._7);this._p=null;this._e=[]},receive:function(a){this.debug('Client ? received from ?: ?',this._8._0,this._a,a);Faye.each(a,this._8.receiveMessage,this._8)},retry:function(a,b){var c=this;return function(){Faye.ENV.setTimeout(function(){c.request(a,2*b)},1000*b)}}}),{get:function(g,h,i,j){var k=g.endpoint;if(h===undefined)h=this.supportedConnectionTypes();Faye.asyncEach(this._q,function(b,c){var d=b[0],f=b[1];if(Faye.indexOf(h,d)<0)return c();f.isUsable(k,function(a){if(a)i.call(j,new f(g,k));else c()})},function(){throw new Error('Could not find a usable connection type for '+k);})},register:function(a,b){this._q.push([a,b]);b.prototype.connectionType=a},_q:[],supportedConnectionTypes:function(){return Faye.map(this._q,function(a){return a[0]})}});Faye.extend(Faye.Transport.prototype,Faye.Logging);Faye.extend(Faye.Transport.prototype,Faye.Timeouts);Faye.Event={_f:[],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._f.push({_g:a,_r:b,_l:c,_m:d,_w:f})},detach:function(a,b,c,d){var f=this._f.length,g;while(f--){g=this._f[f];if((a&&a!==g._g)||(b&&b!==g._r)||(c&&c!==g._l)||(d&&d!==g._m))continue;if(g._g.removeEventListener)g._g.removeEventListener(g._r,g._w,false);else g._g.detachEvent('on'+g._r,g._w);this._f.splice(f,1);g=null}}};Faye.Event.on(Faye.ENV,'unload',Faye.Event.detach,Faye.Event);Faye.URI=Faye.extend(Faye.Class({queryString:function(){var c=[],d;Faye.each(this.params,function(a,b){c.push(encodeURIComponent(a)+'='+encodeURIComponent(b))});return c.join('&')},isLocal:function(){var a=Faye.URI.parse(Faye.ENV.location.href);var b=(a.hostname!==this.hostname)||(a.port!==this.port)||(a.protocol!==this.protocol);return!b},toURL:function(){var a=this.queryString();return this.protocol+this.hostname+':'+this.port+this.pathname+(a?'?'+a:'')}}),{parse:function(d,f){if(typeof d!=='string')return d;var g=new this();var h=function(b,c){d=d.replace(c,function(a){if(a)g[b]=a;return''})};h('protocol',/^https?\:\/+/);h('hostname',/^[^\/\:]+/);h('port',/^:[0-9]+/);Faye.extend(g,{protocol:'http://',hostname:Faye.ENV.location.hostname,port:Faye.ENV.location.port},false);if(!g.port)g.port=(g.protocol==='https://')?'443':'80';g.port=g.port.replace(/\D/g,'');var i=d.split('?'),j=i.shift(),k=i.join('?'),m=k?k.split('&'):[],o=m.length,l={};while(o--){i=m[o].split('=');l[decodeURIComponent(i[0]||'')]=decodeURIComponent(i[1]||'')}if(typeof f==='object')Faye.extend(l,f);g.pathname=j;g.params=l;return g}});if(!this.JSON){JSON={}}(function(){function k(a){return a<10?'0'+a:a}if(typeof Date.prototype.toJSON!=='function'){Date.prototype.toJSON=function(a){return this.getUTCFullYear()+'-'+k(this.getUTCMonth()+1)+'-'+k(this.getUTCDate())+'T'+k(this.getUTCHours())+':'+k(this.getUTCMinutes())+':'+k(this.getUTCSeconds())+'Z'};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(a){return this.valueOf()}}var m=/[\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,l,p,s={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'},n;function r(c){o.lastIndex=0;return o.test(c)?'"'+c.replace(o,function(a){var b=s[a];return typeof b==='string'?b:'\\u'+('0000'+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+c+'"'}function q(a,b){var c,d,f,g,h=l,i,j=b[a];if(j&&typeof j==='object'&&typeof j.toJSON==='function'){j=j.toJSON(a)}if(typeof n==='function'){j=n.call(b,a,j)}switch(typeof j){case'string':return r(j);case'number':return isFinite(j)?String(j):'null';case'boolean':case'null':return String(j);case'object':if(!j){return'null'}l+=p;i=[];if(Object.prototype.toString.apply(j)==='[object Array]'){g=j.length;for(c=0;c<g;c+=1){i[c]=q(c,j)||'null'}f=i.length===0?'[]':l?'[\n'+l+i.join(',\n'+l)+'\n'+h+']':'['+i.join(',')+']';l=h;return f}if(n&&typeof n==='object'){g=n.length;for(c=0;c<g;c+=1){d=n[c];if(typeof d==='string'){f=q(d,j);if(f){i.push(r(d)+(l?': ':':')+f)}}}}else{for(d in j){if(Object.hasOwnProperty.call(j,d)){f=q(d,j);if(f){i.push(r(d)+(l?': ':':')+f)}}}}f=i.length===0?'{}':l?'{\n'+l+i.join(',\n'+l)+'\n'+h+'}':'{'+i.join(',')+'}';l=h;return f}}Faye.stringify=function(a,b,c){var d;l='';p='';if(typeof c==='number'){for(d=0;d<c;d+=1){p+=' '}}else if(typeof c==='string'){p=c}n=b;if(b&&typeof b!=='function'&&(typeof b!=='object'||typeof b.length!=='number')){throw new Error('JSON.stringify');}return q('',{'':a})};if(typeof JSON.stringify!=='function'){JSON.stringify=Faye.stringify}if(typeof JSON.parse!=='function'){JSON.parse=function(g,h){var i;function j(a,b){var c,d,f=a[b];if(f&&typeof f==='object'){for(c in f){if(Object.hasOwnProperty.call(f,c)){d=j(f,c);if(d!==undefined){f[c]=d}else{delete f[c]}}}}return h.call(a,b,f)}m.lastIndex=0;if(m.test(g)){g=g.replace(m,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 h==='function'?j({'':i},''):i}throw new SyntaxError('JSON.parse');}}}());Faye.Transport.WebSocket=Faye.extend(Faye.Class(Faye.Transport,{UNCONNECTED:1,CONNECTING:2,CONNECTED:3,batching:false,request:function(b,c){this._7=this._7||c;this._h=this._h||{};Faye.each(b,function(a){this._h[a.id]=a},this);this.withSocket(function(a){a.send(Faye.toJSON(b))})},withSocket:function(a,b){this.callback(a,b);this.connect()},connect:function(){this._1=this._1||this.UNCONNECTED;if(this._1!==this.UNCONNECTED)return;this._1=this.CONNECTING;this._b=new WebSocket(Faye.Transport.WebSocket.getSocketUrl(this._a));var d=this;this._b.onopen=function(){delete d._7;d._1=d.CONNECTED;d.setDeferredStatus('succeeded',d._b)};this._b.onmessage=function(b){var c=[].concat(JSON.parse(b.data));Faye.each(c,function(a){delete d._h[a.id]});d.receive(c)};this._b.onclose=function(){var a=(d._1===d.CONNECTED);d.setDeferredStatus('deferred');d._1=d.UNCONNECTED;delete d._b;if(a)return d.resend();Faye.ENV.setTimeout(function(){d.connect()},1000*d._7);d._7=d._7*2}},resend:function(){var c=Faye.map(this._h,function(a,b){return b});this.request(c)}}),{WEBSOCKET_TIMEOUT:1000,getSocketUrl:function(a){return Faye.URI.parse(a).toURL().replace(/^http(s?):/ig,'ws$1:')},isUsable:function(a,b,c){if(!Faye.ENV.WebSocket)return b.call(c,false);var d=false,f=false,g=this.getSocketUrl(a),h=new WebSocket(g);h.onopen=function(){d=true;h.close();b.call(c,true);f=true;h=null};var i=function(){if(!f&&!d)b.call(c,false);f=true};h.onclose=h.onerror=i;Faye.ENV.setTimeout(i,this.WEBSOCKET_TIMEOUT)}});Faye.extend(Faye.Transport.WebSocket.prototype,Faye.Deferrable);Faye.Transport.register('websocket',Faye.Transport.WebSocket);Faye.Transport.XHR=Faye.extend(Faye.Class(Faye.Transport,{request:function(b,c){var d=this.retry(b,c),f=Faye.URI.parse(this._a).pathname,g=this,h=Faye.ENV.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest();h.open('POST',f,true);h.setRequestHeader('Content-Type','application/json');h.setRequestHeader('X-Requested-With','XMLHttpRequest');h.onreadystatechange=function(){if(h.readyState!==4)return;var a=h.status;try{if((a>=200&&a<300)||a===304||a===1223)g.receive(JSON.parse(h.responseText));else d()}catch(e){d()}finally{Faye.Event.detach(Faye.ENV,'beforeunload',i);h.onreadystatechange=function(){};h=null}};var i=function(){h.abort()};Faye.Event.on(Faye.ENV,'beforeunload',i);h.send(Faye.toJSON(b))}}),{isUsable:function(a,b,c){b.call(c,Faye.URI.parse(a).isLocal())}});Faye.Transport.register('long-polling',Faye.Transport.XHR);Faye.Transport.CORS=Faye.extend(Faye.Class(Faye.Transport,{request:function(a,b){var c=Faye.ENV.XDomainRequest?XDomainRequest:XMLHttpRequest,d=new c(),f=this.retry(a,b),g=this;d.open('POST',this._a,true);d.onload=function(){try{g.receive(JSON.parse(d.responseText))}catch(e){f()}finally{d.onload=d.onerror=null;d=null}};d.onerror=f;d.onprogress=function(){};d.send('message='+encodeURIComponent(Faye.toJSON(a)))}}),{isUsable:function(a,b,c){if(Faye.URI.parse(a).isLocal())return b.call(c,false);if(Faye.ENV.XDomainRequest)return b.call(c,true);if(Faye.ENV.XMLHttpRequest){var d=new Faye.ENV.XMLHttpRequest();return b.call(c,d.withCredentials!==undefined)}return b.call(c,false)}});Faye.Transport.register('cross-origin-long-polling',Faye.Transport.CORS);Faye.Transport.JSONP=Faye.extend(Faye.Class(Faye.Transport,{request:function(b,c){var d={message:Faye.toJSON(b)},f=document.getElementsByTagName('head')[0],g=document.createElement('script'),h=Faye.Transport.JSONP.getCallbackName(),i=Faye.URI.parse(this._a,d),j=this;var k=function(){if(!g.parentNode)return false;g.parentNode.removeChild(g);return true};Faye.ENV[h]=function(a){Faye.ENV[h]=undefined;try{delete Faye.ENV[h]}catch(e){}if(!k())return;j.receive(a)};Faye.ENV.setTimeout(function(){if(!Faye.ENV[h])return;k();j.request(b,2*c)},1000*c);i.params.jsonp=h;g.type='text/javascript';g.src=i.toURL();f.appendChild(g)}}),{_x:0,getCallbackName:function(){this._x+=1;return'__jsonp'+this._x+'__'},isUsable:function(a,b,c){b.call(c,true)}});Faye.Transport.register('callback-polling',Faye.Transport.JSONP);
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.6.3',BAYEUX_VERSION:'1.0',ID_LENGTH:128,JSONP_CALLBACK:'jsonpcallback',CONNECTION_TYPES:['long-polling','cross-origin-long-polling','callback-polling','websocket','in-process'],MANDATORY_CONNECTION_TYPES:['long-polling','callback-polling','in-process'],ENV:(function(){return this})(),random:function(a){a=a||this.ID_LENGTH;if(a>32){var b=Math.ceil(a/32),c='';while(b--)c+=this.random(32);return c}var d=Math.pow(2,a)-1,f=d.toString(36).length,c=Math.floor(Math.random()*d).toString(36);while(c.length<f)c='0'+c;return c},commonElement:function(a,b){for(var c=0,d=a.length;c<d;c++){if(this.indexOf(b,a[c])!==-1)return a[c]}return null},indexOf:function(a,b){for(var c=0,d=a.length;c<d;c++){if(a[c]===b)return c}return-1},each:function(a,b,c){if(a instanceof Array){for(var d=0,f=a.length;d<f;d++){if(a[d]!==undefined)b.call(c||null,a[d],d)}}else{for(var g in a){if(a.hasOwnProperty(g))b.call(c||null,g,a[g])}}},map:function(a,b,c){var d=[];this.each(a,function(){d.push(b.apply(c||null,arguments))});return d},filter:function(a,b,c){var d=[];this.each(a,function(){if(b.apply(c,arguments))d.push(arguments[0])});return d},size:function(a){var b=0;this.each(a,function(){b+=1});return b},enumEqual:function(c,d){if(d instanceof Array){if(!(c instanceof Array))return false;var f=c.length;if(f!==d.length)return false;while(f--){if(c[f]!==d[f])return false}return true}else{if(!(c instanceof Object))return false;if(this.size(d)!==this.size(c))return false;var g=true;this.each(c,function(a,b){g=g&&(d[a]===b)});return g}},asyncEach:function(a,b,c,d){var f=a.length,g=-1,h=0,i=false;var j=function(){h-=1;g+=1;if(g===f)return c&&c.call(d);b(a[g],m)};var k=function(){if(i)return;i=true;while(h>0)j();i=false};var m=function(){h+=1;k()};m()},toJSON:function(a){if(this.stringify)return this.stringify(a,function(key,value){return(this[key]instanceof Array)?this[key]:value});return JSON.stringify(a)},timestamp:function(){var b=new Date(),c=b.getFullYear(),d=b.getMonth()+1,f=b.getDate(),g=b.getHours(),h=b.getMinutes(),i=b.getSeconds();var j=function(a){return a<10?'0'+a:String(a)};return j(c)+'-'+j(d)+'-'+j(f)+' '+j(g)+':'+j(h)+':'+j(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.Namespace=Faye.Class({initialize:function(){this._c={}},exists:function(a){return this._c.hasOwnProperty(a)},generate:function(){var a=Faye.random();while(this._c.hasOwnProperty(a))a=Faye.random();return this._c[a]=a},release:function(a){delete this._c[a]}});Faye.Error=Faye.Class({initialize:function(a,b,c){this.code=a;this.params=Array.prototype.slice.call(b);this.message=c},toString:function(){return this.code+':'+this.params.join(',')+':'+this.message}});Faye.Error.parse=function(a){a=a||'';if(!Faye.Grammar.ERROR.test(a))return new this(null,[],a);var b=a.split(':'),c=parseInt(b[0]),d=b[1].split(','),a=b[2];return new this(c,d,a)};Faye.Error.versionMismatch=function(){return new this(300,arguments,"Version mismatch").toString()};Faye.Error.conntypeMismatch=function(){return new this(301,arguments,"Connection types not supported").toString()};Faye.Error.extMismatch=function(){return new this(302,arguments,"Extension mismatch").toString()};Faye.Error.badRequest=function(){return new this(400,arguments,"Bad request").toString()};Faye.Error.clientUnknown=function(){return new this(401,arguments,"Unknown client").toString()};Faye.Error.parameterMissing=function(){return new this(402,arguments,"Missing required parameter").toString()};Faye.Error.channelForbidden=function(){return new this(403,arguments,"Forbidden channel").toString()};Faye.Error.channelUnknown=function(){return new this(404,arguments,"Unknown channel").toString()};Faye.Error.channelInvalid=function(){return new this(405,arguments,"Invalid channel").toString()};Faye.Error.extUnknown=function(){return new this(406,arguments,"Unknown extension").toString()};Faye.Error.publishFailed=function(){return new this(407,arguments,"Failed to publish").toString()};Faye.Error.serverError=function(){return new this(500,arguments,"Internal server error").toString()};Faye.Deferrable={callback:function(a,b){if(!a)return;if(this._t==='succeeded')return a.apply(b,this._j);this._k=this._k||[];this._k.push([a,b])},errback:function(a,b){if(!a)return;if(this._t==='failed')return a.apply(b,this._j);this._l=this._l||[];this._l.push([a,b])},setDeferredStatus:function(){var a=Array.prototype.slice.call(arguments),b=a.shift(),c;this._t=b;this._j=a;if(b==='succeeded')c=this._k;else if(b==='failed')c=this._l;if(!c)return;var d;while(d=c.shift())d[0].apply(d[1],this._j)}};Faye.Publisher={countSubscribers:function(a){if(!this._3||!this._3[a])return 0;return this._3[a].length},addSubscriber:function(a,b,c){this._3=this._3||{};var d=this._3[a]=this._3[a]||[];d.push([b,c])},removeSubscriber:function(a,b,c){if(!this._3||!this._3[a])return;if(!b){delete this._3[a];return}var d=this._3[a],f=d.length;while(f--){if(b!==d[f][0])continue;if(c&&d[f][1]!==c)continue;d.splice(f,1)}},removeSubscribers:function(){this._3={}},publishEvent:function(){var b=Array.prototype.slice.call(arguments),c=b.shift();if(!this._3||!this._3[c])return;Faye.each(this._3[c],function(a){a[0].apply(a[1],b)})}};Faye.Timeouts={addTimeout:function(a,b,c,d){this._4=this._4||{};if(this._4.hasOwnProperty(a))return;var f=this;this._4[a]=Faye.ENV.setTimeout(function(){delete f._4[a];c.call(d)},1000*b)},removeTimeout:function(a){this._4=this._4||{};var b=this._4[a];if(!b)return;clearTimeout(b);delete this._4[a]}};Faye.Logging={LOG_LEVELS:{error:3,warn:2,info:1,debug:0},logLevel:'error',log:function(a,b){if(!Faye.logger)return;var c=Faye.Logging.LOG_LEVELS;if(c[Faye.Logging.logLevel]>c[b])return;var a=Array.prototype.slice.apply(a),d=' ['+b.toUpperCase()+'] [Faye',f=this.className,g=a.shift().replace(/\?/g,function(){try{return Faye.toJSON(a.shift())}catch(e){return'[Object]'}});for(var h in Faye){if(f)continue;if(typeof Faye[h]!=='function')continue;if(this instanceof Faye[h])f=h}if(f)d+='.'+f;d+='] ';Faye.logger(Faye.timestamp()+d+g)}};Faye.each(Faye.Logging.LOG_LEVELS,function(a,b){Faye.Logging[a]=function(){this.log(arguments,a)}});Faye.Grammar={LOWALPHA:/^[a-z]$/,UPALPHA:/^[A-Z]$/,ALPHA:/^([a-z]|[A-Z])$/,DIGIT:/^[0-9]$/,ALPHANUM:/^(([a-z]|[A-Z])|[0-9])$/,MARK:/^(\-|\_|\!|\~|\(|\)|\$|\@)$/,STRING:/^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*$/,TOKEN:/^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+$/,INTEGER:/^([0-9])+$/,CHANNEL_SEGMENT:/^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+$/,CHANNEL_SEGMENTS:/^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+(\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+)*$/,CHANNEL_NAME:/^\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+(\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+)*$/,WILD_CARD:/^\*{1,2}$/,CHANNEL_PATTERN:/^(\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+)*\/\*{1,2}$/,VERSION_ELEMENT:/^(([a-z]|[A-Z])|[0-9])(((([a-z]|[A-Z])|[0-9])|\-|\_))*$/,VERSION:/^([0-9])+(\.(([a-z]|[A-Z])|[0-9])(((([a-z]|[A-Z])|[0-9])|\-|\_))*)*$/,CLIENT_ID:/^((([a-z]|[A-Z])|[0-9]))+$/,ID:/^((([a-z]|[A-Z])|[0-9]))+$/,ERROR_MESSAGE:/^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*$/,ERROR_ARGS:/^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*(,(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*)*$/,ERROR_CODE:/^[0-9][0-9][0-9]$/,ERROR:/^([0-9][0-9][0-9]:(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*(,(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*)*:(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*|[0-9][0-9][0-9]::(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*)$/};Faye.Extensible={addExtension:function(a){this._5=this._5||[];this._5.push(a);if(a.added)a.added()},removeExtension:function(a){if(!this._5)return;var b=this._5.length;while(b--){if(this._5[b]!==a)continue;this._5.splice(b,1);if(a.removed)a.removed()}},pipeThroughExtensions:function(c,d,f,g){this.debug('Passing through ? extensions: ?',c,d);if(!this._5)return f.call(g,d);var h=this._5.slice();var i=function(a){if(!a)return f.call(g,a);var b=h.shift();if(!b)return f.call(g,a);if(b[c])b[c](a,i);else i(a)};i(d)}};Faye.extend(Faye.Extensible,Faye.Logging);Faye.Channel=Faye.Class({initialize:function(a){this.id=this.name=a},push:function(a){this.publishEvent('message',a)},isUnused:function(){return this.countSubscribers('message')===0}});Faye.extend(Faye.Channel.prototype,Faye.Publisher);Faye.extend(Faye.Channel,{HANDSHAKE:'/meta/handshake',CONNECT:'/meta/connect',SUBSCRIBE:'/meta/subscribe',UNSUBSCRIBE:'/meta/unsubscribe',DISCONNECT:'/meta/disconnect',META:'meta',SERVICE:'service',expand:function(a){var b=this.parse(a),c=['/**',a];var d=b.slice();d[d.length-1]='*';c.push(this.unparse(d));for(var f=1,g=b.length;f<g;f++){d=b.slice(0,f);d.push('**');c.push(this.unparse(d))}return c},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)},unparse:function(a){return'/'+a.join('/')},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)},Set:Faye.Class({initialize:function(){this._2={}},getKeys:function(){var c=[];Faye.each(this._2,function(a,b){c.push(a)});return c},remove:function(a){delete this._2[a]},hasSubscription:function(a){return this._2.hasOwnProperty(a)},subscribe:function(c,d,f){if(!d)return;Faye.each(c,function(a){var b=this._2[a]=this._2[a]||new Faye.Channel(a);b.addSubscriber('message',d,f)},this)},unsubscribe:function(a,b,c){var d=this._2[a];if(!d)return false;d.removeSubscriber('message',b,c);if(d.isUnused()){this.remove(a);return true}else{return false}},distributeMessage:function(c){var d=Faye.Channel.expand(c.channel);Faye.each(d,function(a){var b=this._2[a];if(b)b.publishEvent('message',c.data)},this)}})});Faye.Subscription=Faye.Class({initialize:function(a,b,c,d){this._8=a;this._2=b;this._m=c;this._n=d;this._u=false},cancel:function(){if(this._u)return;this._8.unsubscribe(this._2,this._m,this._n);this._u=true},unsubscribe:function(){this.cancel()}});Faye.extend(Faye.Subscription.prototype,Faye.Deferrable);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',INTERVAL:0.0,initialize:function(b,c){this.info('New client created for ?',b);this.endpoint=b||this.DEFAULT_ENDPOINT;this._v=c||{};Faye.Transport.get(this,Faye.MANDATORY_CONNECTION_TYPES,function(a){this._d=a},this);this._1=this.UNCONNECTED;this._2=new Faye.Channel.Set();this._e=0;this._o={};this._6={reconnect:this.RETRY,interval:1000*(this._v.interval||this.INTERVAL),timeout:1000*(this._v.timeout||this.CONNECTION_TIMEOUT)};if(Faye.Event)Faye.Event.on(Faye.ENV,'beforeunload',this.disconnect,this)},getClientId:function(){return this._0},getState:function(){switch(this._1){case this.UNCONNECTED:return'UNCONNECTED';case this.CONNECTING:return'CONNECTING';case this.CONNECTED:return'CONNECTED';case this.DISCONNECTED:return'DISCONNECTED'}},handshake:function(c,d){if(this._6.reconnect===this.NONE)return;if(this._1!==this.UNCONNECTED)return;this._1=this.CONNECTING;var f=this;this.info('Initiating handshake with ?',this.endpoint);this._9({channel:Faye.Channel.HANDSHAKE,version:Faye.BAYEUX_VERSION,supportedConnectionTypes:[this._d.connectionType]},function(b){if(b.successful){this._1=this.CONNECTED;this._0=b.clientId;Faye.Transport.get(this,b.supportedConnectionTypes,function(a){this._d=a},this);this.info('Handshake successful: ?',this._0);this.subscribe(this._2.getKeys(),true);if(c)c.call(d)}else{this.info('Handshake unsuccessful');Faye.ENV.setTimeout(function(){f.handshake(c,d)},this._6.interval);this._1=this.UNCONNECTED}},this)},connect:function(a,b){if(this._6.reconnect===this.NONE)return;if(this._1===this.DISCONNECTED)return;if(this._1===this.UNCONNECTED)return this.handshake(function(){this.connect(a,b)},this);this.callback(a,b);if(this._1!==this.CONNECTED)return;this.info('Calling deferred actions for ?',this._0);this.setDeferredStatus('succeeded');this.setDeferredStatus('deferred');if(this._p)return;this._p=true;this.info('Initiating connection for ?',this._0);this._9({channel:Faye.Channel.CONNECT,clientId:this._0,connectionType:this._d.connectionType},this._w,this)},disconnect:function(){if(this._1!==this.CONNECTED)return;this._1=this.DISCONNECTED;this.info('Disconnecting ?',this._0);this._9({channel:Faye.Channel.DISCONNECT,clientId:this._0});this.info('Clearing channel listeners for ?',this._0);this._2=new Faye.Channel.Set()},subscribe:function(c,d,f){if(c instanceof Array)return Faye.each(c,function(channel){this.subscribe(channel,d,f)},this);var g=new Faye.Subscription(this,c,d,f);var h=(d===true);if(!h&&this._2.hasSubscription(c)){this._2.subscribe([c],d,f);g.setDeferredStatus('succeeded');return g}this.connect(function(){this.info('Client ? attempting to subscribe to ?',this._0,c);this._9({channel:Faye.Channel.SUBSCRIBE,clientId:this._0,subscription:c},function(a){if(!a.successful)return g.setDeferredStatus('failed',Faye.Error.parse(a.error));var b=[].concat(a.subscription);this.info('Subscription acknowledged for ? to ?',this._0,b);if(!h)this._2.subscribe(b,d,f);g.setDeferredStatus('succeeded')},this)},this);return g},unsubscribe:function(c,d,f){if(c instanceof Array)return Faye.each(c,function(channel){this.unsubscribe(channel,d,f)},this);var g=this._2.unsubscribe(c,d,f);if(!g)return;this.connect(function(){this.info('Client ? attempting to unsubscribe from ?',this._0,c);this._9({channel:Faye.Channel.UNSUBSCRIBE,clientId:this._0,subscription:c},function(a){if(!a.successful)return;var b=[].concat(a.subscription);this.info('Unsubscription acknowledged for ? from ?',this._0,b)},this)},this)},publish:function(a,b){if(!Faye.Grammar.CHANNEL_NAME.test(a))throw new Error("Cannot publish: '"+a+"' is not a valid channel name");this.connect(function(){this.info('Client ? queueing published message to ?: ?',this._0,a,b);this._9({channel:a,data:b,clientId:this._0})},this)},receiveMessage:function(c){this.pipeThroughExtensions('incoming',c,function(a){if(!a)return;if(a.advice)this._z(a.advice);var b=this._o[a.id];if(b){delete this._o[a.id];b[0].call(b[1],a)}this._A(a)},this)},_9:function(b,c,d){b.id=this._B();if(c)this._o[b.id]=[c,d];this.pipeThroughExtensions('outgoing',b,function(a){if(!a)return;this._d.send(a,this._6.timeout/1000)},this)},_B:function(){this._e+=1;if(this._e>=Math.pow(2,32))this._e=0;return this._e.toString(36)},_z:function(a){Faye.extend(this._6,a);if(this._6.reconnect===this.HANDSHAKE&&this._1!==this.DISCONNECTED){this._1=this.UNCONNECTED;this._0=null;this._w()}},_A:function(a){if(!a.channel||!a.data)return;this.info('Client ? calling listeners for ? with ?',this._0,a.channel,a.data);this._2.distributeMessage(a)},_C:function(){if(!this._p)return;this._p=null;this.info('Closed connection for ?',this._0)},_w:function(){this._C();var a=this;Faye.ENV.setTimeout(function(){a.connect()},this._6.interval)}});Faye.extend(Faye.Client.prototype,Faye.Deferrable);Faye.extend(Faye.Client.prototype,Faye.Logging);Faye.extend(Faye.Client.prototype,Faye.Extensible);Faye.Transport=Faye.extend(Faye.Class({MAX_DELAY:0.0,batching:true,initialize:function(a,b){this.debug('Created new ? transport for ?',this.connectionType,b);this._8=a;this._a=b;this._f=[]},send:function(a,b){this.debug('Client ? sending message to ?: ?',this._8._0,this._a,a);if(!this.batching)return this.request([a],b);this._f.push(a);this._7=b;if(a.channel===Faye.Channel.HANDSHAKE)return this.flush();if(a.channel===Faye.Channel.CONNECT)this._q=a;this.addTimeout('publish',this.MAX_DELAY,this.flush,this)},flush:function(){this.removeTimeout('publish');if(this._f.length>1&&this._q)this._q.advice={timeout:0};this.request(this._f,this._7);this._q=null;this._f=[]},receive:function(a){this.debug('Client ? received from ?: ?',this._8._0,this._a,a);Faye.each(a,this._8.receiveMessage,this._8)},retry:function(a,b){var c=this;return function(){Faye.ENV.setTimeout(function(){c.request(a,2*b)},1000*b)}}}),{get:function(g,h,i,j){var k=g.endpoint;if(h===undefined)h=this.supportedConnectionTypes();Faye.asyncEach(this._r,function(b,c){var d=b[0],f=b[1];if(Faye.indexOf(h,d)<0)return c();f.isUsable(k,function(a){if(a)i.call(j,new f(g,k));else c()})},function(){throw new Error('Could not find a usable connection type for '+k);})},register:function(a,b){this._r.push([a,b]);b.prototype.connectionType=a},_r:[],supportedConnectionTypes:function(){return Faye.map(this._r,function(a){return a[0]})}});Faye.extend(Faye.Transport.prototype,Faye.Logging);Faye.extend(Faye.Transport.prototype,Faye.Timeouts);Faye.Event={_g:[],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._g.push({_h:a,_s:b,_m:c,_n:d,_x:f})},detach:function(a,b,c,d){var f=this._g.length,g;while(f--){g=this._g[f];if((a&&a!==g._h)||(b&&b!==g._s)||(c&&c!==g._m)||(d&&d!==g._n))continue;if(g._h.removeEventListener)g._h.removeEventListener(g._s,g._x,false);else g._h.detachEvent('on'+g._s,g._x);this._g.splice(f,1);g=null}}};Faye.Event.on(Faye.ENV,'unload',Faye.Event.detach,Faye.Event);Faye.URI=Faye.extend(Faye.Class({queryString:function(){var c=[],d;Faye.each(this.params,function(a,b){c.push(encodeURIComponent(a)+'='+encodeURIComponent(b))});return c.join('&')},isLocal:function(){var a=Faye.URI.parse(Faye.ENV.location.href);var b=(a.hostname!==this.hostname)||(a.port!==this.port)||(a.protocol!==this.protocol);return!b},toURL:function(){var a=this.queryString();return this.protocol+this.hostname+':'+this.port+this.pathname+(a?'?'+a:'')}}),{parse:function(d,f){if(typeof d!=='string')return d;var g=new this();var h=function(b,c){d=d.replace(c,function(a){if(a)g[b]=a;return''})};h('protocol',/^https?\:\/+/);h('hostname',/^[^\/\:]+/);h('port',/^:[0-9]+/);Faye.extend(g,{protocol:'http://',hostname:Faye.ENV.location.hostname,port:Faye.ENV.location.port},false);if(!g.port)g.port=(g.protocol==='https://')?'443':'80';g.port=g.port.replace(/\D/g,'');var i=d.split('?'),j=i.shift(),k=i.join('?'),m=k?k.split('&'):[],o=m.length,l={};while(o--){i=m[o].split('=');l[decodeURIComponent(i[0]||'')]=decodeURIComponent(i[1]||'')}if(typeof f==='object')Faye.extend(l,f);g.pathname=j;g.params=l;return g}});if(!this.JSON){JSON={}}(function(){function k(a){return a<10?'0'+a:a}if(typeof Date.prototype.toJSON!=='function'){Date.prototype.toJSON=function(a){return this.getUTCFullYear()+'-'+k(this.getUTCMonth()+1)+'-'+k(this.getUTCDate())+'T'+k(this.getUTCHours())+':'+k(this.getUTCMinutes())+':'+k(this.getUTCSeconds())+'Z'};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(a){return this.valueOf()}}var m=/[\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,l,p,s={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'},n;function r(c){o.lastIndex=0;return o.test(c)?'"'+c.replace(o,function(a){var b=s[a];return typeof b==='string'?b:'\\u'+('0000'+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+c+'"'}function q(a,b){var c,d,f,g,h=l,i,j=b[a];if(j&&typeof j==='object'&&typeof j.toJSON==='function'){j=j.toJSON(a)}if(typeof n==='function'){j=n.call(b,a,j)}switch(typeof j){case'string':return r(j);case'number':return isFinite(j)?String(j):'null';case'boolean':case'null':return String(j);case'object':if(!j){return'null'}l+=p;i=[];if(Object.prototype.toString.apply(j)==='[object Array]'){g=j.length;for(c=0;c<g;c+=1){i[c]=q(c,j)||'null'}f=i.length===0?'[]':l?'[\n'+l+i.join(',\n'+l)+'\n'+h+']':'['+i.join(',')+']';l=h;return f}if(n&&typeof n==='object'){g=n.length;for(c=0;c<g;c+=1){d=n[c];if(typeof d==='string'){f=q(d,j);if(f){i.push(r(d)+(l?': ':':')+f)}}}}else{for(d in j){if(Object.hasOwnProperty.call(j,d)){f=q(d,j);if(f){i.push(r(d)+(l?': ':':')+f)}}}}f=i.length===0?'{}':l?'{\n'+l+i.join(',\n'+l)+'\n'+h+'}':'{'+i.join(',')+'}';l=h;return f}}Faye.stringify=function(a,b,c){var d;l='';p='';if(typeof c==='number'){for(d=0;d<c;d+=1){p+=' '}}else if(typeof c==='string'){p=c}n=b;if(b&&typeof b!=='function'&&(typeof b!=='object'||typeof b.length!=='number')){throw new Error('JSON.stringify');}return q('',{'':a})};if(typeof JSON.stringify!=='function'){JSON.stringify=Faye.stringify}if(typeof JSON.parse!=='function'){JSON.parse=function(g,h){var i;function j(a,b){var c,d,f=a[b];if(f&&typeof f==='object'){for(c in f){if(Object.hasOwnProperty.call(f,c)){d=j(f,c);if(d!==undefined){f[c]=d}else{delete f[c]}}}}return h.call(a,b,f)}m.lastIndex=0;if(m.test(g)){g=g.replace(m,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 h==='function'?j({'':i},''):i}throw new SyntaxError('JSON.parse');}}}());Faye.Transport.WebSocket=Faye.extend(Faye.Class(Faye.Transport,{UNCONNECTED:1,CONNECTING:2,CONNECTED:3,batching:false,request:function(b,c){this._7=this._7||c;this._i=this._i||{};Faye.each(b,function(a){this._i[a.id]=a},this);this.withSocket(function(a){a.send(Faye.toJSON(b))})},withSocket:function(a,b){this.callback(a,b);this.connect()},connect:function(){this._1=this._1||this.UNCONNECTED;if(this._1!==this.UNCONNECTED)return;this._1=this.CONNECTING;this._b=new WebSocket(Faye.Transport.WebSocket.getSocketUrl(this._a));var d=this;this._b.onopen=function(){delete d._7;d._1=d.CONNECTED;d.setDeferredStatus('succeeded',d._b)};this._b.onmessage=function(b){var c=[].concat(JSON.parse(b.data));Faye.each(c,function(a){delete d._i[a.id]});d.receive(c)};this._b.onclose=function(){var a=(d._1===d.CONNECTED);d.setDeferredStatus('deferred');d._1=d.UNCONNECTED;delete d._b;if(a)return d.resend();Faye.ENV.setTimeout(function(){d.connect()},1000*d._7);d._7=d._7*2}},resend:function(){var c=Faye.map(this._i,function(a,b){return b});this.request(c)}}),{WEBSOCKET_TIMEOUT:1000,getSocketUrl:function(a){return Faye.URI.parse(a).toURL().replace(/^http(s?):/ig,'ws$1:')},isUsable:function(a,b,c){if(!Faye.ENV.WebSocket)return b.call(c,false);var d=false,f=false,g=this.getSocketUrl(a),h=new WebSocket(g);h.onopen=function(){d=true;h.close();b.call(c,true);f=true;h=null};var i=function(){if(!f&&!d)b.call(c,false);f=true};h.onclose=h.onerror=i;Faye.ENV.setTimeout(i,this.WEBSOCKET_TIMEOUT)}});Faye.extend(Faye.Transport.WebSocket.prototype,Faye.Deferrable);Faye.Transport.register('websocket',Faye.Transport.WebSocket);Faye.Transport.XHR=Faye.extend(Faye.Class(Faye.Transport,{request:function(b,c){var d=this.retry(b,c),f=Faye.URI.parse(this._a).pathname,g=this,h=Faye.ENV.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest();h.open('POST',f,true);h.setRequestHeader('Content-Type','application/json');h.setRequestHeader('X-Requested-With','XMLHttpRequest');h.onreadystatechange=function(){if(h.readyState!==4)return;var a=h.status;try{if((a>=200&&a<300)||a===304||a===1223)g.receive(JSON.parse(h.responseText));else d()}catch(e){d()}finally{Faye.Event.detach(Faye.ENV,'beforeunload',i);h.onreadystatechange=function(){};h=null}};var i=function(){h.abort()};Faye.Event.on(Faye.ENV,'beforeunload',i);h.send(Faye.toJSON(b))}}),{isUsable:function(a,b,c){b.call(c,Faye.URI.parse(a).isLocal())}});Faye.Transport.register('long-polling',Faye.Transport.XHR);Faye.Transport.CORS=Faye.extend(Faye.Class(Faye.Transport,{request:function(a,b){var c=Faye.ENV.XDomainRequest?XDomainRequest:XMLHttpRequest,d=new c(),f=this.retry(a,b),g=this;d.open('POST',this._a,true);d.onload=function(){try{g.receive(JSON.parse(d.responseText))}catch(e){f()}finally{d.onload=d.onerror=null;d=null}};d.onerror=f;d.onprogress=function(){};d.send('message='+encodeURIComponent(Faye.toJSON(a)))}}),{isUsable:function(a,b,c){if(Faye.URI.parse(a).isLocal())return b.call(c,false);if(Faye.ENV.XDomainRequest)return b.call(c,true);if(Faye.ENV.XMLHttpRequest){var d=new Faye.ENV.XMLHttpRequest();return b.call(c,d.withCredentials!==undefined)}return b.call(c,false)}});Faye.Transport.register('cross-origin-long-polling',Faye.Transport.CORS);Faye.Transport.JSONP=Faye.extend(Faye.Class(Faye.Transport,{request:function(b,c){var d={message:Faye.toJSON(b)},f=document.getElementsByTagName('head')[0],g=document.createElement('script'),h=Faye.Transport.JSONP.getCallbackName(),i=Faye.URI.parse(this._a,d),j=this;var k=function(){if(!g.parentNode)return false;g.parentNode.removeChild(g);return true};Faye.ENV[h]=function(a){Faye.ENV[h]=undefined;try{delete Faye.ENV[h]}catch(e){}if(!k())return;j.receive(a)};Faye.ENV.setTimeout(function(){if(!Faye.ENV[h])return;k();j.request(b,2*c)},1000*c);i.params.jsonp=h;g.type='text/javascript';g.src=i.toURL();f.appendChild(g)}}),{_y:0,getCallbackName:function(){this._y+=1;return'__jsonp'+this._y+'__'},isUsable:function(a,b,c){b.call(c,true)}});Faye.Transport.register('callback-polling',Faye.Transport.JSONP);
data/lib/faye.rb CHANGED
@@ -1,11 +1,10 @@
1
1
  require 'forwardable'
2
2
  require 'set'
3
- require 'rubygems'
4
3
  require 'eventmachine'
5
4
  require 'json'
6
5
 
7
6
  module Faye
8
- VERSION = '0.6.2'
7
+ VERSION = '0.6.3'
9
8
 
10
9
  ROOT = File.expand_path(File.dirname(__FILE__))
11
10
 
@@ -1,4 +1,3 @@
1
- require 'rubygems'
2
1
  require 'json'
3
2
  require 'rack'
4
3
  require 'thin'
@@ -18,7 +18,6 @@ module Faye
18
18
 
19
19
  class Base
20
20
  include Logging
21
- include Timeouts
22
21
 
23
22
  attr_reader :interval, :timeout
24
23
 
@@ -2,6 +2,8 @@ module Faye
2
2
  module Engine
3
3
 
4
4
  class Memory < Base
5
+ include Timeouts
6
+
5
7
  def initialize(options)
6
8
  @namespace = Namespace.new
7
9
  @clients = {}
@@ -36,11 +38,10 @@ module Faye
36
38
  end
37
39
 
38
40
  def ping(client_id)
39
- timeout = @options[:timeout]
40
- return unless Numeric === timeout
41
- debug 'Ping ?, ?', client_id, timeout
41
+ return unless Numeric === @timeout
42
+ debug 'Ping ?, ?', client_id, @timeout
42
43
  remove_timeout(client_id)
43
- add_timeout(client_id, 2 * timeout) { destroy_client(client_id) }
44
+ add_timeout(client_id, 2 * @timeout) { destroy_client(client_id) }
44
45
  end
45
46
 
46
47
  def subscribe(client_id, channel, &callback)
@@ -2,17 +2,21 @@ module Faye
2
2
  module Engine
3
3
 
4
4
  class Redis < Base
5
- DEFAULT_HOST = 'localhost'
6
- DEFAULT_PORT = 6379
5
+ DEFAULT_HOST = 'localhost'
6
+ DEFAULT_PORT = 6379
7
+ DEFAULT_DATABASE = 0
8
+ DEFAULT_GC = 60
9
+ LOCK_TIMEOUT = 120
7
10
 
8
11
  def init
9
12
  return if @redis
10
13
  require 'em-hiredis'
11
14
 
12
- host = @options[:host] || DEFAULT_HOST
13
- port = @options[:port] || DEFAULT_PORT
14
- db = @options[:database] || 0
15
+ host = @options[:host] || DEFAULT_HOST
16
+ port = @options[:port] || DEFAULT_PORT
17
+ db = @options[:database] || 0
15
18
  auth = @options[:password]
19
+ gc = @options[:gc] || DEFAULT_GC
16
20
  @ns = @options[:namespace] || ''
17
21
 
18
22
  @redis = EventMachine::Hiredis::Client.connect(host, port)
@@ -29,16 +33,19 @@ module Faye
29
33
  @subscriber.on(:message) do |topic, message|
30
34
  empty_queue(message) if topic == @ns + '/notifications'
31
35
  end
36
+
37
+ @gc = EventMachine.add_periodic_timer(gc, &method(:gc))
32
38
  end
33
39
 
34
40
  def disconnect
35
41
  @subscriber.unsubscribe(@ns + '/notifications')
42
+ EventMachine.cancel_timer(@gc)
36
43
  end
37
44
 
38
45
  def create_client(&callback)
39
46
  init
40
47
  client_id = Faye.random
41
- @redis.sadd(@ns + '/clients', client_id) do |added|
48
+ @redis.zadd(@ns + '/clients', 0, client_id) do |added|
42
49
  if added == 0
43
50
  create_client(&callback)
44
51
  else
@@ -51,12 +58,9 @@ module Faye
51
58
 
52
59
  def destroy_client(client_id, &callback)
53
60
  init
54
- @redis.srem(@ns + '/clients', client_id)
61
+ @redis.zrem(@ns + '/clients', client_id)
55
62
  @redis.del(@ns + "/clients/#{client_id}/messages")
56
63
 
57
- remove_timeout(client_id)
58
- @redis.del(@ns + "/clients/#{client_id}/ping")
59
-
60
64
  @redis.smembers(@ns + "/clients/#{client_id}/channels") do |channels|
61
65
  n, i = channels.size, 0
62
66
  if n == 0
@@ -78,25 +82,18 @@ module Faye
78
82
 
79
83
  def client_exists(client_id, &callback)
80
84
  init
81
- @redis.sismember(@ns + '/clients', client_id) do |exists|
82
- callback.call(exists != 0)
85
+ @redis.zscore(@ns + '/clients', client_id) do |score|
86
+ callback.call(score != nil)
83
87
  end
84
88
  end
85
89
 
86
90
  def ping(client_id)
87
- timeout = @options[:timeout]
88
- time = Time.now.to_i.to_s
89
-
90
- return unless Numeric === timeout
91
+ init
92
+ return unless Numeric === @timeout
91
93
 
92
- debug 'Ping ?, ?', client_id, timeout
93
- remove_timeout(client_id)
94
- @redis.set(@ns + "/clients/#{client_id}/ping", time)
95
- add_timeout(client_id, 2 * timeout) do
96
- @redis.get(@ns + "/clients/#{client_id}/ping") do |ping|
97
- destroy_client(client_id) if ping == time
98
- end
99
- end
94
+ time = Time.now.to_i
95
+ debug 'Ping ?, ?', client_id, time
96
+ @redis.zadd(@ns + '/clients', time, client_id)
100
97
  end
101
98
 
102
99
  def subscribe(client_id, channel, &callback)
@@ -120,15 +117,16 @@ module Faye
120
117
  def publish(message)
121
118
  init
122
119
  debug 'Publishing message ?', message
120
+
123
121
  json_message = JSON.dump(message)
124
- channels = Channel.expand(message['channel'])
125
- channels.each do |channel|
126
- @redis.smembers(@ns + "/channels#{channel}") do |clients|
127
- clients.each do |client_id|
128
- debug 'Queueing for client ?: ?', client_id, message
129
- @redis.sadd(@ns + "/clients/#{client_id}/messages", json_message)
130
- @redis.publish(@ns + '/notifications', client_id)
131
- end
122
+ channels = Channel.expand(message['channel'])
123
+ keys = channels.map { |c| @ns + "/channels#{c}" }
124
+
125
+ @redis.sunion(*keys) do |clients|
126
+ clients.each do |client_id|
127
+ debug 'Queueing for client ?: ?', client_id, message
128
+ @redis.rpush(@ns + "/clients/#{client_id}/messages", json_message)
129
+ @redis.publish(@ns + '/notifications', client_id)
132
130
  end
133
131
  end
134
132
  end
@@ -140,13 +138,58 @@ module Faye
140
138
  init
141
139
 
142
140
  key = @ns + "/clients/#{client_id}/messages"
143
- @redis.smembers(key) do |json_messages|
141
+ @redis.lrange(key, 0, -1) do |json_messages|
142
+ @redis.ltrim(key, json_messages.size, -1)
144
143
  json_messages.each do |json_message|
145
- @redis.srem(key, json_message)
146
144
  conn.deliver(JSON.parse(json_message))
147
145
  end
148
146
  end
149
147
  end
148
+
149
+ def gc
150
+ return unless Numeric === @timeout
151
+ with_lock 'gc' do |release_lock|
152
+ cutoff = Time.now.to_i - 2 * @timeout
153
+ @redis.zrangebyscore(@ns + '/clients', 0, cutoff) do |clients|
154
+ i, n = 0, clients.size
155
+ if i == n
156
+ release_lock.call
157
+ else
158
+ clients.each do |client_id|
159
+ destroy_client(client_id) do
160
+ i += 1
161
+ release_lock.call if i == n
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end
168
+
169
+ def with_lock(lock_name, &block)
170
+ lock_key = @ns + '/locks/' + lock_name
171
+ current_time = Time.now.to_i * 1000
172
+ expiry = current_time + LOCK_TIMEOUT * 1000 + 1
173
+
174
+ release_lock = lambda do
175
+ @redis.del(lock_key) if Time.now.to_i * 1000 < expiry
176
+ end
177
+
178
+ @redis.setnx(lock_key, expiry) do |set|
179
+ if set == 1
180
+ block.call(release_lock)
181
+ else
182
+ @redis.get(lock_key) do |timeout|
183
+ lock_timeout = timeout.to_i(10)
184
+ if lock_timeout < current_time
185
+ @redis.getset(lock_key, expiry) do |old_value|
186
+ block.call(release_lock) if old_value == timeout
187
+ end
188
+ end
189
+ end
190
+ end
191
+ end
192
+ end
150
193
  end
151
194
 
152
195
  register 'redis', Redis
@@ -21,14 +21,14 @@ module Faye
21
21
  def initialize(endpoint = nil, options = {})
22
22
  info('New client created for ?', endpoint)
23
23
 
24
- @endpoint = endpoint || RackAdapter::DEFAULT_ENDPOINT
25
- @options = options
26
-
27
- @transport = Transport.get(self, MANDATORY_CONNECTION_TYPES)
28
- @state = UNCONNECTED
29
- @channels = Channel::Set.new
24
+ @endpoint = endpoint || RackAdapter::DEFAULT_ENDPOINT
25
+ @options = options
26
+
27
+ @transport = Transport.get(self, MANDATORY_CONNECTION_TYPES)
28
+ @state = UNCONNECTED
29
+ @channels = Channel::Set.new
30
+ @message_id = 0
30
31
 
31
- @namespace = Namespace.new
32
32
  @response_callbacks = {}
33
33
 
34
34
  @advice = {
@@ -286,14 +286,20 @@ module Faye
286
286
  private
287
287
 
288
288
  def send(message, &callback)
289
- message['id'] = @namespace.generate
289
+ message['id'] = generate_message_id
290
290
  @response_callbacks[message['id']] = callback if callback
291
291
 
292
292
  pipe_through_extensions(:outgoing, message) do |message|
293
293
  @transport.send(message, @advice['timeout'] / 1000.0) if message
294
294
  end
295
295
  end
296
-
296
+
297
+ def generate_message_id
298
+ @message_id += 1
299
+ @message_id = 0 if @message_id >= 2**32
300
+ @message_id.to_s(36)
301
+ end
302
+
297
303
  def handle_advice(advice)
298
304
  @advice.update(advice)
299
305
 
@@ -73,16 +73,17 @@ module Faye
73
73
  info 'Handling message: ? (local: ?)', message, local
74
74
 
75
75
  channel_name = message['channel']
76
+
77
+ return handle_meta(message, local, &callback) if Channel.meta?(channel_name)
78
+
76
79
  @engine.publish(message) unless message['error'] or Grammar::CHANNEL_NAME !~ channel_name
77
80
 
78
- if Channel.meta?(channel_name)
79
- handle_meta(message, local, &callback)
80
- elsif message['clientId'].nil?
81
- callback.call([])
82
- else
81
+ if message['clientId']
83
82
  response = make_response(message)
84
83
  response['successful'] = !response['error']
85
84
  callback.call([response])
85
+ else
86
+ callback.call([])
86
87
  end
87
88
  end
88
89
 
@@ -97,6 +98,8 @@ module Faye
97
98
  end
98
99
 
99
100
  def advize(response)
101
+ return unless [Channel::HANDSHAKE, Channel::CONNECT].include?(response['channel'])
102
+
100
103
  advice = response['advice'] ||= {}
101
104
  if response['error']
102
105
  advice['reconnect'] ||= 'handshake'
@@ -5,6 +5,10 @@ module Faye
5
5
  endpoint.is_a?(Server)
6
6
  end
7
7
 
8
+ def batching?
9
+ false
10
+ end
11
+
8
12
  def request(message, timeout)
9
13
  @endpoint.process(message, true) { |responses| receive(responses) }
10
14
  end
data/spec/browser.html CHANGED
@@ -8,6 +8,14 @@
8
8
  <body>
9
9
  <script type="text/javascript">
10
10
 
11
+ if (typeof TestSwarm === 'undefined')
12
+ TestSwarm = {
13
+ submit: function(result) {
14
+ if (window.console) console.log(Faye.toJSON(result));
15
+ },
16
+ heartbeat: function() {}
17
+ }
18
+
11
19
  JS.Packages(function() { with(this) {
12
20
  file('../build/faye-browser-min.js').provides('Faye')
13
21
  autoload(/.*Spec/, {from: './javascript'})
data/spec/install.sh CHANGED
@@ -1,16 +1,21 @@
1
1
  # This script installs all the necessary software to run the Ruby and
2
- # Node versions of Faye. Tested on Ubuntu 10.04 LTS 64-bit EC2 image:
2
+ # Node versions of Faye, as well as the load testing tools AB and Tsung.
3
+
4
+ # Tested on Ubuntu 10.04 LTS 64-bit EC2 image:
3
5
  # http://uec-images.ubuntu.com/releases/10.04/release/
4
6
 
5
- FAYE_BRANCH=extract-engine
6
- NODE_VERSION=0.4.7
7
- REDIS_VERSION=2.2.7
7
+ FAYE_BRANCH=master
8
+ NODE_VERSION=0.4.8
9
+ PHANTOM_VERSION=1.2
10
+ REDIS_VERSION=2.2.11
8
11
  RUBY_VERSION=1.9.2
12
+ TSUNG_VERSION=1.3.3
9
13
 
10
14
  sudo apt-get update
11
- sudo apt-get install build-essential g++ git-core \
15
+ sudo apt-get install build-essential g++ git-core curl wget \
12
16
  openssl libcurl4-openssl-dev libreadline-dev \
13
- curl wget
17
+ apache2-utils erlang gnuplot \
18
+ libqt4-dev qt4-qmake xvfb
14
19
 
15
20
  bash < <(curl -s https://rvm.beginrescueend.com/install/rvm)
16
21
  echo "source \"\$HOME/.rvm/scripts/rvm\"" | tee -a ~/.bashrc
@@ -22,7 +27,7 @@ echo "install: --no-rdoc --no-ri
22
27
  update: --no-rdoc --no-ri" | tee ~/.gemrc
23
28
  gem install rake bundler
24
29
 
25
- cd
30
+ cd ~
26
31
  git clone git://github.com/creationix/nvm.git ~/.nvm
27
32
  . ~/.nvm/nvm.sh
28
33
  echo ". ~/.nvm/nvm.sh" | tee -a ~/.bashrc
@@ -38,7 +43,27 @@ sudo make
38
43
  sudo ln -s /usr/src/redis-$REDIS_VERSION/src/redis-server /usr/bin/redis-server
39
44
  sudo ln -s /usr/src/redis-$REDIS_VERSION/src/redis-cli /usr/bin/redis-cli
40
45
 
41
- cd
46
+ cd /usr/src
47
+ sudo git clone git://github.com/ariya/phantomjs.git
48
+ cd phantomjs
49
+ sudo git checkout $PHANTOM_VERSION
50
+ sudo qmake-qt4
51
+ sudo make
52
+ sudo ln -s /usr/src/phantomjs/bin/phantomjs /usr/bin/phantomjs
53
+ echo "To use phantomjs, run DISPLAY=:1 Xvfb :1 -screen 0 1024x768x16"
54
+
55
+ cd /usr/src
56
+ sudo wget http://tsung.erlang-projects.org/dist/tsung-$TSUNG_VERSION.tar.gz
57
+ sudo tar zxvf tsung-$TSUNG_VERSION.tar.gz
58
+ cd tsung-$TSUNG_VERSION
59
+ sudo ./configure
60
+ sudo make
61
+ sudo make install
62
+ sudo ln -s /usr/lib/tsung/bin/tsung_stats.pl /usr/bin/tsung-stats
63
+ echo "To use tsung-stats you need to 'install Template' from CPAN"
64
+ sudo perl -MCPAN -eshell
65
+
66
+ cd ~
42
67
  git clone git://github.com/jcoglan/faye.git
43
68
  cd faye
44
69
  git checkout $FAYE_BRANCH
@@ -158,11 +158,12 @@ JS.ENV.ClientSpec = JS.Test.describe("Client", function() { with(this) {
158
158
 
159
159
  client.receiveMessage({advice: {reconnect: "handshake"}})
160
160
 
161
- stubResponse({channel: "/meta/handshake",
162
- successful: true,
163
- version: "1.0",
161
+ stubResponse({channel: "/meta/handshake",
162
+ successful: true,
163
+ version: "1.0",
164
164
  supportedConnectionTypes: ["websocket"],
165
- clientId: "reconnectid" })
165
+ clientId: "reconnectid",
166
+ subscription: "/messages/foo" }) // tacked on to trigger subscribe() callback
166
167
  }})
167
168
 
168
169
  it("resends the subscriptions to the server", function() { with(this) {
@@ -135,7 +135,7 @@ JS.ENV.EngineSpec = JS.Test.describe("Pub/sub engines", function() { with(this)
135
135
  }})
136
136
 
137
137
  describe("ping", function() { with(this) {
138
- define("options", function() { return {timeout: 0.3} })
138
+ define("options", function() { return {timeout: 0.3, gc: 0.08} })
139
139
 
140
140
  it("removes a client if it does not ping often enough", function() { with(this) {
141
141
  clock_tick(700)
@@ -27,7 +27,7 @@ JS.ENV.Server.ExtensionsSpec = JS.Test.describe("Server extensions", function()
27
27
  stub(engine, "publish")
28
28
  var response = null
29
29
  server.process({channel: "/meta/handshake"}, false, function(r) { response = r })
30
- assertEqual( [{channel: "/foo", data: "hello", advice: anything()}], response )
30
+ assertEqual( [{channel: "/foo", data: "hello"}], response )
31
31
  }})
32
32
  }})
33
33
 
@@ -53,7 +53,7 @@ JS.ENV.Server.ExtensionsSpec = JS.Test.describe("Server extensions", function()
53
53
  stub(engine, "publish")
54
54
  var response = null
55
55
  server.process({channel: "/meta/handshake"}, false, function(r) { response = r })
56
- assertEqual( [{channel: "/foo", data: "hello", advice: anything(), ext: {auth: "password"}}], response )
56
+ assertEqual( [{channel: "/foo", data: "hello", ext: {auth: "password"}}], response )
57
57
  }})
58
58
  }})
59
59
  }})
@@ -32,7 +32,6 @@ JS.ENV.ServerSpec = JS.Test.describe("Server", function() { with(this) {
32
32
 
33
33
  it("routes single messages to appropriate handlers", function() { with(this) {
34
34
  expect(server, "handshake").given(handshake, false).yielding([{}])
35
- expect(engine, "publish").given(handshake)
36
35
  server.process(handshake, false, function() {})
37
36
  }})
38
37
 
@@ -43,11 +42,12 @@ JS.ENV.ServerSpec = JS.Test.describe("Server", function() { with(this) {
43
42
  expect(server, "subscribe").given(subscribe, false).yielding([{}])
44
43
  expect(server, "unsubscribe").given(unsubscribe, false).yielding([{}])
45
44
 
46
- expect(engine, "publish").given(handshake)
47
- expect(engine, "publish").given(connect)
48
- expect(engine, "publish").given(disconnect)
49
- expect(engine, "publish").given(subscribe)
50
- expect(engine, "publish").given(unsubscribe)
45
+ expect(engine, "publish").given(handshake).exactly(0)
46
+ expect(engine, "publish").given(connect).exactly(0)
47
+ expect(engine, "publish").given(disconnect).exactly(0)
48
+ expect(engine, "publish").given(subscribe).exactly(0)
49
+ expect(engine, "publish").given(unsubscribe).exactly(0)
50
+
51
51
  expect(engine, "publish").given(publish)
52
52
 
53
53
  server.process([handshake, connect, disconnect, subscribe, unsubscribe, publish], false, function() {})
@@ -59,7 +59,7 @@ JS.ENV.ServerSpec = JS.Test.describe("Server", function() { with(this) {
59
59
  server.process(publish, false, function() {})
60
60
  }})
61
61
 
62
- it("returns no respons", function() { with(this) {
62
+ it("returns no response", function() { with(this) {
63
63
  stub(engine, "publish")
64
64
  server.process(publish, false, function(response) {
65
65
  assertEqual( [], response)
@@ -98,14 +98,14 @@ JS.ENV.ServerSpec = JS.Test.describe("Server", function() { with(this) {
98
98
 
99
99
  describe("handshaking", function() { with(this) {
100
100
  before(function() { with(this) {
101
- expect(engine, "publish").given(handshake)
102
- expect(server, "handshake").given(handshake, false).yielding([{successful: true}])
101
+ expect(server, "handshake").given(handshake, false).yielding([{channel: "/meta/handshake", successful: true}])
103
102
  }})
104
103
 
105
104
  it("returns the handshake response with advice", function() { with(this) {
106
105
  server.process(handshake, false, function(response) {
107
106
  assertEqual([
108
- { successful: true,
107
+ { channel: "/meta/handshake",
108
+ successful: true,
109
109
  advice: {reconnect: "retry", interval: 0, timeout: 60000}
110
110
  }
111
111
  ], response)
@@ -116,7 +116,6 @@ JS.ENV.ServerSpec = JS.Test.describe("Server", function() { with(this) {
116
116
  describe("connecting for messages", function() { with(this) {
117
117
  before(function() { with(this) {
118
118
  this.messages = [{channel: "/a"}, {channel: "/b"}]
119
- expect(engine, "publish").given(connect)
120
119
  expect(server, "connect").given(connect, false).yielding([messages])
121
120
  }})
122
121
 
data/spec/phantom.js ADDED
@@ -0,0 +1,17 @@
1
+ // This script should be run with PhantomJS
2
+ // http://www.phantomjs.org/
3
+
4
+ var page = new WebPage()
5
+
6
+ page.onConsoleMessage = function(message) {
7
+ try {
8
+ var result = JSON.parse(message)
9
+ if ('total' in result && 'fail' in result) {
10
+ console.log(message)
11
+ var status = (!result.fail && !result.error) ? 0 : 1
12
+ phantom.exit(status)
13
+ }
14
+ } catch (e) {}
15
+ }
16
+
17
+ page.open('spec/browser.html')
@@ -10,7 +10,6 @@ describe "server extensions" do
10
10
 
11
11
  let(:server) { Faye::Server.new }
12
12
  let(:message) { {"channel" => "/foo", "data" => "hello"} }
13
- let(:advice) { {"reconnect"=>"retry", "interval"=>0, "timeout"=>60000} }
14
13
 
15
14
  before do
16
15
  Faye::Engine.stub(:get).and_return engine
@@ -37,7 +36,7 @@ describe "server extensions" do
37
36
  engine.stub(:publish)
38
37
  response = nil
39
38
  server.process({"channel" => "/meta/handshake"}, false) { |r| response = r }
40
- response.should == [{"channel" => "/foo", "data" => "hello", "advice" => advice}]
39
+ response.should == [{"channel" => "/foo", "data" => "hello"}]
41
40
  end
42
41
  end
43
42
 
@@ -62,7 +61,7 @@ describe "server extensions" do
62
61
  engine.stub(:publish)
63
62
  response = nil
64
63
  server.process({"channel" => "/meta/handshake"}, false) { |r| response = r }
65
- response.should == [{"channel" => "/foo", "data" => "hello", "advice" => advice, "ext" => {"auth" => "password"}}]
64
+ response.should == [{"channel" => "/foo", "data" => "hello", "ext" => {"auth" => "password"}}]
66
65
  end
67
66
  end
68
67
  end
@@ -35,7 +35,6 @@ describe Faye::Server do
35
35
 
36
36
  it "routes single messages to appropriate handlers" do
37
37
  server.should_receive(:handshake).with(handshake, false)
38
- engine.should_receive(:publish).with(handshake)
39
38
  server.process(handshake, false)
40
39
  end
41
40
 
@@ -46,11 +45,11 @@ describe Faye::Server do
46
45
  server.should_receive(:subscribe).with(subscribe, false)
47
46
  server.should_receive(:unsubscribe).with(unsubscribe, false)
48
47
 
49
- engine.should_receive(:publish).with(handshake)
50
- engine.should_receive(:publish).with(connect)
51
- engine.should_receive(:publish).with(disconnect)
52
- engine.should_receive(:publish).with(subscribe)
53
- engine.should_receive(:publish).with(unsubscribe)
48
+ engine.should_not_receive(:publish).with(handshake)
49
+ engine.should_not_receive(:publish).with(connect)
50
+ engine.should_not_receive(:publish).with(disconnect)
51
+ engine.should_not_receive(:publish).with(subscribe)
52
+ engine.should_not_receive(:publish).with(unsubscribe)
54
53
  engine.should_receive(:publish).with(publish)
55
54
 
56
55
  server.process([handshake, connect, disconnect, subscribe, unsubscribe, publish], false)
@@ -93,14 +92,15 @@ describe Faye::Server do
93
92
 
94
93
  describe "handshaking" do
95
94
  before do
96
- engine.should_receive(:publish).with(handshake)
97
- server.should_receive(:handshake).with(handshake, false).and_yield({"successful" => true})
95
+ response = {"channel" => "/meta/handshake", "successful" => true}
96
+ server.should_receive(:handshake).with(handshake, false).and_yield(response)
98
97
  end
99
98
 
100
99
  it "returns the handshake response with advice" do
101
100
  server.process(handshake, false) do |response|
102
101
  response.should == [
103
- { "successful" => true,
102
+ { "channel" => "/meta/handshake",
103
+ "successful" => true,
104
104
  "advice" => {"reconnect" => "retry", "interval" => 0, "timeout" => 60000}
105
105
  }
106
106
  ]
@@ -112,7 +112,6 @@ describe Faye::Server do
112
112
  let(:messages) { [{"channel" => "/a"}, {"channel" => "/b"}] }
113
113
 
114
114
  before do
115
- engine.should_receive(:publish).with(connect)
116
115
  server.should_receive(:connect).with(connect, false).and_yield(messages)
117
116
  end
118
117
 
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: faye
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.6.2
5
+ version: 0.6.3
6
6
  platform: ruby
7
7
  authors:
8
8
  - James Coglan
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2011-06-19 00:00:00 +01:00
13
+ date: 2011-07-10 00:00:00 +01:00
14
14
  default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
@@ -134,6 +134,39 @@ dependencies:
134
134
  version: "0"
135
135
  type: :development
136
136
  version_requirements: *id011
137
+ - !ruby/object:Gem::Dependency
138
+ name: RedCloth
139
+ prerelease: false
140
+ requirement: &id012 !ruby/object:Gem::Requirement
141
+ none: false
142
+ requirements:
143
+ - - ~>
144
+ - !ruby/object:Gem::Version
145
+ version: 3.0.0
146
+ type: :development
147
+ version_requirements: *id012
148
+ - !ruby/object:Gem::Dependency
149
+ name: sinatra
150
+ prerelease: false
151
+ requirement: &id013 !ruby/object:Gem::Requirement
152
+ none: false
153
+ requirements:
154
+ - - ">="
155
+ - !ruby/object:Gem::Version
156
+ version: "0"
157
+ type: :development
158
+ version_requirements: *id013
159
+ - !ruby/object:Gem::Dependency
160
+ name: staticmatic
161
+ prerelease: false
162
+ requirement: &id014 !ruby/object:Gem::Requirement
163
+ none: false
164
+ requirements:
165
+ - - ">="
166
+ - !ruby/object:Gem::Version
167
+ version: "0"
168
+ type: :development
169
+ version_requirements: *id014
137
170
  description:
138
171
  email: jcoglan@gmail.com
139
172
  executables: []
@@ -147,6 +180,7 @@ files:
147
180
  - README.rdoc
148
181
  - lib/faye-browser-min.js
149
182
  - spec/browser.html
183
+ - spec/phantom.js
150
184
  - spec/redis.conf
151
185
  - spec/testswarm.pl
152
186
  - spec/install.sh