faye 0.6.3 → 0.6.4

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,11 @@
1
+ === 0.6.4 / 2011-08-18
2
+ TestSwarm build: http://swarm.jcoglan.com/job/96/
3
+
4
+ * Support WebSocket protocol used by Chrome 14 and Firefox 6
5
+ * Fix handling of multibyte characters in WebSocket messages on Node
6
+ * Improve message routing in Node memory engine to avoid false duplicates
7
+
8
+
1
9
  === 0.6.3 / 2011-07-10
2
10
  TestSwarm build: http://swarm.jcoglan.com/job/69/
3
11
 
@@ -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.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);
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.4',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){if(a.map)return a.map(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;var d=Faye.ENV.WebSocket||Faye.ENV.MozWebSocket;this._b=new d(Faye.Transport.WebSocket.getSocketUrl(this._a));var f=this;this._b.onopen=function(){delete f._7;f._1=f.CONNECTED;f.setDeferredStatus('succeeded',f._b)};this._b.onmessage=function(b){var c=[].concat(JSON.parse(b.data));Faye.each(c,function(a){delete f._i[a.id]});f.receive(c)};this._b.onclose=function(){var a=(f._1===f.CONNECTED);f.setDeferredStatus('deferred');f._1=f.UNCONNECTED;delete f._b;if(a)return f.resend();Faye.ENV.setTimeout(function(){f.connect()},1000*f._7);f._7=f._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){var d=Faye.ENV.WebSocket||Faye.ENV.MozWebSocket;if(!d)return b.call(c,false);var f=false,g=false,h=this.getSocketUrl(a),i=new d(h);i.onopen=function(){f=true;i.close();b.call(c,true);g=true;i=null};var j=function(){if(!g&&!f)b.call(c,false);g=true};i.onclose=i.onerror=j;Faye.ENV.setTimeout(j,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);
@@ -4,7 +4,7 @@ require 'eventmachine'
4
4
  require 'json'
5
5
 
6
6
  module Faye
7
- VERSION = '0.6.3'
7
+ VERSION = '0.6.4'
8
8
 
9
9
  ROOT = File.expand_path(File.dirname(__FILE__))
10
10
 
@@ -59,7 +59,7 @@ module Faye
59
59
  end
60
60
 
61
61
  return handle_options(request) if env['REQUEST_METHOD'] == 'OPTIONS'
62
- return handle_upgrade(request) if env['HTTP_UPGRADE'] == 'WebSocket'
62
+ return handle_upgrade(request) if env['HTTP_UPGRADE'] =~ /^WebSocket$/i
63
63
  return [200, TYPE_SCRIPT, File.new(SCRIPT_PATH)] if request.path_info =~ /\.js$/
64
64
  handle_request(request)
65
65
  end
@@ -127,9 +127,9 @@ module Faye
127
127
  socket.onmessage = lambda do |message|
128
128
  begin
129
129
  message = JSON.parse(message.data)
130
- debug 'Received via WebSocket: ?', message
130
+ debug "Received via WebSocket[#{socket.version}]: ?", message
131
131
  @server.process(message, false) do |replies|
132
- debug 'Sending via WebSocket: ?', replies
132
+ debug "Sending via WebSocket[#{socket.version}]: ?", replies
133
133
  socket.send(JSON.unparse(replies))
134
134
  end
135
135
  rescue
@@ -72,15 +72,20 @@ module Faye
72
72
 
73
73
  def publish(message)
74
74
  debug 'Publishing message ?', message
75
+
75
76
  channels = Channel.expand(message['channel'])
77
+ clients = Set.new
78
+
76
79
  channels.each do |channel|
77
- next unless clients = @channels[channel]
78
- clients.each do |client_id|
79
- debug 'Queueing for client ?: ?', client_id, message
80
- @messages[client_id] ||= Set.new
81
- @messages[client_id].add(message)
82
- empty_queue(client_id)
83
- end
80
+ next unless subs = @channels[channel]
81
+ subs.each(&clients.method(:add))
82
+ end
83
+
84
+ clients.each do |client_id|
85
+ debug 'Queueing for client ?: ?', client_id, message
86
+ @messages[client_id] ||= []
87
+ @messages[client_id] << message
88
+ empty_queue(client_id)
84
89
  end
85
90
  end
86
91
 
@@ -4,6 +4,8 @@ module Faye
4
4
  attr_accessor :logger
5
5
  end
6
6
 
7
+ self.logger = method(:puts)
8
+
7
9
  module Logging
8
10
 
9
11
  DEFAULT_LOG_LEVEL = :error
