faye 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of faye might be problematic. Click here for more details.
- data/History.txt +1 -0
- data/Jakefile +2 -0
- data/Manifest.txt +33 -0
- data/README.txt +111 -0
- data/Rakefile +13 -0
- data/client/channel.js +135 -0
- data/client/client.js +265 -0
- data/client/faye.js +95 -0
- data/client/transport.js +91 -0
- data/client/util/class.js +20 -0
- data/client/util/event.js +44 -0
- data/client/util/json.js +479 -0
- data/client/util/uri.js +69 -0
- data/client/util/xhr.js +99 -0
- data/examples/soapbox/README +22 -0
- data/examples/soapbox/app.rb +8 -0
- data/examples/soapbox/config.ru +8 -0
- data/examples/soapbox/public/jquery.js +19 -0
- data/examples/soapbox/public/soapbox.js +100 -0
- data/examples/soapbox/public/style.css +43 -0
- data/examples/soapbox/views/index.erb +50 -0
- data/jake.yml +19 -0
- data/lib/faye-min.js +1 -0
- data/lib/faye.rb +30 -0
- data/lib/faye/channel.rb +122 -0
- data/lib/faye/connection.rb +110 -0
- data/lib/faye/error.rb +44 -0
- data/lib/faye/grammar.rb +58 -0
- data/lib/faye/rack_adapter.rb +53 -0
- data/lib/faye/server.rb +231 -0
- data/test/test_channel.rb +34 -0
- data/test/test_grammar.rb +86 -0
- data/test/test_server.rb +420 -0
- metadata +130 -0
data/lib/faye-min.js
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
if(!this.Faye)Faye={};Faye.extend=function(a,c,b){if(!c)return a;for(var d in c){if(c.hasOwnProperty(d)&&a[d]!==c[d]){if(!a.hasOwnProperty(d)||b!==false)a[d]=c[d]}}return a};Faye.extend(Faye,{BAYEUX_VERSION:'1.0',ENV:this,VERSION:'0.1.0',Grammar:{LOWALPHA:/^[a-z]$/,UPALPHA:/^[A-Z]$/,ALPHA:/^([a-z]|[A-Z])$/,DIGIT:/^[0-9]$/,ALPHANUM:/^(([a-z]|[A-Z])|[0-9])$/,MARK:/^(\-|\_|\!|\~|\(|\)|\$|\@)$/,STRING:/^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*$/,TOKEN:/^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+$/,INTEGER:/^([0-9])+$/,CHANNEL_SEGMENT:/^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+$/,CHANNEL_SEGMENTS:/^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+(\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+)*$/,CHANNEL_NAME:/^\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+(\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+)*$/,WILD_CARD:/^\*{1,2}$/,CHANNEL_PATTERN:/^(\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+)*\/\*{1,2}$/,VERSION_ELEMENT:/^(([a-z]|[A-Z])|[0-9])(((([a-z]|[A-Z])|[0-9])|\-|\_))*$/,VERSION:/^([0-9])+(\.(([a-z]|[A-Z])|[0-9])(((([a-z]|[A-Z])|[0-9])|\-|\_))*)*$/,CLIENT_ID:/^((([a-z]|[A-Z])|[0-9]))+$/,ID:/^((([a-z]|[A-Z])|[0-9]))+$/,ERROR_MESSAGE:/^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*$/,ERROR_ARGS:/^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*(,(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*)*$/,ERROR_CODE:/^[0-9][0-9][0-9]$/,ERROR:/^([0-9][0-9][0-9]:(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*(,(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*)*:(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*|[0-9][0-9][0-9]::(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*)$/},commonElement:function(a,c){for(var b=0,d=a.length;b<d;b++){if(this.indexOf(c,a[b])!==-1)return a[b]}return null},indexOf:function(a,c){for(var b=0,d=a.length;b<d;b++){if(a[b]===c)return b}return-1},each:function(a,c,b){if(a instanceof Array){for(var d=0,f=a.length;d<f;d++){if(a[d]!==undefined)c.call(b||null,a[d],d)}}else{for(var g in a){if(a.hasOwnProperty(g))c.call(b||null,g,a[g])}}},size:function(a){var c=0;this.each(a,function(){c+=1});return c},enumEqual:function(b,d){if(d instanceof Array){if(!(b instanceof Array))return false;var f=b.length;if(f!==d.length)return false;while(f--){if(b[f]!==d[f])return false}return true}else{if(!(b instanceof Object))return false;if(this.size(d)!==this.size(b))return false;var g=true;this.each(b,function(a,c){g=g&&(d[a]===c)});return g}}});Faye.Class=function(a,c){if(typeof a!=='function'){c=a;a=Object}var b=function(){if(!this.initialize)return this;return this.initialize.apply(this,arguments)||this};var d=function(){};d.prototype=a.prototype;b.prototype=new d();Faye.extend(b.prototype,c);return b};Faye.Event={_9:[],on:function(a,c,b,d){var f=function(){b.call(d)};if(a.addEventListener)a.addEventListener(c,f,false);else a.attachEvent('on'+c,f);this._9.push({_a:a,_g:c,_r:b,_b:d,_l:f})},detach:function(a,c,b,d){var f=this._9.length,g;while(f--){g=this._9[f];if((a&&a!==g._a)||(c&&c!==g._g)||(b&&b!==g._r)||(d&&d!==g._b))continue;if(g._a.removeEventListener)g._a.removeEventListener(g._g,g._l,false);else g._a.detachEvent('on'+g._g,g._l);this._9.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 b=[],d;Faye.each(this.params,function(a,c){b.push(encodeURIComponent(a)+'='+encodeURIComponent(c))});return b.join('&')},isLocal:function(){var a=Faye.URI.parse(Faye.ENV.location.href);var c=(a.hostname!==this.hostname)||(a.port!==this.port)||(a.protocol!==this.protocol);return!c},toURL:function(){return this.protocol+this.hostname+':'+this.port+this.pathname+'?'+this.queryString()}}),{parse:function(d,f){if(typeof d!=='string')return d;var g=new this();var k=function(c,b){d=d.replace(b,function(a){if(a)g[c]=a;return''})};k('protocol',/^https?\:\/+/);k('hostname',/^[a-z0-9\-]+(\.[a-z0-9\-]+)*\.[a-z0-9\-]{2,6}/i);k('port',/^:[0-9]+/);Faye.extend(g,{protocol:'http://',hostname:Faye.ENV.location.hostname,port:Faye.ENV.location.port},false);if(!g.port)g.port=(g.protocol==='https://')?'443':'80';g.port=g.port.replace(/\D/g,'');var i=d.split('?'),h=i.shift(),l=i.join('?'),n=l?l.split('&'):[],o=n.length,j={};while(o--){i=n[o].split('=');j[decodeURIComponent(i[0]||'')]=decodeURIComponent(i[1]||'')}Faye.extend(j,f);g.pathname=h;g.params=j;return g}});Faye.XHR={request:function(a,c,b,d,f){var g=new this.Request(a,c,b,d,f);g.send();return g},getXhrObject:function(){return Faye.ENV.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest()},Request:Faye.Class({initialize:function(a,c,b,d,f){this._c=a.toUpperCase();this._4=Faye.URI.parse(c,b);this._s=(typeof d==='function')?{success:d}:d;this._b=f||null;this._1=null},send:function(){if(this._h)return;var a=this._4.pathname,c=this._4.queryString();if(this._c==='GET')a+='?'+c;var b=this._c==='POST'?c:'';this._h=true;this._1=Faye.XHR.getXhrObject();this._1.open(this._c,a,true);if(this._c==='POST')this._1.setRequestHeader('Content-Type','application/x-www-form-urlencoded');var d=this,f=function(){if(d._1.readyState!==4)return;if(g){clearInterval(g);g=null}Faye.Event.detach(Faye.ENV,'beforeunload',d.abort,d);d._h=false;d._t();d=null};var g=setInterval(f,10);Faye.Event.on(Faye.ENV,'beforeunload',this.abort,this);this._1.send(b)},abort:function(){this._1.abort()},_t:function(){var a=this._s;if(!a)return;return this.success()?a.success&&a.success.call(this._b,this):a.failure&&a.failure.call(this._b,this)},waiting:function(){return!!this._h},complete:function(){return this._1&&!this.waiting()},success:function(){if(!this.complete())return false;var a=this._1.status;return(a>=200&&a<300)||a===304||a===1223},failure:function(){if(!this.complete())return false;return!this.success()},text:function(){if(!this.complete())return null;return this._1.responseText},status:function(){if(!this.complete())return null;return this._1.status}})};Faye.Channel={HANDSHAKE:'/meta/handshake',CONNECT:'/meta/connect',SUBSCRIBE:'/meta/subscribe',UNSUBSCRIBE:'/meta/unsubscribe',DISCONNECT:'/meta/disconnect',META:'meta',SERVICE:'service',isValid:function(a){return Faye.Grammar.CHANNEL_NAME.test(a)||Faye.Grammar.CHANNEL_PATTERN.test(a)},parse:function(a){if(!this.isValid(a))return null;return a.split('/').slice(1)},isMeta:function(a){var c=this.parse(a);return c?(c[0]===this.META):null},isService:function(a){var c=this.parse(a);return c?(c[0]===this.SERVICE):null},isSubscribable:function(a){if(!this.isValid(a))return null;return!this.isMeta(a)&&!this.isService(a)},Tree:Faye.Class({initialize:function(a){this._2=a;this._5={}},eachChild:function(b,d){Faye.each(this._5,function(a,c){b.call(d,a,c)})},each:function(b,d,f){this.eachChild(function(a,c){a=b.concat(a);c.each(a,d,f)});if(this._2!==undefined)d.call(f,b,this._2)},map:function(b,d){var f=[];this.each([],function(a,c){f.push(b.call(d,a,c))});return f},get:function(a){var c=this.traverse(a);return c?c._2:null},set:function(a,c){var b=this.traverse(a,true);if(b)b._2=c},traverse:function(a,c){if(typeof a==='string')a=Faye.Channel.parse(a);if(a===null)return null;if(a.length===0)return this;var b=this._5[a[0]];if(!b&&!c)return null;if(!b)b=this._5[a[0]]=new Faye.Channel.Tree();return b.traverse(a.slice(1),c)},glob:function(f){if(typeof f==='string')f=Faye.Channel.parse(f);if(f===null)return[];if(f.length===0)return(this._2===undefined)?[]:[this._2];var g=[];if(Faye.enumEqual(f,['*'])){Faye.each(this._5,function(a,c){if(c._2!==undefined)g.push(c._2)});return g}if(Faye.enumEqual(f,['**'])){g=this.map(function(a,c){return c});g.pop();return g}Faye.each(this._5,function(c,b){if(c!==f[0]&&c!=='*')return;var d=b.glob(f.slice(1));Faye.each(d,function(a){g.push(a)})});if(this._5['**'])g.push(this._5['**']._2);return g}})};Faye.Transport=Faye.extend(Faye.Class({initialize:function(a,c){this._m=a;this._4=c},send:function(b,d,f){b={message:JSON.stringify(b)};return this.request(b,function(c){if(!d)return;Faye.each([].concat(c),function(a){d.call(f,a);if(a.advice)this._m._u(a.advice);if(a.data&&a.channel)this._m._v(a)},this)},this)}}),{get:function(a,c){var b=a._4;if(c===undefined)c=this.supportedConnectionTypes();var d=Faye.URI.parse(b).isLocal()?['callback-polling']:['callback-polling'];var f=Faye.commonElement(d,c);if(!f)throw'Could not find a usable connection type for '+b;var g=this._i[f];return new g(a,b)},register:function(a,c){this._i[a]=c;c.prototype.connectionType=a},_i:{},supportedConnectionTypes:function(){var b=[],d;Faye.each(this._i,function(a,c){b.push(a)});return b}});Faye.XHRTransport=Faye.Class(Faye.Transport,{request:function(c,b,d){Faye.XHR.request('post',this._4,c,function(a){if(b)b.call(d,JSON.parse(a.text()))})}});Faye.Transport.register('long-polling',Faye.XHRTransport);Faye.JSONPTransport=Faye.extend(Faye.Class(Faye.Transport,{request:function(c,b,d){var f=document.getElementsByTagName('head')[0],g=document.createElement('script'),k=Faye.JSONPTransport.getCallbackName(),i=Faye.URI.parse(this._4,c);Faye.ENV[k]=function(a){Faye.ENV[k]=undefined;try{delete Faye.ENV[k]}catch(e){}f.removeChild(g);if(b)b.call(d,a)};i.params.jsonp=k;g.type='text/javascript';g.src=i.toURL();f.appendChild(g)}}),{_n:0,getCallbackName:function(){this._n+=1;return'__jsonp'+this._n+'__'}});Faye.Transport.register('callback-polling',Faye.JSONPTransport);Faye.Client=Faye.Class({_d:{},_w:{},_7:{},_x:{},_o:'handshake',_y:'retry',_p:'none',DEFAULT_ENDPOINT:'/bayeux',MAX_DELAY:0.1,INTERVAL:1000.0,initialize:function(a){this._4=a||this.DEFAULT_ENDPOINT;this._3=Faye.Transport.get(this);this._0=this._d;this._j=[];this._e=new Faye.Channel.Tree();this._6={reconnect:this._y,interval:this.INTERVAL};Faye.Event.on(Faye.ENV,'beforeunload',this.disconnect,this)},handshake:function(c,b){if(this._6.reconnect===this._p)return;if(this._0!==this._d)return;this._0=this._w;var d=this,f=this.generateId();this._3.send({channel:Faye.Channel.HANDSHAKE,version:Faye.BAYEUX_VERSION,supportedConnectionTypes:Faye.Transport.supportedConnectionTypes(),id:f},function(a){if(a.id!==f)return;if(!a.successful){setTimeout(function(){d.handshake(c,b)},this._6.interval);return this._0=this._d}this._0=this._7;this._8=a.clientId;this._3=Faye.Transport.get(this,a.supportedConnectionTypes);if(c)c.call(b)},this)},connect:function(c,b){if(this._6.reconnect===this._p)return;if(this._6.reconnect===this._o||this._0===this._d)return this.handshake(function(){this.connect(c,b)},this);if(this._0!==this._7)return;if(this._f)return;this._f=this.generateId();var d=this;this._3.send({channel:Faye.Channel.CONNECT,clientId:this._8,connectionType:this._3.connectionType,id:this._f},function(a){if(a.id!==this._f)return;delete this._f;if(a.successful)this.connect();else setTimeout(function(){d.connect()},this._6.interval)},this);if(c)c.call(b)},disconnect:function(){if(this._0!==this._7)return;this._0=this._x;this._3.send({channel:Faye.Channel.DISCONNECT,clientId:this._8});this._e=new Faye.Channel.Tree()},subscribe:function(b,d,f){if(this._0!==this._7)return;b=[].concat(b);this._k(b);var g=this.generateId();this._3.send({channel:Faye.Channel.SUBSCRIBE,clientId:this._8,subscription:b,id:g},function(c){if(c.id!==g)return;if(!c.successful)return;b=[].concat(c.subscription);Faye.each(b,function(a){this._e.set(a,[d,f])},this)},this)},unsubscribe:function(b,d,f){if(this._0!==this._7)return;b=[].concat(b);this._k(b);var g=this.generateId();this._3.send({channel:Faye.Channel.UNSUBSCRIBE,clientId:this._8,subscription:b,id:g},function(c){if(c.id!==g)return;if(!c.successful)return;b=[].concat(c.subscription);Faye.each(b,function(a){this._e.set(a,null)},this)},this)},publish:function(a,c){if(this._0!==this._7)return;this._k([a]);this.enqueue({channel:a,data:c,clientId:this._8});if(this._q)return;var b=this;this._q=setTimeout(function(){delete b._q;b.flush()},this.MAX_DELAY*1000)},generateId:function(a){a=a||32;return Math.floor(Math.pow(2,a)*Math.random()).toString(16)},enqueue:function(a){this._j.push(a)},flush:function(){this._3.send(this._j);this._j=[]},_k:function(c){Faye.each(c,function(a){if(!Faye.Channel.isValid(a))throw'"'+a+'" is not a valid channel name';if(!Faye.Channel.isSubscribable(a))throw'Clients may not subscribe to channel "'+a+'"';})},_u:function(a){Faye.extend(this._6,a);if(this._6.reconnect===this._o)this._8=null},_v:function(c){var b=this._e.glob(c.channel);Faye.each(b,function(a){if(!a)return;a[0].call(a[1],c.data)})}});if(!this.JSON){JSON={}}(function(){function l(a){return a<10?'0'+a:a}if(typeof Date.prototype.toJSON!=='function'){Date.prototype.toJSON=function(a){return this.getUTCFullYear()+'-'+l(this.getUTCMonth()+1)+'-'+l(this.getUTCDate())+'T'+l(this.getUTCHours())+':'+l(this.getUTCMinutes())+':'+l(this.getUTCSeconds())+'Z'};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(a){return this.valueOf()}}var n=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,o=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,j,p,s={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'},m;function r(b){o.lastIndex=0;return o.test(b)?'"'+b.replace(o,function(a){var c=s[a];return typeof c==='string'?c:'\\u'+('0000'+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+b+'"'}function q(a,c){var b,d,f,g,k=j,i,h=c[a];if(h&&typeof h==='object'&&typeof h.toJSON==='function'){h=h.toJSON(a)}if(typeof m==='function'){h=m.call(c,a,h)}switch(typeof h){case'string':return r(h);case'number':return isFinite(h)?String(h):'null';case'boolean':case'null':return String(h);case'object':if(!h){return'null'}j+=p;i=[];if(Object.prototype.toString.apply(h)==='[object Array]'){g=h.length;for(b=0;b<g;b+=1){i[b]=q(b,h)||'null'}f=i.length===0?'[]':j?'[\n'+j+i.join(',\n'+j)+'\n'+k+']':'['+i.join(',')+']';j=k;return f}if(m&&typeof m==='object'){g=m.length;for(b=0;b<g;b+=1){d=m[b];if(typeof d==='string'){f=q(d,h);if(f){i.push(r(d)+(j?': ':':')+f)}}}}else{for(d in h){if(Object.hasOwnProperty.call(h,d)){f=q(d,h);if(f){i.push(r(d)+(j?': ':':')+f)}}}}f=i.length===0?'{}':j?'{\n'+j+i.join(',\n'+j)+'\n'+k+'}':'{'+i.join(',')+'}';j=k;return f}}if(typeof JSON.stringify!=='function'){JSON.stringify=function(a,c,b){var d;j='';p='';if(typeof b==='number'){for(d=0;d<b;d+=1){p+=' '}}else if(typeof b==='string'){p=b}m=c;if(c&&typeof c!=='function'&&(typeof c!=='object'||typeof c.length!=='number')){throw new Error('JSON.stringify');}return q('',{'':a})}}if(typeof JSON.parse!=='function'){JSON.parse=function(g,k){var i;function h(a,c){var b,d,f=a[c];if(f&&typeof f==='object'){for(b in f){if(Object.hasOwnProperty.call(f,b)){d=h(f,b);if(d!==undefined){f[b]=d}else{delete f[b]}}}}return k.call(a,c,f)}n.lastIndex=0;if(n.test(g)){g=g.replace(n,function(a){return'\\u'+('0000'+a.charCodeAt(0).toString(16)).slice(-4)})}if(/^[\],:{}\s]*$/.test(g.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,'@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,']').replace(/(?:^|:|,)(?:\s*\[)+/g,''))){i=eval('('+g+')');return typeof k==='function'?h({'':i},''):i}throw new SyntaxError('JSON.parse');}}}());
|
data/lib/faye.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'observer'
|
3
|
+
require 'set'
|
4
|
+
require 'rubygems'
|
5
|
+
require 'eventmachine'
|
6
|
+
|
7
|
+
module Faye
|
8
|
+
VERSION = '0.1.0'
|
9
|
+
|
10
|
+
ROOT = File.expand_path(File.dirname(__FILE__))
|
11
|
+
CLIENT_SCRIPT = File.join(ROOT, 'faye-min.js')
|
12
|
+
|
13
|
+
BAYEUX_VERSION = '1.0'
|
14
|
+
ID_LENGTH = 128
|
15
|
+
JSONP_CALLBACK = 'jsonpcallback'
|
16
|
+
CONNECTION_TYPES = %w[long-polling callback-polling]
|
17
|
+
|
18
|
+
%w[grammar server channel connection error].each do |lib|
|
19
|
+
require File.join(ROOT, 'faye', lib)
|
20
|
+
end
|
21
|
+
|
22
|
+
autoload :RackAdapter, File.join(ROOT, 'faye', 'rack_adapter')
|
23
|
+
|
24
|
+
def self.random(bitlength = ID_LENGTH)
|
25
|
+
field = 2 ** bitlength
|
26
|
+
strlen = bitlength / 4
|
27
|
+
("%0#{strlen}s" % rand(field).to_s(16)).gsub(' ', '0')
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
data/lib/faye/channel.rb
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
module Faye
|
2
|
+
class Channel
|
3
|
+
HANDSHAKE = '/meta/handshake'
|
4
|
+
CONNECT = '/meta/connect'
|
5
|
+
SUBSCRIBE = '/meta/subscribe'
|
6
|
+
UNSUBSCRIBE = '/meta/unsubscribe'
|
7
|
+
DISCONNECT = '/meta/disconnect'
|
8
|
+
|
9
|
+
META = :meta
|
10
|
+
SERVICE = :service
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def valid?(name)
|
14
|
+
Grammar::CHANNEL_NAME =~ name or
|
15
|
+
Grammar::CHANNEL_PATTERN =~ name
|
16
|
+
end
|
17
|
+
|
18
|
+
def parse(name)
|
19
|
+
return nil unless valid?(name)
|
20
|
+
name.split('/')[1..-1].map { |s| s.to_sym }
|
21
|
+
end
|
22
|
+
|
23
|
+
def meta?(name)
|
24
|
+
segments = parse(name)
|
25
|
+
segments ? (segments.first == META) : nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def service?(name)
|
29
|
+
segments = parse(name)
|
30
|
+
segments ? (segments.first == SERVICE) : nil
|
31
|
+
end
|
32
|
+
|
33
|
+
def subscribable?(name)
|
34
|
+
return nil unless valid?(name)
|
35
|
+
not meta?(name) and not service?(name)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
include Observable
|
40
|
+
attr_reader :name
|
41
|
+
|
42
|
+
def initialize(name)
|
43
|
+
@name = name
|
44
|
+
end
|
45
|
+
|
46
|
+
def <<(message)
|
47
|
+
changed(true)
|
48
|
+
notify_observers(message)
|
49
|
+
end
|
50
|
+
|
51
|
+
class Tree
|
52
|
+
include Enumerable
|
53
|
+
attr_accessor :value
|
54
|
+
|
55
|
+
def initialize(value = nil)
|
56
|
+
@value = value
|
57
|
+
@children = {}
|
58
|
+
end
|
59
|
+
|
60
|
+
def each_child
|
61
|
+
@children.each { |key, subtree| yield(key, subtree) }
|
62
|
+
end
|
63
|
+
|
64
|
+
def each(prefix = [], &block)
|
65
|
+
each_child { |path, subtree| subtree.each(prefix + [path], &block) }
|
66
|
+
yield(prefix, @value) unless @value.nil?
|
67
|
+
end
|
68
|
+
|
69
|
+
def [](name)
|
70
|
+
subtree = traverse(name)
|
71
|
+
subtree ? subtree.value : nil
|
72
|
+
end
|
73
|
+
|
74
|
+
def []=(name, value)
|
75
|
+
subtree = traverse(name, true)
|
76
|
+
subtree.value = value unless subtree.nil?
|
77
|
+
end
|
78
|
+
|
79
|
+
def traverse(path, create_if_absent = false)
|
80
|
+
path = Channel.parse(path) if String === path
|
81
|
+
|
82
|
+
return nil if path.nil?
|
83
|
+
return self if path.empty?
|
84
|
+
|
85
|
+
subtree = @children[path.first]
|
86
|
+
return nil if subtree.nil? and not create_if_absent
|
87
|
+
subtree = @children[path.first] = self.class.new if subtree.nil?
|
88
|
+
|
89
|
+
subtree.traverse(path[1..-1], create_if_absent)
|
90
|
+
end
|
91
|
+
|
92
|
+
def glob(path = [])
|
93
|
+
path = Channel.parse(path) if String === path
|
94
|
+
|
95
|
+
return [] if path.nil?
|
96
|
+
return @value.nil? ? [] : [@value] if path.empty?
|
97
|
+
|
98
|
+
if path == [:*]
|
99
|
+
return @children.inject([]) do |list, (key, subtree)|
|
100
|
+
list << subtree.value unless subtree.value.nil?
|
101
|
+
list
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
if path == [:**]
|
106
|
+
list = map { |key, value| value }
|
107
|
+
list.pop
|
108
|
+
return list
|
109
|
+
end
|
110
|
+
|
111
|
+
list = @children.values_at(path.first, :*).
|
112
|
+
compact.
|
113
|
+
map { |t| t.glob(path[1..-1]) }
|
114
|
+
|
115
|
+
list << @children[:**].value if @children[:**]
|
116
|
+
list.flatten
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module Faye
|
2
|
+
class Connection
|
3
|
+
include EventMachine::Deferrable
|
4
|
+
include Observable
|
5
|
+
|
6
|
+
extend Forwardable
|
7
|
+
def_delegators :EventMachine, :add_timer, :cancel_timer
|
8
|
+
|
9
|
+
MAX_DELAY = 0.1
|
10
|
+
INTERVAL = 1.0
|
11
|
+
TIMEOUT = 60.0
|
12
|
+
|
13
|
+
attr_reader :id
|
14
|
+
|
15
|
+
def initialize(id, options = {})
|
16
|
+
@id = id
|
17
|
+
@options = options
|
18
|
+
@channels = Set.new
|
19
|
+
@inbox = Set.new
|
20
|
+
end
|
21
|
+
|
22
|
+
def timeout
|
23
|
+
@options[:timeout] || TIMEOUT
|
24
|
+
end
|
25
|
+
|
26
|
+
def subscribe(channel)
|
27
|
+
channel.add_observer(self) if @channels.add?(channel)
|
28
|
+
end
|
29
|
+
|
30
|
+
def unsubscribe(channel)
|
31
|
+
return @channels.each(&method(:unsubscribe)) if channel == :all
|
32
|
+
return unless @channels.member?(channel)
|
33
|
+
@channels.delete(channel)
|
34
|
+
channel.delete_observer(self)
|
35
|
+
end
|
36
|
+
|
37
|
+
def update(event)
|
38
|
+
@inbox.add(event)
|
39
|
+
begin_delivery_timeout! if @connected
|
40
|
+
end
|
41
|
+
|
42
|
+
def connect(&block)
|
43
|
+
callback(&block)
|
44
|
+
return if @connected
|
45
|
+
|
46
|
+
@mark_for_deletion = false
|
47
|
+
@connected = true
|
48
|
+
|
49
|
+
begin_delivery_timeout! unless @inbox.empty?
|
50
|
+
begin_connection_timeout!
|
51
|
+
end
|
52
|
+
|
53
|
+
def flush!
|
54
|
+
return unless @connected
|
55
|
+
release_connection!
|
56
|
+
|
57
|
+
events = @inbox.entries
|
58
|
+
@inbox = Set.new
|
59
|
+
|
60
|
+
set_deferred_status(:succeeded, events)
|
61
|
+
set_deferred_status(:deferred)
|
62
|
+
end
|
63
|
+
|
64
|
+
def disconnect!
|
65
|
+
unsubscribe(:all)
|
66
|
+
flush!
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def begin_delivery_timeout!
|
72
|
+
return unless @connected and not @inbox.empty? and @delivery_timeout.nil?
|
73
|
+
@delivery_timeout = add_timer(MAX_DELAY) { flush! }
|
74
|
+
end
|
75
|
+
|
76
|
+
def begin_connection_timeout!
|
77
|
+
return unless @connected and @connection_timeout.nil?
|
78
|
+
@connection_timeout = add_timer(timeout) { flush! }
|
79
|
+
end
|
80
|
+
|
81
|
+
def release_connection!
|
82
|
+
if @connection_timeout
|
83
|
+
cancel_timer(@connection_timeout)
|
84
|
+
@connection_timeout = nil
|
85
|
+
end
|
86
|
+
|
87
|
+
if @delivery_timeout
|
88
|
+
cancel_timer(@delivery_timeout)
|
89
|
+
@delivery_timeout = nil
|
90
|
+
end
|
91
|
+
|
92
|
+
@connected = false
|
93
|
+
schedule_for_deletion!
|
94
|
+
end
|
95
|
+
|
96
|
+
def schedule_for_deletion!
|
97
|
+
return if @mark_for_deletion
|
98
|
+
@mark_for_deletion = true
|
99
|
+
|
100
|
+
add_timer(10 * INTERVAL) do
|
101
|
+
if @mark_for_deletion
|
102
|
+
changed(true)
|
103
|
+
notify_observers(:stale_client, self)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
data/lib/faye/error.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
module Faye
|
2
|
+
class Error
|
3
|
+
|
4
|
+
def self.parse(string)
|
5
|
+
return nil unless Grammar::ERROR =~ string
|
6
|
+
parts = string.split(':')
|
7
|
+
args = parts[1].split(',')
|
8
|
+
new(parts[0].to_i, args, parts[2])
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.method_missing(type, *args)
|
12
|
+
code = const_get(type.to_s.upcase)
|
13
|
+
new(code[0], args, code[1]).to_s
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :code, :args, :message
|
17
|
+
|
18
|
+
def initialize(code, args, message)
|
19
|
+
@code = code
|
20
|
+
@args = args
|
21
|
+
@message = message
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
"#{ @code }:#{ @args * ',' }:#{ @message }"
|
26
|
+
end
|
27
|
+
|
28
|
+
# http://code.google.com/p/cometd/wiki/BayeuxCodes
|
29
|
+
VERSION_MISMATCH = [300, 'Version mismatch']
|
30
|
+
CONNTYPE_MISMATCH = [301, 'Connection types not supported']
|
31
|
+
EXT_MISMATCH = [302, 'Extension mismatch']
|
32
|
+
BAD_REQUEST = [400, 'Bad request']
|
33
|
+
CLIENT_UNKNOWN = [401, 'Unknown client']
|
34
|
+
PARAMETER_MISSING = [402, 'Missing required parameter']
|
35
|
+
CHANNEL_FORBIDDEN = [403, 'Forbidden channel']
|
36
|
+
CHANNEL_UNKNOWN = [404, 'Unknown channel']
|
37
|
+
CHANNEL_INVALID = [405, 'Invalid channel']
|
38
|
+
EXT_UNKNOWN = [406, 'Unknown extension']
|
39
|
+
PUBLISH_FAILED = [407, 'Failed to publish']
|
40
|
+
SERVER_ERROR = [500, 'Internal server error']
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
data/lib/faye/grammar.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
module Faye
|
2
|
+
module Grammar
|
3
|
+
|
4
|
+
def self.rule(&block)
|
5
|
+
source = instance_eval(&block)
|
6
|
+
%r{^#{string(source)}$}
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.choice(*list)
|
10
|
+
'(' + list.map(&method(:string)) * '|' + ')'
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.repeat(*pattern)
|
14
|
+
'(' + string(pattern) + ')*'
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.oneormore(*pattern)
|
18
|
+
'(' + string(pattern) + ')+'
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.string(item)
|
22
|
+
return item.map(&method(:string)) * '' if Array === item
|
23
|
+
String === item ? item : item.source.gsub(/^\^/, '').gsub(/\$$/, '')
|
24
|
+
end
|
25
|
+
|
26
|
+
LOWALPHA = rule {[ '[a-z]' ]}
|
27
|
+
UPALPHA = rule {[ '[A-Z]' ]}
|
28
|
+
ALPHA = rule {[ choice(LOWALPHA, UPALPHA) ]}
|
29
|
+
DIGIT = rule {[ '[0-9]' ]}
|
30
|
+
ALPHANUM = rule {[ choice(ALPHA, DIGIT) ]}
|
31
|
+
MARK = rule {[ choice(*%w[\\- \\_ \\! \\~ \\( \\) \\$ \\@]) ]}
|
32
|
+
STRING = rule {[ repeat(choice(ALPHANUM, MARK, ' ', '\\/', '\\*', '\\.')) ]}
|
33
|
+
TOKEN = rule {[ oneormore(choice(ALPHANUM, MARK)) ]}
|
34
|
+
INTEGER = rule {[ oneormore(DIGIT) ]}
|
35
|
+
|
36
|
+
CHANNEL_SEGMENT = rule {[ TOKEN ]}
|
37
|
+
CHANNEL_SEGMENTS = rule {[ CHANNEL_SEGMENT, repeat('\\/', CHANNEL_SEGMENT) ]}
|
38
|
+
CHANNEL_NAME = rule {[ '\\/', CHANNEL_SEGMENTS ]}
|
39
|
+
|
40
|
+
WILD_CARD = rule {[ '\\*{1,2}' ]}
|
41
|
+
CHANNEL_PATTERN = rule {[ repeat('\\/', CHANNEL_SEGMENT), '\\/', WILD_CARD ]}
|
42
|
+
|
43
|
+
VERSION_ELEMENT = rule {[ ALPHANUM, repeat(choice(ALPHANUM, '\\-', '\\_')) ]}
|
44
|
+
VERSION = rule {[ INTEGER, repeat('\\.', VERSION_ELEMENT) ]}
|
45
|
+
|
46
|
+
CLIENT_ID = rule {[ oneormore(ALPHANUM) ]}
|
47
|
+
|
48
|
+
ID = rule {[ oneormore(ALPHANUM) ]}
|
49
|
+
|
50
|
+
ERROR_MESSAGE = rule {[ STRING ]}
|
51
|
+
ERROR_ARGS = rule {[ STRING, repeat(',', STRING) ]}
|
52
|
+
ERROR_CODE = rule {[ DIGIT, DIGIT, DIGIT ]}
|
53
|
+
ERROR = rule {[ choice(string([ERROR_CODE, ':', ERROR_ARGS, ':', ERROR_MESSAGE]),
|
54
|
+
string([ERROR_CODE, ':', ':', ERROR_MESSAGE])) ]}
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rack'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Faye
|
6
|
+
class RackAdapter
|
7
|
+
DEFAULT_ENDPOINT = '/bayeux'
|
8
|
+
|
9
|
+
TYPE_JSON = {'Content-Type' => 'text/json'}
|
10
|
+
TYPE_SCRIPT = {'Content-Type' => 'text/javascript'}
|
11
|
+
TYPE_TEXT = {'Content-Type' => 'text/plain'}
|
12
|
+
|
13
|
+
def initialize(app = nil, options = nil)
|
14
|
+
@app = app if app.respond_to?(:call)
|
15
|
+
@options = [app, options].grep(Hash).first || {}
|
16
|
+
|
17
|
+
@endpoint = @options[:mount] || DEFAULT_ENDPOINT
|
18
|
+
@script = @endpoint + '.js'
|
19
|
+
@server = Server.new(@options)
|
20
|
+
end
|
21
|
+
|
22
|
+
def call(env)
|
23
|
+
request = Rack::Request.new(env)
|
24
|
+
case request.path_info
|
25
|
+
|
26
|
+
when @endpoint then
|
27
|
+
message = JSON.parse(request.params['message'])
|
28
|
+
jsonp = request.params['jsonp'] || JSONP_CALLBACK
|
29
|
+
type = request.get? ? TYPE_SCRIPT : TYPE_JSON
|
30
|
+
response = nil
|
31
|
+
|
32
|
+
@server.process(message, false) do |replies|
|
33
|
+
response = JSON.unparse(replies)
|
34
|
+
response = "#{ jsonp }(#{ response });" if request.get?
|
35
|
+
end
|
36
|
+
|
37
|
+
# TODO support Thin's async responses
|
38
|
+
sleep(0.1) while response.nil?
|
39
|
+
[200, type, [response]]
|
40
|
+
|
41
|
+
when @script then
|
42
|
+
[200, TYPE_SCRIPT, File.new(CLIENT_SCRIPT)]
|
43
|
+
|
44
|
+
else
|
45
|
+
env['faye.server'] = @server
|
46
|
+
@app ? @app.call(env) :
|
47
|
+
[404, TYPE_TEXT, ["Sure you're not looking for #{@endpoint} ?"]]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
data/lib/faye/server.rb
ADDED
@@ -0,0 +1,231 @@
|
|
1
|
+
module Faye
|
2
|
+
class Server
|
3
|
+
def initialize(options = {})
|
4
|
+
@options = options
|
5
|
+
@channels = Channel::Tree.new
|
6
|
+
@clients = {}
|
7
|
+
Thread.new { EventMachine.run } unless EventMachine.reactor_running?
|
8
|
+
end
|
9
|
+
|
10
|
+
def process(messages, local = false, &callback)
|
11
|
+
messages = [messages].flatten
|
12
|
+
processed, responses = 0, []
|
13
|
+
|
14
|
+
messages.each do |message|
|
15
|
+
handle(message, local) do |reply|
|
16
|
+
reply = [reply].flatten
|
17
|
+
responses.concat(reply)
|
18
|
+
processed += 1
|
19
|
+
callback[responses] if processed == messages.size
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def handle(message, local = false, &callback)
|
25
|
+
client_id = message['clientId']
|
26
|
+
channel = message['channel']
|
27
|
+
|
28
|
+
if Channel.meta?(channel)
|
29
|
+
response = __send__(Channel.parse(channel)[1], message, local)
|
30
|
+
|
31
|
+
client_id ||= response['clientId']
|
32
|
+
response['advice'] ||= {}
|
33
|
+
response['advice']['reconnect'] ||= @clients.has_key?(client_id) ? 'retry' : 'handshake'
|
34
|
+
response['advice']['interval'] ||= Connection::INTERVAL * 1000
|
35
|
+
|
36
|
+
response['id'] = message['id']
|
37
|
+
|
38
|
+
return callback[response] unless response['channel'] == Channel::CONNECT and
|
39
|
+
response['successful'] == true
|
40
|
+
|
41
|
+
return connection(response['clientId']).connect do |events|
|
42
|
+
callback[[response] + events]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
return callback[[]] if message['clientId'].nil? or Channel.service?(channel)
|
47
|
+
|
48
|
+
@channels.glob(channel).each { |c| c << message }
|
49
|
+
|
50
|
+
callback[ { 'channel' => channel,
|
51
|
+
'successful' => true,
|
52
|
+
'id' => message['id'] } ]
|
53
|
+
end
|
54
|
+
|
55
|
+
# MUST contain * version
|
56
|
+
# * supportedConnectionTypes
|
57
|
+
# MAY contain * minimumVersion
|
58
|
+
# * ext
|
59
|
+
# * id
|
60
|
+
def handshake(message, local = false)
|
61
|
+
response = { 'channel' => Channel::HANDSHAKE,
|
62
|
+
'version' => BAYEUX_VERSION,
|
63
|
+
'supportedConnectionTypes' => CONNECTION_TYPES,
|
64
|
+
'id' => message['id'] }
|
65
|
+
|
66
|
+
response['error'] = Error.parameter_missing('version') if message['version'].nil?
|
67
|
+
|
68
|
+
client_conns = message['supportedConnectionTypes']
|
69
|
+
if client_conns
|
70
|
+
common_conns = client_conns.select { |c| CONNECTION_TYPES.include?(c) }
|
71
|
+
response['error'] = Error.conntype_mismatch(*client_conns) if common_conns.empty?
|
72
|
+
else
|
73
|
+
response['error'] = Error.parameter_missing('supportedConnectionTypes')
|
74
|
+
end
|
75
|
+
|
76
|
+
response['successful'] = response['error'].nil?
|
77
|
+
return response unless response['successful']
|
78
|
+
|
79
|
+
response['clientId'] = generate_id
|
80
|
+
response
|
81
|
+
end
|
82
|
+
|
83
|
+
# MUST contain * clientId
|
84
|
+
# * connectionType
|
85
|
+
# MAY contain * ext
|
86
|
+
# * id
|
87
|
+
def connect(message, local = false)
|
88
|
+
response = { 'channel' => Channel::CONNECT,
|
89
|
+
'id' => message['id'] }
|
90
|
+
|
91
|
+
client_id = message['clientId']
|
92
|
+
client = client_id ? @clients[client_id] : nil
|
93
|
+
|
94
|
+
response['error'] = Error.client_unknown(client_id) if client.nil?
|
95
|
+
response['error'] = Error.parameter_missing('clientId') if client_id.nil?
|
96
|
+
response['error'] = Error.parameter_missing('connectionType') if message['connectionType'].nil?
|
97
|
+
|
98
|
+
response['successful'] = response['error'].nil?
|
99
|
+
return response unless response['successful']
|
100
|
+
|
101
|
+
response['clientId'] = client.id
|
102
|
+
response
|
103
|
+
end
|
104
|
+
|
105
|
+
# MUST contain * clientId
|
106
|
+
# MAY contain * ext
|
107
|
+
# * id
|
108
|
+
def disconnect(message, local = false)
|
109
|
+
response = { 'channel' => Channel::DISCONNECT,
|
110
|
+
'id' => message['id'] }
|
111
|
+
|
112
|
+
client_id = message['clientId']
|
113
|
+
client = client_id ? @clients[client_id] : nil
|
114
|
+
|
115
|
+
response['error'] = Error.client_unknown(client_id) if client.nil?
|
116
|
+
response['error'] = Error.parameter_missing('clientId') if client_id.nil?
|
117
|
+
|
118
|
+
response['successful'] = response['error'].nil?
|
119
|
+
return response unless response['successful']
|
120
|
+
|
121
|
+
destroy_client(client)
|
122
|
+
|
123
|
+
response['clientId'] = client_id
|
124
|
+
response
|
125
|
+
end
|
126
|
+
|
127
|
+
# MUST contain * clientId
|
128
|
+
# * subscription
|
129
|
+
# MAY contain * ext
|
130
|
+
# * id
|
131
|
+
def subscribe(message, local = false)
|
132
|
+
response = { 'channel' => Channel::SUBSCRIBE,
|
133
|
+
'clientId' => message['clientId'],
|
134
|
+
'id' => message['id'] }
|
135
|
+
|
136
|
+
client_id = message['clientId']
|
137
|
+
client = client_id ? @clients[client_id] : nil
|
138
|
+
|
139
|
+
subscription = message['subscription']
|
140
|
+
subscription = [subscription].flatten
|
141
|
+
|
142
|
+
response['error'] = Error.client_unknown(client_id) if client.nil?
|
143
|
+
response['error'] = Error.parameter_missing('clientId') if client_id.nil?
|
144
|
+
response['error'] = Error.parameter_missing('subscription') if message['subscription'].nil?
|
145
|
+
|
146
|
+
response['subscription'] = subscription.compact
|
147
|
+
|
148
|
+
subscription.each do |channel|
|
149
|
+
next if response['error']
|
150
|
+
response['error'] = Error.channel_forbidden(channel) unless Channel.subscribable?(channel)
|
151
|
+
response['error'] = Error.channel_invalid(channel) unless Channel.valid?(channel)
|
152
|
+
|
153
|
+
next if response['error']
|
154
|
+
channel = @channels[channel] ||= Channel.new(channel)
|
155
|
+
client.subscribe(channel)
|
156
|
+
end
|
157
|
+
|
158
|
+
response['successful'] = response['error'].nil?
|
159
|
+
response
|
160
|
+
end
|
161
|
+
|
162
|
+
# MUST contain * clientId
|
163
|
+
# * subscription
|
164
|
+
# MAY contain * ext
|
165
|
+
# * id
|
166
|
+
def unsubscribe(message, local = false)
|
167
|
+
response = { 'channel' => Channel::UNSUBSCRIBE,
|
168
|
+
'clientId' => message['clientId'],
|
169
|
+
'id' => message['id'] }
|
170
|
+
|
171
|
+
client_id = message['clientId']
|
172
|
+
client = client_id ? @clients[client_id] : nil
|
173
|
+
|
174
|
+
subscription = message['subscription']
|
175
|
+
subscription = [subscription].flatten
|
176
|
+
|
177
|
+
response['error'] = Error.client_unknown(client_id) if client.nil?
|
178
|
+
response['error'] = Error.parameter_missing('clientId') if client_id.nil?
|
179
|
+
response['error'] = Error.parameter_missing('subscription') if message['subscription'].nil?
|
180
|
+
|
181
|
+
response['subscription'] = subscription.compact
|
182
|
+
|
183
|
+
subscription.each do |channel|
|
184
|
+
next if response['error']
|
185
|
+
|
186
|
+
if not Channel.valid?(channel)
|
187
|
+
response['error'] = Error.channel_invalid(channel)
|
188
|
+
next
|
189
|
+
end
|
190
|
+
|
191
|
+
channel = @channels[channel]
|
192
|
+
client.unsubscribe(channel) if channel
|
193
|
+
end
|
194
|
+
|
195
|
+
response['successful'] = response['error'].nil?
|
196
|
+
response
|
197
|
+
end
|
198
|
+
|
199
|
+
# Notifies the server of stale connections that should be deleted
|
200
|
+
def update(message, client)
|
201
|
+
return unless message == :stale_client
|
202
|
+
destroy_client(client)
|
203
|
+
end
|
204
|
+
|
205
|
+
def client_ids
|
206
|
+
@clients.keys
|
207
|
+
end
|
208
|
+
|
209
|
+
private
|
210
|
+
|
211
|
+
def generate_id
|
212
|
+
id = Faye.random
|
213
|
+
id = Faye.random while @clients.has_key?(id)
|
214
|
+
connection(id).id
|
215
|
+
end
|
216
|
+
|
217
|
+
def connection(id)
|
218
|
+
return @clients[id] if @clients.has_key?(id)
|
219
|
+
client = Connection.new(id, @options)
|
220
|
+
client.add_observer(self)
|
221
|
+
@clients[id] = client
|
222
|
+
end
|
223
|
+
|
224
|
+
def destroy_client(client)
|
225
|
+
client.disconnect!
|
226
|
+
client.delete_observer(self)
|
227
|
+
@clients.delete(client.id)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|