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/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
+
@@ -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
+
@@ -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
+
@@ -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
+