@@ -1,6 +1,10 @@
1
1
  module Faye
2
2
  class WebSocket
3
3
 
4
+ autoload :Draft75Parser, File.expand_path('..', __FILE__) + '/web_socket/draft75_parser'
5
+ autoload :Draft76Parser, File.expand_path('..', __FILE__) + '/web_socket/draft76_parser'
6
+ autoload :Protocol8Parser, File.expand_path('..', __FILE__) + '/web_socket/protocol8_parser'
7
+
4
8
  include Publisher
5
9
 
6
10
  CONNECTING = 0
@@ -8,9 +12,22 @@ module Faye
8
12
  CLOSING = 2
9
13
  CLOSED = 3
10
14
 
11
- attr_reader :url, :ready_state, :buffered_amount
15
+ attr_reader :url, :ready_state
12
16
  attr_accessor :onopen, :onmessage, :onerror, :onclose
13
17
 
18
+ extend Forwardable
19
+ def_delegators :@parser, :version
20
+
21
+ def self.parser(request)
22
+ if request.env['HTTP_SEC_WEBSOCKET_VERSION']
23
+ Protocol8Parser
24
+ elsif request.env['HTTP_SEC_WEBSOCKET_KEY1']
25
+ Draft76Parser
26
+ else
27
+ Draft75Parser
28
+ end
29
+ end
30
+
14
31
  def initialize(request)
15
32
  @request = request
16
33
  @callback = @request.env['async.callback']
@@ -19,23 +36,25 @@ module Faye
19
36
 
20
37
  @url = @request.env['websocket.url']
21
38
  @ready_state = OPEN
22
- @buffered_amount = 0
23
39
 
24
40
  event = Event.new
25
41
  event.init_event('open', false, false)
26
42
  dispatch_event(event)
27
43
 
28
- @buffer = []
29
- @buffering = false
30
-
31
- @request.env[Thin::Request::WEBSOCKET_RECEIVE_CALLBACK] = lambda do |data|
32
- data.each_char(&method(:handle_char))
33
- end
44
+ @parser = WebSocket.parser(@request).new(self)
45
+ @request.env[Thin::Request::WEBSOCKET_RECEIVE_CALLBACK] = @parser.method(:parse)
34
46
  end
35
47
 
36
- def send(data)
37
- string = ["\x00", data, "\xFF"].map(&method(:encode)) * ''
38
- @stream.write(string)
48
+ def receive(data)
49
+ event = Event.new
50
+ event.init_event('message', false, false)
51
+ event.data = encode(data)
52
+ dispatch_event(event)
53
+ end
54
+
55
+ def send(data, type = nil, error_type = nil)
56
+ frame = @parser.frame(encode(data), type, error_type)
57
+ @stream.write(frame) if frame
39
58
  end
40
59
 
41
60
  def close
@@ -58,28 +77,6 @@ module Faye
58
77
  callback.call(event) if callback
59
78
  end
60
79
 
61
- private
62
-
63
- def handle_char(data)
64
- case data
65
- when "\x00" then
66
- @buffering = true
67
-
68
- when "\xFF" then
69
- event = Event.new
70
- event.init_event('message', false, false)
71
- event.data = encode(@buffer.join(''))
72
-
73
- dispatch_event(event)
74
-
75
- @buffer = []
76
- @buffering = false
77
-
78
- else
79
- @buffer.push(data) if @buffering
80
- end
81
- end
82
-
83
80
  def encode(string, encoding = 'UTF-8')
84
81
  return string unless string.respond_to?(:force_encoding)
85
82
  string.force_encoding(encoding)
