faye 0.6.4 → 0.6.5
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 +9 -0
- data/lib/faye-browser-min.js +1 -1
- data/lib/faye.rb +6 -1
- data/lib/faye/adapters/rack_adapter.rb +8 -2
- data/lib/{thin_extensions.rb → faye/thin_extensions.rb} +3 -21
- data/lib/faye/util/web_socket.rb +23 -10
- data/lib/faye/util/web_socket/draft75_parser.rb +19 -16
- data/lib/faye/util/web_socket/draft76_parser.rb +37 -36
- data/lib/faye/util/web_socket/protocol8_parser.rb +160 -85
- data/spec/javascript/engine_spec.js +6 -0
- data/spec/javascript/server/integration_spec.js +78 -0
- data/spec/javascript/web_socket/draft75parser_spec.js +39 -0
- data/spec/javascript/web_socket/protocol8parser_spec.js +130 -0
- data/spec/node.js +18 -0
- data/spec/ruby/engine_spec.rb +9 -0
- data/spec/ruby/server/integration_spec.rb +88 -0
- data/spec/ruby/web_socket/draft75_parser_spec.rb +41 -0
- data/spec/ruby/web_socket/protocol8_parser_spec.rb +120 -0
- data/spec/spec_helper.rb +19 -0
- metadata +9 -3
data/History.txt
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
=== 0.6.5 / 2011-08-29
|
2
|
+
TestSwarm build: http://swarm.jcoglan.com/job/99/
|
3
|
+
|
4
|
+
* Fix UTF-8 encoding bugs in draft-75/76 and protocol-8 WebSocket parsers
|
5
|
+
* Switch to streaming parser for WebSocket protocol-8
|
6
|
+
* Remove an SREM operation that shouldn't have been in the Redis engine
|
7
|
+
* Move thin_extensions.rb so it's not on the Rubygems load path
|
8
|
+
|
9
|
+
|
1
10
|
=== 0.6.4 / 2011-08-18
|
2
11
|
TestSwarm build: http://swarm.jcoglan.com/job/96/
|
3
12
|
|
data/lib/faye-browser-min.js
CHANGED
@@ -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.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);
|
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.5',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);
|
data/lib/faye.rb
CHANGED
@@ -4,7 +4,7 @@ require 'eventmachine'
|
|
4
4
|
require 'json'
|
5
5
|
|
6
6
|
module Faye
|
7
|
-
VERSION = '0.6.
|
7
|
+
VERSION = '0.6.5'
|
8
8
|
|
9
9
|
ROOT = File.expand_path(File.dirname(__FILE__))
|
10
10
|
|
@@ -57,6 +57,11 @@ module Faye
|
|
57
57
|
end
|
58
58
|
end
|
59
59
|
|
60
|
+
def self.encode(string, encoding = 'UTF-8')
|
61
|
+
return string unless string.respond_to?(:force_encoding)
|
62
|
+
string.force_encoding(encoding)
|
63
|
+
end
|
64
|
+
|
60
65
|
def self.ensure_reactor_running!
|
61
66
|
Thread.new { EM.run } unless EM.reactor_running?
|
62
67
|
while not EM.reactor_running?; end
|
@@ -2,7 +2,7 @@ require 'json'
|
|
2
2
|
require 'rack'
|
3
3
|
require 'thin'
|
4
4
|
require 'cgi'
|
5
|
-
require Faye::ROOT + '/thin_extensions'
|
5
|
+
require Faye::ROOT + '/faye/thin_extensions'
|
6
6
|
|
7
7
|
module Faye
|
8
8
|
class RackAdapter
|
@@ -45,7 +45,13 @@ module Faye
|
|
45
45
|
|
46
46
|
def listen(port)
|
47
47
|
handler = Rack::Handler.get('thin')
|
48
|
-
handler.run(self, :Port => port)
|
48
|
+
handler.run(self, :Port => port) { |s| @thin_server = s }
|
49
|
+
end
|
50
|
+
|
51
|
+
def stop
|
52
|
+
return unless @thin_server
|
53
|
+
@thin_server.stop
|
54
|
+
@thin_server = nil
|
49
55
|
end
|
50
56
|
|
51
57
|
def call(env)
|
@@ -35,7 +35,7 @@ class Thin::Connection
|
|
35
35
|
if @request.parse(data)
|
36
36
|
if @request.websocket?
|
37
37
|
@response.persistent!
|
38
|
-
@response.
|
38
|
+
@response.websocket = true
|
39
39
|
@serving = :websocket
|
40
40
|
end
|
41
41
|
|
@@ -57,32 +57,14 @@ class Thin::Request
|
|
57
57
|
@env['HTTP_CONNECTION'].split(/\s*,\s*/).include?('Upgrade') and
|
58
58
|
['WebSocket', 'websocket'].include?(@env['HTTP_UPGRADE'])
|
59
59
|
end
|
60
|
-
|
61
|
-
def secure_websocket?
|
62
|
-
if @env.has_key?('HTTP_X_FORWARDED_PROTO')
|
63
|
-
@env['HTTP_X_FORWARDED_PROTO'] == 'https'
|
64
|
-
else
|
65
|
-
@env['HTTP_ORIGIN'] =~ /^https:/i
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
def websocket_url
|
70
|
-
scheme = secure_websocket? ? 'wss:' : 'ws:'
|
71
|
-
@env['websocket.url'] = "#{ scheme }//#{ @env['HTTP_HOST'] }#{ @env['REQUEST_URI'] }"
|
72
|
-
end
|
73
|
-
|
74
|
-
def websocket_upgrade_data
|
75
|
-
parser = Faye::WebSocket.parser(self)
|
76
|
-
parser.handshake(self)
|
77
|
-
end
|
78
60
|
end
|
79
61
|
|
80
62
|
class Thin::Response
|
81
63
|
# Headers for sending Websocket upgrade
|
82
|
-
attr_accessor :
|
64
|
+
attr_accessor :websocket
|
83
65
|
|
84
66
|
def each
|
85
|
-
|
67
|
+
yield(head) unless websocket
|
86
68
|
if @body.is_a?(String)
|
87
69
|
yield @body
|
88
70
|
else
|
data/lib/faye/util/web_socket.rb
CHANGED
@@ -12,7 +12,7 @@ module Faye
|
|
12
12
|
CLOSING = 2
|
13
13
|
CLOSED = 3
|
14
14
|
|
15
|
-
attr_reader :url, :ready_state
|
15
|
+
attr_reader :request, :url, :ready_state
|
16
16
|
attr_accessor :onopen, :onmessage, :onerror, :onclose
|
17
17
|
|
18
18
|
extend Forwardable
|
@@ -34,30 +34,35 @@ module Faye
|
|
34
34
|
@stream = Stream.new
|
35
35
|
@callback.call [200, RackAdapter::TYPE_JSON, @stream]
|
36
36
|
|
37
|
-
@url =
|
37
|
+
@url = determine_url
|
38
|
+
@ready_state = CONNECTING
|
39
|
+
@buffered_amount = 0
|
40
|
+
|
41
|
+
@parser = WebSocket.parser(@request).new(self)
|
42
|
+
@stream.write(@parser.handshake_response)
|
43
|
+
|
38
44
|
@ready_state = OPEN
|
39
45
|
|
40
46
|
event = Event.new
|
41
47
|
event.init_event('open', false, false)
|
42
48
|
dispatch_event(event)
|
43
49
|
|
44
|
-
@parser = WebSocket.parser(@request).new(self)
|
45
50
|
@request.env[Thin::Request::WEBSOCKET_RECEIVE_CALLBACK] = @parser.method(:parse)
|
46
51
|
end
|
47
52
|
|
48
53
|
def receive(data)
|
49
54
|
event = Event.new
|
50
55
|
event.init_event('message', false, false)
|
51
|
-
event.data = encode(data)
|
56
|
+
event.data = Faye.encode(data)
|
52
57
|
dispatch_event(event)
|
53
58
|
end
|
54
59
|
|
55
60
|
def send(data, type = nil, error_type = nil)
|
56
|
-
frame = @parser.frame(encode(data), type, error_type)
|
61
|
+
frame = @parser.frame(Faye.encode(data), type, error_type)
|
57
62
|
@stream.write(frame) if frame
|
58
63
|
end
|
59
64
|
|
60
|
-
def close
|
65
|
+
def close(*args)
|
61
66
|
end
|
62
67
|
|
63
68
|
def add_event_listener(type, listener, use_capture)
|
@@ -77,11 +82,19 @@ module Faye
|
|
77
82
|
callback.call(event) if callback
|
78
83
|
end
|
79
84
|
|
80
|
-
|
81
|
-
return string unless string.respond_to?(:force_encoding)
|
82
|
-
string.force_encoding(encoding)
|
83
|
-
end
|
85
|
+
private
|
84
86
|
|
87
|
+
def determine_url
|
88
|
+
env = @request.env
|
89
|
+
secure = if env.has_key?('HTTP_X_FORWARDED_PROTO')
|
90
|
+
env['HTTP_X_FORWARDED_PROTO'] == 'https'
|
91
|
+
else
|
92
|
+
env['HTTP_ORIGIN'] =~ /^https:/i
|
93
|
+
end
|
94
|
+
|
95
|
+
scheme = secure ? 'wss:' : 'ws:'
|
96
|
+
"#{ scheme }//#{ env['HTTP_HOST'] }#{ env['REQUEST_URI'] }"
|
97
|
+
end
|
85
98
|
end
|
86
99
|
|
87
100
|
class WebSocket::Stream
|
@@ -2,15 +2,6 @@ module Faye
|
|
2
2
|
class WebSocket
|
3
3
|
|
4
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
5
|
def initialize(web_socket)
|
15
6
|
@socket = web_socket
|
16
7
|
@buffer = []
|
@@ -21,28 +12,40 @@ module Faye
|
|
21
12
|
'draft-75'
|
22
13
|
end
|
23
14
|
|
15
|
+
def handshake_response
|
16
|
+
request = @socket.request
|
17
|
+
|
18
|
+
upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
|
19
|
+
upgrade << "Upgrade: WebSocket\r\n"
|
20
|
+
upgrade << "Connection: Upgrade\r\n"
|
21
|
+
upgrade << "WebSocket-Origin: #{request.env['HTTP_ORIGIN']}\r\n"
|
22
|
+
upgrade << "WebSocket-Location: #{@socket.url}\r\n"
|
23
|
+
upgrade << "\r\n"
|
24
|
+
upgrade
|
25
|
+
end
|
26
|
+
|
24
27
|
def parse(data)
|
25
|
-
data.
|
28
|
+
data.each_byte(&method(:handle_byte))
|
26
29
|
end
|
27
30
|
|
28
31
|
def frame(data, type = nil, error_type = nil)
|
29
|
-
"\x00
|
32
|
+
["\x00", data, "\xFF"].map(&Faye.method(:encode)) * ''
|
30
33
|
end
|
31
34
|
|
32
35
|
private
|
33
36
|
|
34
|
-
def
|
37
|
+
def handle_byte(data)
|
35
38
|
case data
|
36
|
-
when
|
39
|
+
when 0x00 then
|
37
40
|
@buffering = true
|
38
41
|
|
39
|
-
when
|
40
|
-
@socket.receive(@buffer.join(''))
|
42
|
+
when 0xFF then
|
43
|
+
@socket.receive(Faye.encode(@buffer.join('')))
|
41
44
|
@buffer = []
|
42
45
|
@buffering = false
|
43
46
|
|
44
47
|
else
|
45
|
-
@buffer.push(data) if @buffering
|
48
|
+
@buffer.push(data.chr) if @buffering
|
46
49
|
end
|
47
50
|
end
|
48
51
|
end
|
@@ -4,48 +4,49 @@ module Faye
|
|
4
4
|
class WebSocket
|
5
5
|
|
6
6
|
class Draft76Parser < Draft75Parser
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
7
|
+
def version
|
8
|
+
'draft-76'
|
9
|
+
end
|
10
|
+
|
11
|
+
def handshake_response
|
12
|
+
request = @socket.request
|
27
13
|
|
28
|
-
|
14
|
+
key1 = request.env['HTTP_SEC_WEBSOCKET_KEY1']
|
15
|
+
value1 = number_from_key(key1) / spaces_in_key(key1)
|
29
16
|
|
30
|
-
|
31
|
-
|
32
|
-
end
|
17
|
+
key2 = request.env['HTTP_SEC_WEBSOCKET_KEY2']
|
18
|
+
value2 = number_from_key(key2) / spaces_in_key(key2)
|
33
19
|
|
34
|
-
|
35
|
-
|
36
|
-
|
20
|
+
hash = Digest::MD5.digest(big_endian(value1) +
|
21
|
+
big_endian(value2) +
|
22
|
+
request.body.read)
|
37
23
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
24
|
+
upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
|
25
|
+
upgrade << "Upgrade: WebSocket\r\n"
|
26
|
+
upgrade << "Connection: Upgrade\r\n"
|
27
|
+
upgrade << "Sec-WebSocket-Origin: #{request.env['HTTP_ORIGIN']}\r\n"
|
28
|
+
upgrade << "Sec-WebSocket-Location: #{@socket.url}\r\n"
|
29
|
+
upgrade << "\r\n"
|
30
|
+
upgrade << hash
|
31
|
+
upgrade
|
45
32
|
end
|
46
33
|
|
47
|
-
|
48
|
-
|
34
|
+
private
|
35
|
+
|
36
|
+
def number_from_key(key)
|
37
|
+
key.scan(/[0-9]/).join('').to_i(10)
|
38
|
+
end
|
39
|
+
|
40
|
+
def spaces_in_key(key)
|
41
|
+
key.scan(/ /).size
|
42
|
+
end
|
43
|
+
|
44
|
+
def big_endian(number)
|
45
|
+
string = ''
|
46
|
+
[24,16,8,0].each do |offset|
|
47
|
+
string << (number >> offset & 0xFF).chr
|
48
|
+
end
|
49
|
+
string
|
49
50
|
end
|
50
51
|
end
|
51
52
|
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'base64'
|
2
2
|
require 'digest/sha1'
|
3
|
+
require 'net/http'
|
3
4
|
|
4
5
|
module Faye
|
5
6
|
class WebSocket
|
@@ -32,148 +33,222 @@ module Faye
|
|
32
33
|
:encoding_error => 1007
|
33
34
|
}
|
34
35
|
|
35
|
-
|
36
|
-
|
37
|
-
|
36
|
+
class Handshake
|
37
|
+
def initialize(uri)
|
38
|
+
@uri = uri
|
39
|
+
@key = Base64.encode64((1..16).map { rand(255).chr } * '').strip
|
40
|
+
@accept = Base64.encode64(Digest::SHA1.digest(@key + GUID)).strip
|
41
|
+
@buffer = []
|
42
|
+
end
|
38
43
|
|
39
|
-
|
44
|
+
def request_data
|
45
|
+
hostname = @uri.host + (@uri.port ? ":#{@uri.port}" : '')
|
46
|
+
|
47
|
+
handshake = "GET #{@uri.path} HTTP/1.1\r\n"
|
48
|
+
handshake << "Host: #{hostname}\r\n"
|
49
|
+
handshake << "Upgrade: websocket\r\n"
|
50
|
+
handshake << "Connection: Upgrade\r\n"
|
51
|
+
handshake << "Sec-WebSocket-Key: #{@key}\r\n"
|
52
|
+
handshake << "Sec-WebSocket-Version: 8\r\n"
|
53
|
+
handshake << "\r\n"
|
54
|
+
|
55
|
+
handshake
|
56
|
+
end
|
40
57
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
58
|
+
def parse(data)
|
59
|
+
data.each_byte { |b| @buffer << b.chr }
|
60
|
+
end
|
61
|
+
|
62
|
+
def complete?
|
63
|
+
@buffer[-4..-1] == ["\r", "\n", "\r", "\n"]
|
64
|
+
end
|
65
|
+
|
66
|
+
def valid?
|
67
|
+
data = Faye.encode(@buffer * '')
|
68
|
+
response = Net::HTTPResponse.read_new(Net::BufferedIO.new(StringIO.new(data)))
|
69
|
+
return false unless response.code.to_i == 101
|
70
|
+
|
71
|
+
upgrade, connection = response['Upgrade'], response['Connection']
|
72
|
+
|
73
|
+
upgrade and upgrade =~ /^websocket$/i and
|
74
|
+
connection and connection.split(/\s*,\s*/).include?('Upgrade') and
|
75
|
+
response['Sec-WebSocket-Accept'] == @accept
|
76
|
+
end
|
46
77
|
end
|
47
|
-
|
78
|
+
|
48
79
|
def initialize(web_socket)
|
49
80
|
reset
|
50
81
|
@socket = web_socket
|
82
|
+
@stage = 0
|
51
83
|
end
|
52
84
|
|
53
85
|
def version
|
54
86
|
'protocol-8'
|
55
87
|
end
|
56
88
|
|
89
|
+
def handshake_response
|
90
|
+
sec_key = @socket.request.env['HTTP_SEC_WEBSOCKET_KEY']
|
91
|
+
return '' unless String === sec_key
|
92
|
+
|
93
|
+
accept = Base64.encode64(Digest::SHA1.digest(sec_key + GUID)).strip
|
94
|
+
|
95
|
+
upgrade = "HTTP/1.1 101 Switching Protocols\r\n"
|
96
|
+
upgrade << "Upgrade: websocket\r\n"
|
97
|
+
upgrade << "Connection: Upgrade\r\n"
|
98
|
+
upgrade << "Sec-WebSocket-Accept: #{accept}\r\n"
|
99
|
+
upgrade << "\r\n"
|
100
|
+
upgrade
|
101
|
+
end
|
102
|
+
|
103
|
+
def create_handshake
|
104
|
+
Handshake.new(@socket.uri)
|
105
|
+
end
|
106
|
+
|
57
107
|
def parse(data)
|
58
|
-
|
59
|
-
|
60
|
-
|
108
|
+
data.each_byte do |byte|
|
109
|
+
case @stage
|
110
|
+
when 0 then parse_opcode(byte)
|
111
|
+
when 1 then parse_length(byte)
|
112
|
+
when 2 then parse_extended_length(byte)
|
113
|
+
when 3 then parse_mask(byte)
|
114
|
+
when 4 then parse_payload(byte)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def frame(data, type = nil, error_type = nil)
|
120
|
+
return nil if @closed
|
61
121
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
122
|
+
if error_type
|
123
|
+
data = [ERRORS[error_type]].pack('n') + data
|
124
|
+
end
|
125
|
+
|
126
|
+
opcode = OPCODES[type || :text]
|
127
|
+
frame = (FIN | opcode).chr
|
128
|
+
length = data.respond_to?(:bytes) ? data.bytes.count : data.size
|
69
129
|
|
70
130
|
case length
|
71
|
-
when
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
131
|
+
when 0..125 then
|
132
|
+
frame << length
|
133
|
+
when 126..65535 then
|
134
|
+
frame << 126
|
135
|
+
frame << [length].pack('n')
|
136
|
+
else
|
137
|
+
frame << 127
|
138
|
+
frame << [length >> 32, length & 0xFFFFFFFF].pack('NN')
|
77
139
|
end
|
78
140
|
|
79
|
-
|
80
|
-
|
81
|
-
|
141
|
+
|
142
|
+
Faye.encode(frame) + Faye.encode(data)
|
143
|
+
end
|
144
|
+
|
145
|
+
def close(error_type = nil, &callback)
|
146
|
+
return if @closed
|
147
|
+
@closing_callback ||= callback
|
148
|
+
@socket.send('', :close, error_type || :normal_closure)
|
149
|
+
@closed = true
|
150
|
+
end
|
151
|
+
|
152
|
+
private
|
153
|
+
|
154
|
+
def parse_opcode(data)
|
155
|
+
@final = (data & FIN) == FIN
|
156
|
+
@opcode = (data & OPCODE)
|
157
|
+
@mask = []
|
158
|
+
@payload = []
|
159
|
+
|
160
|
+
return @socket.close(:protocol_error) unless OPCODES.values.include?(@opcode)
|
161
|
+
@stage = 1
|
162
|
+
end
|
163
|
+
|
164
|
+
def parse_length(data)
|
165
|
+
@masked = (data & MASK) == MASK
|
166
|
+
@length = (data & LENGTH)
|
167
|
+
|
168
|
+
if (1..125).include?(@length)
|
169
|
+
@stage = @masked ? 3 : 4
|
82
170
|
else
|
83
|
-
|
84
|
-
|
171
|
+
@length_buffer = []
|
172
|
+
@length_size = (@length == 126) ? 2 : 8
|
173
|
+
@stage = 2
|
85
174
|
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def parse_extended_length(data)
|
178
|
+
@length_buffer << data
|
179
|
+
return unless @length_buffer.size == @length_size
|
180
|
+
@length = integer(@length_buffer)
|
181
|
+
@stage = @masked ? 3 : 4
|
182
|
+
end
|
183
|
+
|
184
|
+
def parse_mask(data)
|
185
|
+
@mask << data
|
186
|
+
return if @mask.size < 4
|
187
|
+
@stage = 4
|
188
|
+
end
|
189
|
+
|
190
|
+
def parse_payload(data)
|
191
|
+
@payload << data
|
192
|
+
return if @payload.size < @length
|
193
|
+
emit_frame
|
194
|
+
@stage = 0
|
195
|
+
end
|
196
|
+
|
197
|
+
def emit_frame
|
198
|
+
payload = unmask(@payload, @mask)
|
86
199
|
|
87
|
-
|
88
|
-
|
89
|
-
raw_payload = data[payload_offset...(payload_offset + length)]
|
90
|
-
payload = unmask(raw_payload, mask_octets)
|
91
|
-
|
92
|
-
case opcode
|
200
|
+
case @opcode
|
93
201
|
when OPCODES[:continuation] then
|
94
202
|
return unless @mode == :text
|
95
203
|
@buffer << payload
|
96
|
-
if final
|
204
|
+
if @final
|
97
205
|
message = @buffer * ''
|
98
206
|
reset
|
99
|
-
@socket.receive(message)
|
207
|
+
@socket.receive(Faye.encode(message))
|
100
208
|
end
|
101
209
|
|
102
210
|
when OPCODES[:text] then
|
103
|
-
if final
|
104
|
-
@socket.receive(payload)
|
211
|
+
if @final
|
212
|
+
@socket.receive(Faye.encode(payload))
|
105
213
|
else
|
106
214
|
@mode = :text
|
107
215
|
@buffer << payload
|
108
216
|
end
|
109
217
|
|
110
218
|
when OPCODES[:binary] then
|
111
|
-
close(:unacceptable)
|
219
|
+
@socket.close(:unacceptable)
|
112
220
|
|
113
221
|
when OPCODES[:close] then
|
114
|
-
close(:normal_closure)
|
222
|
+
@socket.close(:normal_closure)
|
223
|
+
@closing_callback.call if @closing_callback
|
115
224
|
|
116
225
|
when OPCODES[:ping] then
|
117
226
|
@socket.send(payload, :pong)
|
118
227
|
end
|
119
228
|
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
|
-
|
229
|
+
|
149
230
|
def reset
|
150
231
|
@buffer = []
|
151
232
|
@mode = nil
|
152
233
|
end
|
153
234
|
|
154
|
-
def close(error_type)
|
155
|
-
return if @closed
|
156
|
-
@socket.send('', :close, error_type)
|
157
|
-
@closed = true
|
158
|
-
end
|
159
|
-
|
160
235
|
def getbyte(data, offset)
|
161
236
|
data.respond_to?(:getbyte) ? data.getbyte(offset) : data[offset]
|
162
237
|
end
|
163
238
|
|
164
|
-
def integer(
|
239
|
+
def integer(bytes)
|
165
240
|
number = 0
|
166
|
-
|
167
|
-
number +=
|
241
|
+
bytes.each_with_index do |data, i|
|
242
|
+
number += data << (8 * (bytes.size - 1 - i))
|
168
243
|
end
|
169
244
|
number
|
170
245
|
end
|
171
246
|
|
172
|
-
def unmask(payload,
|
173
|
-
return payload unless mask_octets.size > 0
|
247
|
+
def unmask(payload, mask)
|
174
248
|
unmasked = ''
|
175
|
-
|
176
|
-
|
249
|
+
payload.each_with_index do |byte, i|
|
250
|
+
byte = byte ^ mask[i % 4] if mask.size > 0
|
251
|
+
unmasked << byte
|
177
252
|
end
|
178
253
|
unmasked
|
179
254
|
end
|