@@ -0,0 +1,52 @@
1
+ module Faye
2
+ class WebSocket
3
+
4
+ class Draft75Parser
5
+ def self.handshake(request)
6
+ upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
7
+ upgrade << "Upgrade: WebSocket\r\n"
8
+ upgrade << "Connection: Upgrade\r\n"
9
+ upgrade << "WebSocket-Origin: #{request.env['HTTP_ORIGIN']}\r\n"
10
+ upgrade << "WebSocket-Location: #{request.websocket_url}\r\n\r\n"
11
+ upgrade
12
+ end
13
+
14
+ def initialize(web_socket)
15
+ @socket = web_socket
16
+ @buffer = []
17
+ @buffering = false
18
+ end
19
+
20
+ def version
21
+ 'draft-75'
22
+ end
23
+
24
+ def parse(data)
25
+ data.each_char(&method(:handle_char))
26
+ end
27
+
28
+ def frame(data, type = nil, error_type = nil)
29
+ "\x00#{ data }\xFF"
30
+ end
31
+
32
+ private
33
+
34
+ def handle_char(data)
35
+ case data
36
+ when "\x00" then
37
+ @buffering = true
38
+
39
+ when "\xFF" then
40
+ @socket.receive(@buffer.join(''))
41
+ @buffer = []
42
+ @buffering = false
43
+
44
+ else
45
+ @buffer.push(data) if @buffering
46
+ end
47
+ end
48
+ end
49
+
50
+ end
51
+ end
52
+
@@ -0,0 +1,54 @@
1
+ require 'digest/md5'
2
+
3
+ module Faye
4
+ class WebSocket
5
+
6
+ class Draft76Parser < Draft75Parser
7
+ class << self
8
+ def handshake(request)
9
+ key1 = request.env['HTTP_SEC_WEBSOCKET_KEY1']
10
+ value1 = number_from_key(key1) / spaces_in_key(key1)
11
+
12
+ key2 = request.env['HTTP_SEC_WEBSOCKET_KEY2']
13
+ value2 = number_from_key(key2) / spaces_in_key(key2)
14
+
15
+ hash = Digest::MD5.digest(big_endian(value1) +
16
+ big_endian(value2) +
17
+ request.body.read)
18
+
19
+ upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
20
+ upgrade << "Upgrade: WebSocket\r\n"
21
+ upgrade << "Connection: Upgrade\r\n"
22
+ upgrade << "Sec-WebSocket-Origin: #{request.env['HTTP_ORIGIN']}\r\n"
23
+ upgrade << "Sec-WebSocket-Location: #{request.websocket_url}\r\n\r\n"
24
+ upgrade << hash
25
+ upgrade
26
+ end
27
+
28
+ private
29
+
30
+ def number_from_key(key)
31
+ key.scan(/[0-9]/).join('').to_i(10)
32
+ end
33
+
34
+ def spaces_in_key(key)
35
+ key.scan(/ /).size
36
+ end
37
+
38
+ def big_endian(number)
39
+ string = ''
40
+ [24,16,8,0].each do |offset|
41
+ string << (number >> offset & 0xFF).chr
42
+ end
43
+ string
44
+ end
45
+ end
46
+
47
+ def version
48
+ 'draft-76'
49
+ end
50
+ end
51
+
52
+ end
53
+ end
54
+
@@ -0,0 +1,184 @@
1
+ require 'base64'
2
+ require 'digest/sha1'
3
+
4
+ module Faye
5
+ class WebSocket
6
+
7
+ class Protocol8Parser
8
+ GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
9
+
10
+ FIN = MASK = 0b10000000
11
+ RSV1 = 0b01000000
12
+ RSV2 = 0b00100000
13
+ RSV3 = 0b00010000
14
+ OPCODE = 0b00001111
15
+ LENGTH = 0b01111111
16
+
17
+ OPCODES = {
18
+ :continuation => 0,
19
+ :text => 1,
20
+ :binary => 2,
21
+ :close => 8,
22
+ :ping => 9,
23
+ :pong => 10
24
+ }
25
+
26
+ ERRORS = {
27
+ :normal_closure => 1000,
28
+ :going_away => 1001,
29
+ :protocol_error => 1002,
30
+ :unacceptable => 1003,
31
+ :too_large => 1004,
32
+ :encoding_error => 1007
33
+ }
34
+
35
+ def self.handshake(request)
36
+ sec_key = request.env['HTTP_SEC_WEBSOCKET_KEY']
37
+ return '' unless String === sec_key
38
+
39
+ accept = Base64.encode64(Digest::SHA1.digest(sec_key + GUID)).strip
40
+
41
+ upgrade = "HTTP/1.1 101 Switching Protocols\r\n"
42
+ upgrade << "Upgrade: websocket\r\n"
43
+ upgrade << "Connection: Upgrade\r\n"
44
+ upgrade << "Sec-WebSocket-Accept: #{accept}\r\n\r\n"
45
+ upgrade
46
+ end
47
+
48
+ def initialize(web_socket)
49
+ reset
50
+ @socket = web_socket
51
+ end
52
+
53
+ def version
54
+ 'protocol-8'
55
+ end
56
+
57
+ def parse(data)
58
+ byte0 = getbyte(data, 0)
59
+ final = (byte0 & FIN) == FIN
60
+ opcode = (byte0 & OPCODE)
61
+
62
+ return close(:protocol_error) unless OPCODES.values.include?(opcode)
63
+ reset unless opcode == OPCODES[:continuation]
64
+
65
+ byte1 = getbyte(data, 1)
66
+ masked = (byte1 & MASK) == MASK
67
+ length = (byte1 & LENGTH)
68
+ offset = 0
69
+
70
+ case length
71
+ when 126 then
72
+ length = integer(data, 2, 2)
73
+ offset = 2
74
+ when 127 then
75
+ length = integer(data, 2, 8)
76
+ offset = 8
77
+ end
78
+
79
+ if masked
80
+ payload_offset = 2 + offset + 4
81
+ mask_octets = (0..3).map { |i| getbyte(data, 2 + offset + i) }
82
+ else
83
+ payload_offset = 2 + offset
84
+ mask_octets = []
85
+ end
86
+
87
+ return close(:too_large) if getbyte(data, payload_offset + length)
88
+
89
+ raw_payload = data[payload_offset...(payload_offset + length)]
90
+ payload = unmask(raw_payload, mask_octets)
91
+
92
+ case opcode
93
+ when OPCODES[:continuation] then
94
+ return unless @mode == :text
95
+ @buffer << payload
96
+ if final
97
+ message = @buffer * ''
98
+ reset
99
+ @socket.receive(message)
100
+ end
101
+
102
+ when OPCODES[:text] then
103
+ if final
104
+ @socket.receive(payload)
105
+ else
106
+ @mode = :text
107
+ @buffer << payload
108
+ end
109
+
110
+ when OPCODES[:binary] then
111
+ close(:unacceptable)
112
+
113
+ when OPCODES[:close] then
114
+ close(:normal_closure)
115
+
116
+ when OPCODES[:ping] then
117
+ @socket.send(payload, :pong)
118
+ end
119
+ end
120
+
121
+ def frame(data, type = nil, error_type = nil)
122
+ return nil if @closed
123
+
124
+ if error_type
125
+ data = [ERRORS[error_type]].pack('n') + data
126
+ end
127
+
128
+ opcode = OPCODES[type || :text]
129
+ frame = (FIN | opcode).chr
130
+ length = data.size
131
+
132
+ case length
133
+ when 0..125 then
134
+ frame << length.chr
135
+ when 126..65535 then
136
+ frame << 126.chr
137
+ frame << [length].pack('n')
138
+ else
139
+ frame << 127.chr
140
+ frame << [length >> 32, length & 0xFFFFFFFF].pack('NN')
141
+ end
142
+
143
+ @socket.encode(frame, 'UTF-8') +
144
+ @socket.encode(data, 'UTF-8')
145
+ end
146
+
147
+ private
148
+
149
+ def reset
150
+ @buffer = []
151
+ @mode = nil
152
+ end
153
+
154
+ def close(error_type)
155
+ return if @closed
156
+ @socket.send('', :close, error_type)
157
+ @closed = true
158
+ end
159
+
160
+ def getbyte(data, offset)
161
+ data.respond_to?(:getbyte) ? data.getbyte(offset) : data[offset]
162
+ end
163
+
164
+ def integer(data, offset, length)
165
+ number = 0
166
+ (0...length).each do |i|
167
+ number += getbyte(data, offset + i) << (8 * (length - 1 - i))
168
+ end
169
+ number
170
+ end
171
+
172
+ def unmask(payload, mask_octets)
173
+ return payload unless mask_octets.size > 0
174
+ unmasked = ''
175
+ (0...payload.size).each do |i|
176
+ unmasked << (getbyte(payload, i) ^ mask_octets[i % 4]).chr
177
+ end
178
+ unmasked
179
+ end
180
+ end
181
+
182
+ end
183
+ end
184
+
@@ -2,7 +2,7 @@
2
2
  # Based on code from the Cramp project
3
3
  # http://github.com/lifo/cramp
4
4
 
5
- # Copyright (c) 2009-2010 Pratik Naik
5
+ # Copyright (c) 2009-2011 Pratik Naik
6
6
  #
7
7
  # Permission is hereby granted, free of charge, to any person obtaining
8
8
  # a copy of this software and associated documentation files (the
@@ -23,8 +23,6 @@
23
23
  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
24
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
25
 
26
- require 'digest/md5'
27
-
28
26
  class Thin::Connection
29
27
  def receive_data(data)
30
28
  trace { data }
@@ -55,7 +53,9 @@ class Thin::Request
55
53
  WEBSOCKET_RECEIVE_CALLBACK = 'websocket.receive_callback'.freeze
56
54
 
57
55
  def websocket?
58
- @env['HTTP_CONNECTION'] == 'Upgrade' && @env['HTTP_UPGRADE'] == 'WebSocket'
56
+ @env['HTTP_CONNECTION'] and
57
+ @env['HTTP_CONNECTION'].split(/\s*,\s*/).include?('Upgrade') and
58
+ ['WebSocket', 'websocket'].include?(@env['HTTP_UPGRADE'])
59
59
  end
60
60
 
61
61
  def secure_websocket?
@@ -68,73 +68,12 @@ class Thin::Request
68
68
 
69
69
  def websocket_url
70
70
  scheme = secure_websocket? ? 'wss:' : 'ws:'
71
- @env['websocket.url'] = "#{ scheme }//#{ @env['HTTP_HOST'] }#{ @env['REQUEST_PATH'] }"
71
+ @env['websocket.url'] = "#{ scheme }//#{ @env['HTTP_HOST'] }#{ @env['REQUEST_URI'] }"
72
72
  end
73
73
 
74
74
  def websocket_upgrade_data
75
- handler = if @env['HTTP_SEC_WEBSOCKET_KEY1'] and @env['HTTP_SEC_WEBSOCKET_KEY2']
76
- Protocol76
77
- else
78
- Protocol75
79
- end
80
- handler.new(self).handshake
81
- end
82
-
83
- class WebSocketHandler
84
- def initialize(request)
85
- @request = request
86
- end
87
- end
88
-
89
- class Protocol75 < WebSocketHandler
90
- def handshake
91
- upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
92
- upgrade << "Upgrade: WebSocket\r\n"
93
- upgrade << "Connection: Upgrade\r\n"
94
- upgrade << "WebSocket-Origin: #{@request.env['HTTP_ORIGIN']}\r\n"
95
- upgrade << "WebSocket-Location: #{@request.websocket_url}\r\n\r\n"
96
- upgrade
97
- end
98
- end
99
-
100
- class Protocol76 < WebSocketHandler
101
- def handshake
102
- key1 = @request.env['HTTP_SEC_WEBSOCKET_KEY1']
103
- value1 = number_from_key(key1) / spaces_in_key(key1)
104
-
105
- key2 = @request.env['HTTP_SEC_WEBSOCKET_KEY2']
106
- value2 = number_from_key(key2) / spaces_in_key(key2)
107
-
108
- hash = Digest::MD5.digest(big_endian(value1) +
109
- big_endian(value2) +
110
- @request.body.read)
111
-
112
- upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
113
- upgrade << "Upgrade: WebSocket\r\n"
114
- upgrade << "Connection: Upgrade\r\n"
115
- upgrade << "Sec-WebSocket-Origin: #{@request.env['HTTP_ORIGIN']}\r\n"
116
- upgrade << "Sec-WebSocket-Location: #{@request.websocket_url}\r\n\r\n"
117
- upgrade << hash
118
- upgrade
119
- end
120
-
121
- private
122
-
123
- def number_from_key(key)
124
- key.scan(/[0-9]/).join('').to_i(10)
125
- end
126
-
127
- def spaces_in_key(key)
128
- key.scan(/ /).size
129
- end
130
-
131
- def big_endian(number)
132
- string = ''
133
- [24,16,8,0].each do |offset|
134
- string << (number >> offset & 0xFF).chr
135
- end
136
- string
137
- end
75
+ parser = Faye::WebSocket.parser(self)
76
+ parser.handshake(self)
138
77
  end
139
78
  end
140
79
 
@@ -5,9 +5,9 @@
5
5
  # http://uec-images.ubuntu.com/releases/10.04/release/
6
6
 
7
7
  FAYE_BRANCH=master
8
- NODE_VERSION=0.4.8
8
+ NODE_VERSION=0.4.10
9
9
  PHANTOM_VERSION=1.2
10
- REDIS_VERSION=2.2.11
10
+ REDIS_VERSION=2.2.12
11
11
  RUBY_VERSION=1.9.2
12
12
  TSUNG_VERSION=1.3.3
13
13
 
@@ -17,24 +17,25 @@ sudo apt-get install build-essential g++ git-core curl wget \
17
17
  apache2-utils erlang gnuplot \
18
18
  libqt4-dev qt4-qmake xvfb
19
19
 
20
+ # Install RVM and Ruby
20
21
  bash < <(curl -s https://rvm.beginrescueend.com/install/rvm)
21
22
  echo "source \"\$HOME/.rvm/scripts/rvm\"" | tee -a ~/.bashrc
22
23
  source ~/.rvm/scripts/rvm
23
- rvm install 1.9.2
24
- rvm --default use 1.9.2
25
-
24
+ rvm install $RUBY_VERSION
25
+ rvm --default use $RUBY_VERSION
26
26
  echo "install: --no-rdoc --no-ri
27
27
  update: --no-rdoc --no-ri" | tee ~/.gemrc
28
28
  gem install rake bundler
29
29
 
30
+ # Install nvm and Node
30
31
  cd ~
31
32
  git clone git://github.com/creationix/nvm.git ~/.nvm
32
33
  . ~/.nvm/nvm.sh
33
34
  echo ". ~/.nvm/nvm.sh" | tee -a ~/.bashrc
34
35
  nvm install v$NODE_VERSION
35
36
  nvm use v$NODE_VERSION
36
- npm install redis
37
37
 
38
+ # Install Redis from source
38
39
  cd /usr/src
39
40
  sudo wget http://redis.googlecode.com/files/redis-$REDIS_VERSION.tar.gz
40
41
  sudo tar zxvf redis-$REDIS_VERSION.tar.gz
@@ -43,6 +44,7 @@ sudo make
43
44
  sudo ln -s /usr/src/redis-$REDIS_VERSION/src/redis-server /usr/bin/redis-server
44
45
  sudo ln -s /usr/src/redis-$REDIS_VERSION/src/redis-cli /usr/bin/redis-cli
45
46
 
47
+ # Install PhantomJS
46
48
  cd /usr/src
47
49
  sudo git clone git://github.com/ariya/phantomjs.git
48
50
  cd phantomjs
@@ -52,6 +54,7 @@ sudo make
52
54
  sudo ln -s /usr/src/phantomjs/bin/phantomjs /usr/bin/phantomjs
53
55
  echo "To use phantomjs, run DISPLAY=:1 Xvfb :1 -screen 0 1024x768x16"
54
56
 
57
+ # Install Tsung and required Perl modules
55
58
  cd /usr/src
56
59
  sudo wget http://tsung.erlang-projects.org/dist/tsung-$TSUNG_VERSION.tar.gz
57
60
  sudo tar zxvf tsung-$TSUNG_VERSION.tar.gz
@@ -63,11 +66,13 @@ sudo ln -s /usr/lib/tsung/bin/tsung_stats.pl /usr/bin/tsung-stats
63
66
  echo "To use tsung-stats you need to 'install Template' from CPAN"
64
67
  sudo perl -MCPAN -eshell
65
68
 
69
+ # Check out and build Faye project
66
70
  cd ~
67
71
  git clone git://github.com/jcoglan/faye.git
68
72
  cd faye
69
73
  git checkout $FAYE_BRANCH
70
74
  git submodule update --init --recursive
71
75
  bundle install
76
+ npm install redis
72
77
  cd vendor/js.class && jake
73
78
  cd ../.. && jake
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/ruby1.8
2
+
3
+ require 'rubygems'
4
+ require 'testswarm/client'
5
+
6
+ client = TestSwarm::Client.new('http://swarm.jcoglan.com')
7
+ project = client.project('faye', :auth => '6d26e250d81b32099fccc59db53a3a0e648f0e6d')
8
+
9
+ job = TestSwarm::Job.create(
10
+ :rcs => {
11
+ :type => "git",
12
+ :url => "git://github.com/jcoglan/faye.git"
13
+ },
14
+ :directory => "/home/jcoglan/www/swarm.jcoglan.com/app/changeset/#{project.name}",
15
+ :diff => ["javascript", "spec/javascript"],
16
+ :inject => "spec/browser.html",
17
+
18
+ :build => [
19
+ "cd vendor/js.class && jake",
20
+ "rm -rf build",
21
+ "jake"
22
+ ]
23
+ )
24
+
25
+ exit unless job.new?
26
+
27
+ path = "#{client.url}/changeset/#{project.name}/#{job.revision}"
28
+ job.add_suite "Faye tests", "#{path}/spec/browser.html"
29
+
30
+ project.submit_job "Faye Commit #{job.revision}", job,
31
+ :browsers => "all",
32
+ :max => 5
33
+
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: faye
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.6.3
5
+ version: 0.6.4
6
6
  platform: ruby
7
7
  authors:
8
8
  - James Coglan
@@ -10,8 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2011-07-10 00:00:00 +01:00
14
- default_executable:
13
+ date: 2011-08-17 00:00:00 Z
15
14
  dependencies:
16
15
  - !ruby/object:Gem::Dependency
17
16
  name: eventmachine
@@ -182,7 +181,6 @@ files:
182
181
  - spec/browser.html
183
182
  - spec/phantom.js
184
183
  - spec/redis.conf
185
- - spec/testswarm.pl
186
184
  - spec/install.sh
187
185
  - spec/thin_proxy.rb
188
186
  - spec/ruby/server/handshake_spec.rb
@@ -214,9 +212,13 @@ files:
214
212
  - spec/javascript/node_adapter_spec.js
215
213
  - spec/javascript/client_spec.js
216
214
  - spec/javascript/grammar_spec.js
215
+ - spec/testswarm
217
216
  - spec/spec_helper.rb
218
217
  - lib/faye/error.rb
219
218
  - lib/faye/util/namespace.rb
219
+ - lib/faye/util/web_socket/draft75_parser.rb
220
+ - lib/faye/util/web_socket/protocol8_parser.rb
221
+ - lib/faye/util/web_socket/draft76_parser.rb
220
222
  - lib/faye/util/web_socket.rb
221
223
  - lib/faye/mixins/logging.rb
222
224
  - lib/faye/mixins/publisher.rb
@@ -237,7 +239,6 @@ files:
237
239
  - lib/faye/engines/base.rb
238
240
  - lib/thin_extensions.rb
239
241
  - lib/faye.rb
240
- has_rdoc: true
241
242
  homepage: http://faye.jcoglan.com
242
243
  licenses: []
243
244
 
@@ -262,7 +263,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
262
263
  requirements: []
263
264
 
264
265
  rubyforge_project:
265
- rubygems_version: 1.6.2
266
+ rubygems_version: 1.8.5
266
267
  signing_key:
267
268
  specification_version: 3
268
269
  summary: Simple pub/sub messaging for the web
@@ -1,200 +0,0 @@
1
- #!/usr/bin/perl
2
-
3
- # CONFIGURE
4
-
5
- # The location of the TestSwarm that you're going to run against.
6
-
7
- my $SWARM = "http://swarm.jcoglan.com";
8
- my $SWARM_INJECT = "/js/inject.js";
9
-
10
- # Your TestSwarm username.
11
-
12
- my $USER = "faye";
13
-
14
- # Your authorization token.
15
-
16
- my $AUTH_TOKEN = "6d26e250d81b32099fccc59db53a3a0e648f0e6d";
17
-
18
- # The maximum number of times you want the tests to be run.
19
-
20
- my $MAX_RUNS = 5;
21
-
22
- # The type of revision control system being used.
23
- # Currently "svn" or "git" are supported.
24
-
25
- my $RCS_TYPE = "git";
26
-
27
- # The URL from which a copy will be checked out.
28
-
29
- my $RCS_URL = "git://github.com/jcoglan/faye.git";
30
-
31
- # The directory in which the checkouts will occur.
32
-
33
- my $BASE_DIR = "/home/jcoglan/www/swarm.jcoglan.com/app/changeset/$USER";
34
-
35
- # A script tag loading in the TestSwarm injection script will
36
- # be added at the bottom of the <head> in the following file.
37
-
38
- my $INJECT_FILE = "spec/browser.html";
39
-
40
- # Any build commands that need to happen.
41
-
42
- my $BUILD = "git submodule init && git submodule update && cd vendor/js.class && jake && cd ../.. && rm -rf build && jake";
43
-
44
- # The name of the job that will be submitted
45
- # (pick a descriptive, but short, name to make it easy to search)
46
-
47
- # Note: The string {REV} will be replaced with the current
48
- # commit number/hash.
49
-
50
- my $JOB_NAME = "Faye Commit #{REV}";
51
-
52
- # The browsers you wish to run against. Options include:
53
- # - "all" all available browsers.
54
- # - "popular" the most popular browser (99%+ of all browsers in use)
55
- # - "current" the current release of all the major browsers
56
- # - "gbs" the browsers currently supported in Yahoo's Graded Browser Support
57
- # - "beta" upcoming alpha/beta of popular browsers
58
- # - "popularbeta" the most popular browser and their upcoming releases
59
-
60
- my $BROWSERS = "all";
61
-
62
- # All the suites that you wish to run within this job
63
- # (can be any number of suites)
64
-
65
- my %SUITES = ();
66
-
67
- # Comment these out if you wish to define a custom set of SUITES above
68
- my $SUITE = "$SWARM/changeset/$USER/{REV}";
69
- sub BUILD_SUITES {
70
- %SUITES = map { /(\w+).html/; $1 => "$SUITE/$_"; } glob($INJECT_FILE);
71
- }
72
-
73
- ########### NO NEED TO CONFIGURE BELOW HERE ############
74
-
75
- my $DEBUG = 1;
76
- my $curdate = time;
77
- my $co_dir = "tmp-$curdate";
78
-
79
- print "chdir $BASE_DIR\n" if ( $DEBUG );
80
- chdir( $BASE_DIR );
81
-
82
- # Check out a specific revision
83
- if ( $RCS_TYPE eq "svn" ) {
84
- print "svn co $RCS_URL $co_dir\n" if ( $DEBUG );
85
- `svn co $RCS_URL $co_dir`;
86
- } elsif ( $RCS_TYPE eq "git" ) {
87
- print "git clone $RCS_URL $co_dir\n" if ( $DEBUG );
88
- `git clone $RCS_URL $co_dir`;
89
- }
90
-
91
- if ( ! -e $co_dir ) {
92
- die "Problem checking out source.";
93
- }
94
-
95
- print "chdir $co_dir\n" if ( $DEBUG );
96
- chdir( $co_dir );
97
-
98
- my $rev;
99
-
100
- # Figure out the revision of the checkout
101
- if ( $RCS_TYPE eq "svn" ) {
102
- print "svn info | grep Revision\n" if ( $DEBUG );
103
- $rev = `svn info | grep Revision`;
104
- $rev =~ s/Revision: //;
105
- } elsif ( $RCS_TYPE eq "git" ) {
106
- print "git log --abbrev-commit | head -1\n" if ( $DEBUG );
107
- $rev = `git log --abbrev-commit | head -1`;
108
- $rev =~ s/commit.*?(\w+).*$/$1/;
109
- }
110
-
111
- $rev =~ s/\s*//g;
112
-
113
- print "Revision: $rev\n" if ( $DEBUG );
114
-
115
- if ( ! $rev ) {
116
- remove_tmp();
117
- die "Revision information not found.";
118
-
119
- } elsif ( ! -e "../$rev" ) {
120
- print "chdir $BASE_DIR\n" if ( $DEBUG );
121
- chdir( $BASE_DIR );
122
-
123
- print "rename $co_dir $rev\n" if ( $DEBUG );
124
- rename( $co_dir, $rev );
125
-
126
- print "chdir $rev\n" if ( $DEBUG );
127
- chdir ( $rev );
128
-
129
- if ( $BUILD ) {
130
- print "$BUILD\n" if ( $DEBUG );
131
- `$BUILD`;
132
- }
133
-
134
- if ( exists &BUILD_SUITES ) {
135
- &BUILD_SUITES();
136
- }
137
-
138
- foreach my $file ( glob($INJECT_FILE) ) {
139
- my $inject_file = `cat $file`;
140
-
141
- # Inject the TestSwarm injection script into the test suite
142
- $inject_file =~ s/<\/head>/<script>document.write("<scr" + "ipt src='$SWARM$SWARM_INJECT?" + (new Date).getTime() + "'><\/scr" + "ipt>");<\/script><\/head>/;
143
-
144
- open( F, ">$file" );
145
- print F $inject_file;
146
- close( F );
147
- }
148
-
149
- my %props = (
150
- "state" => "addjob",
151
- "output" => "dump",
152
- "user" => $USER,
153
- "max" => $MAX_RUNS,
154
- "job_name" => $JOB_NAME,
155
- "browsers" => $BROWSERS,
156
- "auth" => $AUTH_TOKEN
157
- );
158
-
159
- my $query = "";
160
-
161
- foreach my $prop ( keys %props ) {
162
- $query .= ($query ? "&" : "") . $prop . "=" . clean($props{$prop});
163
- }
164
-
165
- foreach my $suite ( sort keys %SUITES ) {
166
- $query .= "&suites[]=" . clean($suite) .
167
- "&urls[]=" . clean($SUITES{$suite});
168
- }
169
-
170
- print "curl -d \"$query\" $SWARM\n" if ( $DEBUG );
171
-
172
- my $results = `curl -d "$query" $SWARM`;
173
-
174
- print "Results: $results\n" if ( $DEBUG );
175
-
176
- if ( $results ) {
177
- open( F, ">$rev/results.txt" );
178
- print F "$SWARM$results";
179
- close( F );
180
-
181
- } else {
182
- die "Job not submitted properly.";
183
- }
184
-
185
- # Otherwise, give up and clean up
186
- } else {
187
- remove_tmp();
188
- }
189
-
190
- sub remove_tmp {
191
- chdir( $BASE_DIR );
192
- `rm -rf $co_dir`;
193
- }
194
-
195
- sub clean {
196
- my $str = shift;
197
- $str =~ s/{REV}/$rev/g;
198
- $str =~ s/([^A-Za-z0-9])/sprintf("%%%02X", ord($1))/seg;
199
- $str;
200
- }