faye 0.1.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of faye might be problematic. Click here for more details.

@@ -1 +1,29 @@
1
+ === 0.2.2 / 2010-02-10
2
+
3
+ * Kick out requests with malformed JSON as 400s
4
+
5
+
6
+ === 0.2.1 / 2010-02-04
7
+
8
+ * Fix server-side flushing of callback-polling connections
9
+ * Backend can be used cross-domain if running on Node or Thin
10
+
11
+
12
+ === 0.2.0 / 2010-02-02
13
+
14
+ * Port server to JavaScript with an adapter for Node.js
15
+ * Support Thin's async responses in the Ruby version for complete non-blocking
16
+ * Fix some minor client-side bugs in transport choice
17
+
18
+
19
+ === 0.1.1 / 2009-07-26
20
+
21
+ * Fix a broken client build
22
+
23
+
24
+ === 0.1.0 / 2009-06-15
25
+
26
+ * Ruby Bayeux server and Rack adapter
27
+ * Internally evented using EventMachine, web frontend blocks
28
+ * JavaScript client with long-polling and callback-polling
1
29
 
data/Jakefile CHANGED
@@ -1,2 +1,13 @@
1
1
  require File.join('lib', 'faye')
2
+ DIR = File.dirname(__FILE__)
3
+
4
+ jake_hook :build_complete do |build|
5
+ FileUtils.cp build.package('faye-client').build_path(:min), DIR + '/lib/faye-client-min.js'
6
+ FileUtils.cp build.package('faye-client').build_path(:min), DIR + '/examples/node/faye-client-min.js'
7
+ FileUtils.cp build.package(:faye).build_path(:src), DIR + '/examples/node/faye.js'
8
+
9
+ [['faye-client', :src], [:faye, :min], [:core, :src], [:core, :min]].each do |(pkg,typ)|
10
+ FileUtils.rm build.package(pkg).build_path(typ)
11
+ end
12
+ end
2
13
 
@@ -4,16 +4,21 @@ README.txt
4
4
  Rakefile
5
5
  jake.yml
6
6
  Jakefile
7
- client/util/class.js
8
- client/util/event.js
9
- client/util/json.js
10
- client/util/uri.js
11
- client/util/xhr.js
12
- client/faye.js
13
- client/transport.js
14
- client/client.js
15
- client/channel.js
16
- lib/faye-min.js
7
+ build/faye.js
8
+ build/faye-client-min.js
9
+ examples/README.rdoc
10
+ examples/shared/public/index.html
11
+ examples/shared/public/favicon.ico
12
+ examples/shared/public/jquery.js
13
+ examples/shared/public/robots.txt
14
+ examples/shared/public/soapbox.js
15
+ examples/shared/public/style.css
16
+ examples/node/app.js
17
+ examples/node/faye.js
18
+ examples/node/faye-client-min.js
19
+ examples/rack/app.rb
20
+ examples/rack/config.ru
21
+ lib/faye-client-min.js
17
22
  lib/faye.rb
18
23
  lib/faye/channel.rb
19
24
  lib/faye/connection.rb
@@ -21,13 +26,6 @@ lib/faye/error.rb
21
26
  lib/faye/grammar.rb
22
27
  lib/faye/rack_adapter.rb
23
28
  lib/faye/server.rb
24
- examples/soapbox/README
25
- examples/soapbox/app.rb
26
- examples/soapbox/config.ru
27
- examples/soapbox/public/jquery.js
28
- examples/soapbox/public/soapbox.js
29
- examples/soapbox/public/style.css
30
- examples/soapbox/views/index.erb
31
29
  test/test_channel.rb
32
30
  test/test_grammar.rb
33
31
  test/test_server.rb
data/README.txt CHANGED
@@ -2,54 +2,64 @@
2
2
 
3
3
  * http://github.com/jcoglan/faye
4
4
 
5
- Faye is an implementation of the Bayeux protocol in Ruby. It includes a
6
- server backend and an adapter for Rack, and a self-contained JavaScript
7
- client. Bayeux is a protocol designed to facilitate Comet, also known as
8
- 'server push' or 'Ajax push', a technique for pushing messages from the
9
- server to the client to minimize latency.
5
+ Faye provides dirt-simple Comet messaging based on the Bayeux protocol.
6
+ It ships with a self-contained client for use in the browser and two
7
+ message-routing backends; one for Node.js and one for Rack.
10
8
 
11
- NOTE this is not currently intended for large-scale use. It is being
12
- developed mostly as a tool for scripting multiple browsers and automating
13
- JavaScript testing. However, use of a well-known protocol should allow
14
- it to be repurposed fairly easily.
15
9
 
10
+ == Introduction
16
11
 
17
- == Usage
12
+ Comet is an umbrella term for a collection of techniques that let web
13
+ servers push data to clients, allowing for low-latency data transfer.
14
+ Bayeux is a protocol designed to support publish/subscribe-style messaging
15
+ between web clients and servers. Clients can subscribe to named channels
16
+ and publish messages, each message being distributed to all clients
17
+ subscribed to its target channel. See http://svn.cometd.com/trunk/bayeux/bayeux.html
18
18
 
19
- Faye can be installed as middleware in front of any Rack application. For
20
- example, here's a <tt>config.ru</tt> for running it with Sinatra:
19
+ Faye's messaging backend was originally developed in Ruby for a toy
20
+ project of mine, but was not written to scale across multiple processes
21
+ as required for scaling a Ruby server. It has since been ported to Node.js
22
+ which should handle the required connection load more gracefully.
21
23
 
22
- require 'rubygems'
23
- require 'faye'
24
- require 'sinatra'
25
- require 'path/to/sinatra/app'
26
-
27
- use Faye::RackAdapter, :mount => '/comet',
28
- :timeout => 30
29
-
30
- run Sinatra::Application
24
+ The two backends are architecturally identical, using evented messaging
25
+ throughout and maintaining subscription data in memory. Node.js allows
26
+ for far more concurrent connections per process than a typical Ruby server,
27
+ and will get you more mileage from a single process.
28
+
29
+
30
+ == Installation
31
+
32
+ The JavaScript client and Node.js server are in the +build+ directory. Just
33
+ copy them onto your machine and <tt>require('path/to/faye')</tt>.
34
+
35
+ The Rack server is distributed as a Ruby gem:
36
+
37
+ sudo gem install faye
31
38
 
32
- This starts a Comet endpoint at <tt>http://localhost:9292/comet</tt> with
33
- the client script at <tt>http://localhost:9292/comet.js</tt>. The +timeout+
34
- option sets how long (in seconds) a long-polling request will wait before
35
- timing out; this must be less than the timeout set on your frontend web server
36
- so that the Comet server can send a response before Apache (for example)
37
- closes the connection.
38
39
 
39
- In your front-end code, set up the client as follows:
40
+ == Using the client
41
+
42
+ Both backends allow you to specify a 'mount point' that the Comet server
43
+ accepts requests on. Say you set this to <tt>/comet</tt>, the client script
44
+ will be available from <tt>/comet.js</tt> and should connect to <tt>/comet</tt>.
45
+
46
+ You should set up the client as follows:
40
47
 
41
48
  <script type="text/javascript" src="/comet.js"></script>
42
49
 
43
50
  <script type="text/javascript">
44
- CometClient = new Faye.Client('/comet');
45
- CometClient.connect();
51
+ CometClient = new Faye.Client('/comet');
52
+ CometClient.connect();
46
53
  </script>
47
54
 
48
- This client object can then be used to publish and subscribe to named
49
- channels as follows:
55
+ Take care only to have one instance of the client per page; since each one
56
+ opens a long-running request you will hit the two-requests-per-host limit
57
+ and block all other Ajax calls if you use more than one client.
58
+
59
+ This client object can be used to publish and subscribe to named channels:
50
60
 
51
61
  CometClient.subscribe('/path/to/channel', function(message) {
52
- // process received message
62
+ // process received message object
53
63
  });
54
64
 
55
65
  CometClient.publish('/some/other/channel', {foo: 'bar'});
@@ -60,36 +70,127 @@ Channel names must be formatted as absolute path names as shown. Channels
60
70
  beginning with <tt>/meta/</tt> are reserved for use by the messaging protocol
61
71
  and may not be subscribed to.
62
72
 
73
+ The client can also be used cross-domain if connecting to a backend that
74
+ supports callback polling (see below under 'Transports'). Just pass in the
75
+ full path to the endpoint including the domain. Faye figures out whether the
76
+ server is on the same domain and uses an appropriate transport.
77
+
78
+ CometClient = new Faye.Client('http://example.com/comet');
79
+
80
+
81
+ === Transports
82
+
83
+ The Bayeux spec defines several transport mechanisms for clients to establish
84
+ low-latency connections with the server, the two required types being
85
+ <tt>long-polling</tt> and <tt>callback-polling</tt>.
86
+
87
+ <tt>long-polling</tt> is where the client makes an <tt>XMLHttpRequest</tt>
88
+ to the server, and the server waits until it has new messages for that client
89
+ before it returns a response. Faye's client and server backends all support
90
+ this transport. Since it uses XHR, the Comet endpoint must be on the same
91
+ domain as the client page.
92
+
93
+ <tt>callback-polling</tt> involves the client using JSON-P to make the request.
94
+ The server wraps its JSON responses in a JavaScript function call that the
95
+ client then executes. This transport does not require the client and server
96
+ to be on the same domain. Faye's client supports this transport, as does the
97
+ Node.js backend. The Rack backend supports it if running under Thin.
98
+
99
+
100
+ == Using the backend
101
+
102
+ Both the Node.js and Rack backends have identical architectures and are
103
+ designed to be easily plugged into other web services. For Rack the adapter
104
+ is explicitly designed as middleware, while for Node.js the adapter is
105
+ a simple object you can manually offload requests to.
106
+
107
+ The backends provide a service for routing messages between clients; no
108
+ server-side programming is needed to control them, just start them up
109
+ and they'll sit there merrily chewing through requests. They are both
110
+ currently single-process since they hold all channel subscriptions in
111
+ memory. This means, for example, that the Rack backend will not work
112
+ under Passenger since that spawns multiple Ruby processes to serve your
113
+ site.
63
114
 
64
- == Examples
115
+ Faye uses async messaging internally so nothing blocks while waiting for
116
+ new messages. If running under a Ruby web server (except Thin) the server
117
+ will block while waiting for a response from Faye. Thin supports async
118
+ responses and is a better choice for long-running concurrent connections.
65
119
 
66
- See demo apps in +examples+. If you've checked this out from the repo rather than
67
- installing the gem you will need to build the JavaScript client from source, this
68
- is done as follows:
120
+ Both backends support the following initialization options:
69
121
 
70
- sudo gem install jake
71
- jake -f
122
+ * +mount+ - the path at which the Comet service is accessible. e.g. if
123
+ set to <tt>/faye</tt>, the Comet endpoint will be at <tt>http://yoursite.com/faye</tt>
124
+ and the client script at <tt>http://yoursite.com/faye.js</tt>.
125
+ * +timeout+ - the maximum time (seconds) to hold a long-running request
126
+ open before returning a response. This must be smaller than the timeout
127
+ on your frontend webserver to make sure Faye sends a response before
128
+ the server kills the connection.
129
+
130
+ Usage examples and a demo app are in the +examples+ directory.
131
+
132
+
133
+ === Node.js backend
134
+
135
+ Here's a very simple Node web server that offloads requests to the Comet
136
+ service to Faye and deals with all other requests itself. The Faye object
137
+ returns +true+ or +false+ to indicate whether it handled the request.
138
+ You'll need <tt>faye.js</tt> and <tt>faye-client-min.js</tt> in the same
139
+ directory.
140
+
141
+ var http = require('http')
142
+ faye = require('./faye');
143
+
144
+ var comet = new faye.NodeAdapter({mount: '/comet', timeout: 45});
145
+
146
+ http.createServer(function(request, response) {
147
+ if (comet.call(request, response)) return;
148
+
149
+ response.sendHeader(200, {'Content-Type': 'text/plain'});
150
+ response.sendBody('Hello, non-Comet request!');
151
+ response.finish();
152
+
153
+ }).listen(9292);
154
+
155
+
156
+ === Rack backend
157
+
158
+ Faye can be installed as middleware in front of any Rack application. The
159
+ Rack backend uses EventMachine for asynchronous message distribution and
160
+ timeouts. It can run under any web server, though Thin is best placed for
161
+ handling long-running concurrent connections and supports async server
162
+ responses. Under other servers the request thread will block while waiting
163
+ for a response from Faye.
164
+
165
+ Here's a <tt>config.ru</tt> for running it with Sinatra:
166
+
167
+ require 'rubygems'
168
+ require 'faye'
169
+ require 'sinatra'
170
+ require 'path/to/sinatra/app'
171
+
172
+ use Faye::RackAdapter, :mount => '/comet',
173
+ :timeout => 25
174
+
175
+ run Sinatra::Application
176
+
177
+ This functions much the same as the Node.js example; Faye catches Comet
178
+ requests and deals with them, letting all other requests fall down the
179
+ stack of Rack middlewares.
72
180
 
73
181
 
74
182
  == To-do
75
183
 
76
184
  * Provide support for user-defined <tt>/service/*</tt> channels
77
- * Support Thin's async response
78
185
  * Allow server to scale to multiple nodes
79
186
  * Provide a server-side client
80
- * Console for scripting browsers from the command line
81
-
82
-
83
- == Installation
84
-
85
- sudo gem install hoe faye
86
187
 
87
188
 
88
189
  == License
89
190
 
90
191
  (The MIT License)
91
192
 
92
- Copyright (c) 2009 James Coglan
193
+ Copyright (c) 2009-2010 James Coglan
93
194
 
94
195
  Permission is hereby granted, free of charge, to any person obtaining
95
196
  a copy of this software and associated documentation files (the
@@ -109,3 +210,4 @@ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
109
210
  CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
110
211
  TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
111
212
  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
213
+
@@ -0,0 +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)&&a[d]!==b[d]){if(!a.hasOwnProperty(d)||c!==false)a[d]=b[d]}}return a};Faye.extend(Faye,{BAYEUX_VERSION:'1.0',VERSION:'0.2.2',JSONP_CALLBACK:'jsonpcallback',ID_LENGTH:128,CONNECTION_TYPES:["long-polling","callback-polling"],ENV:this,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,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])}}},size:function(a){var b=0;this.each(a,function(){b+=1});return b},random:function(){var a=Math.pow(2,this.ID_LENGTH);return(Math.random()*a).toString(16).replace(/0*$/,'')},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}}});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.Observable={on:function(a,b,c){this._1=this._1||{};var d=this._1[a]=this._1[a]||[];d.push([b,c])},stopObserving:function(a,b,c){if(!this._1||!this._1[a])return;if(!b){delete this._1[a];return}var d=this._1[a],f=d.length;while(f--){if(b&&d[f][0]!==b)continue;if(c&&d[f][1]!==c)continue;d.splice(f,1)}},fire:function(){var b=Array.prototype.slice.call(arguments),c=b.shift();if(!this._1||!this._1[c])return;Faye.each(this._1[c],function(a){a[0].apply(a[1],b.slice())})}};Faye.Channel=Faye.Class({initialize:function(a){this.__id=this._A=a},push:function(a){this.fire('message',a)}});Faye.extend(Faye.Channel.prototype,Faye.Observable);Faye.extend(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 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)},Tree:Faye.Class({initialize:function(a){this._3=a;this._5={}},eachChild:function(c,d){Faye.each(this._5,function(a,b){c.call(d,a,b)})},each:function(c,d,f){this.eachChild(function(a,b){a=c.concat(a);b.each(a,d,f)});if(this._3!==undefined)d.call(f,c,this._3)},map:function(c,d){var f=[];this.each([],function(a,b){f.push(c.call(d,a,b))});return f},get:function(a){var b=this.traverse(a);return b?b._3:null},set:function(a,b){var c=this.traverse(a,true);if(c)c._3=b},traverse:function(a,b){if(typeof a==='string')a=Faye.Channel.parse(a);if(a===null)return null;if(a.length===0)return this;var c=this._5[a[0]];if(!c&&!b)return null;if(!c)c=this._5[a[0]]=new Faye.Channel.Tree();return c.traverse(a.slice(1),b)},findOrCreate:function(a){var b=this.get(a);if(b)return b;b=new Faye.Channel(a);this.set(a,b);return b},glob:function(f){if(typeof f==='string')f=Faye.Channel.parse(f);if(f===null)return[];if(f.length===0)return(this._3===undefined)?[]:[this._3];var g=[];if(Faye.enumEqual(f,['*'])){Faye.each(this._5,function(a,b){if(b._3!==undefined)g.push(b._3)});return g}if(Faye.enumEqual(f,['**'])){g=this.map(function(a,b){return b});g.pop();return g}Faye.each(this._5,function(b,c){if(b!==f[0]&&b!=='*')return;var d=c.glob(f.slice(1));Faye.each(d,function(a){g.push(a)})});if(this._5['**'])g.push(this._5['**']._3);return g}})});Faye.Event={_a:[],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._a.push({_b:a,_h:b,_s:c,_c:d,_m:f})},detach:function(a,b,c,d){var f=this._a.length,g;while(f--){g=this._a[f];if((a&&a!==g._b)||(b&&b!==g._h)||(c&&c!==g._s)||(d&&d!==g._c))continue;if(g._b.removeEventListener)g._b.removeEventListener(g._h,g._m,false);else g._b.detachEvent('on'+g._h,g._m);this._a.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(){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(b,c){d=d.replace(c,function(a){if(a)g[b]=a;return''})};k('protocol',/^https?\:\/+/);k('hostname',/^[^\/\:]+/);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,b,c,d,f){var g=new this.Request(a,b,c,d,f);g.send();return g},getXhrObject:function(){return Faye.ENV.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest()},Request:Faye.Class({initialize:function(a,b,c,d,f){this._d=a.toUpperCase();this._6=Faye.URI.parse(b,c);this._t=(typeof d==='function')?{success:d}:d;this._c=f||null;this._2=null},send:function(){if(this._i)return;var a=this._6.pathname,b=this._6.queryString();if(this._d==='GET')a+='?'+b;var c=this._d==='POST'?b:'';this._i=true;this._2=Faye.XHR.getXhrObject();this._2.open(this._d,a,true);if(this._d==='POST')this._2.setRequestHeader('Content-Type','application/x-www-form-urlencoded');var d=this,f=function(){if(d._2.readyState!==4)return;if(g){clearInterval(g);g=null}Faye.Event.detach(Faye.ENV,'beforeunload',d.abort,d);d._i=false;d._u();d=null};var g=setInterval(f,10);Faye.Event.on(Faye.ENV,'beforeunload',this.abort,this);this._2.send(c)},abort:function(){this._2.abort()},_u:function(){var a=this._t;if(!a)return;return this.success()?a.success&&a.success.call(this._c,this):a.failure&&a.failure.call(this._c,this)},waiting:function(){return!!this._i},complete:function(){return this._2&&!this.waiting()},success:function(){if(!this.complete())return false;var a=this._2.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._2.responseText},status:function(){if(!this.complete())return null;return this._2.status}})};Faye.Transport=Faye.extend(Faye.Class({initialize:function(a,b){this._n=a;this._6=b},send:function(c,d,f){c={message:JSON.stringify(c)};return this.request(c,function(b){if(!d)return;Faye.each([].concat(b),function(a){d.call(f,a);if(a.advice)this._n._v(a.advice);if(a.data&&a.channel)this._n._w(a)},this)},this)}}),{get:function(a,b){var c=a._6;if(b===undefined)b=this.supportedConnectionTypes();var d=Faye.URI.parse(c).isLocal()?['long-polling','callback-polling']:['callback-polling'];var f=Faye.commonElement(d,b);if(!f)throw'Could not find a usable connection type for '+c;var g=this._j[f];return new g(a,c)},register:function(a,b){this._j[a]=b;b.prototype.connectionType=a},_j:{},supportedConnectionTypes:function(){var c=[],d;Faye.each(this._j,function(a,b){c.push(a)});return c}});Faye.XHRTransport=Faye.Class(Faye.Transport,{request:function(b,c,d){Faye.XHR.request('post',this._6,b,function(a){if(c)c.call(d,JSON.parse(a.text()))})}});Faye.Transport.register('long-polling',Faye.XHRTransport);Faye.JSONPTransport=Faye.extend(Faye.Class(Faye.Transport,{request:function(b,c,d){var f=document.getElementsByTagName('head')[0],g=document.createElement('script'),k=Faye.JSONPTransport.getCallbackName(),i=Faye.URI.parse(this._6,b);Faye.ENV[k]=function(a){Faye.ENV[k]=undefined;try{delete Faye.ENV[k]}catch(e){}f.removeChild(g);if(c)c.call(d,a)};i.params.jsonp=k;g.type='text/javascript';g.src=i.toURL();f.appendChild(g)}}),{_o:0,getCallbackName:function(){this._o+=1;return'__jsonp'+this._o+'__'}});Faye.Transport.register('callback-polling',Faye.JSONPTransport);Faye.Client=Faye.Class({_e:{},_x:{},_8:{},_y:{},_p:'handshake',_z:'retry',_q:'none',DEFAULT_ENDPOINT:'/bayeux',MAX_DELAY:0.1,INTERVAL:1000.0,initialize:function(a){this._6=a||this.DEFAULT_ENDPOINT;this._4=Faye.Transport.get(this);this._0=this._e;this._k=[];this._f=new Faye.Channel.Tree();this._7={reconnect:this._z,interval:this.INTERVAL};Faye.Event.on(Faye.ENV,'beforeunload',this.disconnect,this)},handshake:function(b,c){if(this._7.reconnect===this._q)return;if(this._0!==this._e)return;this._0=this._x;var d=this,f=this.generateId();this._4.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(b,c)},this._7.interval);return this._0=this._e}this._0=this._8;this._9=a.clientId;this._4=Faye.Transport.get(this,a.supportedConnectionTypes);if(b)b.call(c)},this)},connect:function(b,c){if(this._7.reconnect===this._q)return;if(this._7.reconnect===this._p||this._0===this._e)return this.handshake(function(){this.connect(b,c)},this);if(this._0!==this._8)return;if(this._g)return;this._g=this.generateId();var d=this;this._4.send({channel:Faye.Channel.CONNECT,clientId:this._9,connectionType:this._4.connectionType,id:this._g},function(a){if(a.id!==this._g)return;delete this._g;if(a.successful)this.connect();else setTimeout(function(){d.connect()},this._7.interval)},this);if(b)b.call(c)},disconnect:function(){if(this._0!==this._8)return;this._0=this._y;this._4.send({channel:Faye.Channel.DISCONNECT,clientId:this._9});this._f=new Faye.Channel.Tree()},subscribe:function(c,d,f){if(this._0!==this._8)return;c=[].concat(c);this._l(c);var g=this.generateId();this._4.send({channel:Faye.Channel.SUBSCRIBE,clientId:this._9,subscription:c,id:g},function(b){if(b.id!==g)return;if(!b.successful)return;c=[].concat(b.subscription);Faye.each(c,function(a){this._f.set(a,[d,f])},this)},this)},unsubscribe:function(c,d,f){if(this._0!==this._8)return;c=[].concat(c);this._l(c);var g=this.generateId();this._4.send({channel:Faye.Channel.UNSUBSCRIBE,clientId:this._9,subscription:c,id:g},function(b){if(b.id!==g)return;if(!b.successful)return;c=[].concat(b.subscription);Faye.each(c,function(a){this._f.set(a,null)},this)},this)},publish:function(a,b){if(this._0!==this._8)return;this._l([a]);this.enqueue({channel:a,data:b,clientId:this._9});if(this._r)return;var c=this;this._r=setTimeout(function(){delete c._r;c.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._k.push(a)},flush:function(){this._4.send(this._k);this._k=[]},_l:function(b){Faye.each(b,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+'"';})},_v:function(a){Faye.extend(this._7,a);if(this._7.reconnect===this._p)this._9=null},_w:function(b){var c=this._f.glob(b.channel);Faye.each(c,function(a){if(!a)return;a[0].call(a[1],b.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(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,k=j,i,h=b[a];if(h&&typeof h==='object'&&typeof h.toJSON==='function'){h=h.toJSON(a)}if(typeof m==='function'){h=m.call(b,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(c=0;c<g;c+=1){i[c]=q(c,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(c=0;c<g;c+=1){d=m[c];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,b,c){var d;j='';p='';if(typeof c==='number'){for(d=0;d<c;d+=1){p+=' '}}else if(typeof c==='string'){p=c}m=b;if(b&&typeof b!=='function'&&(typeof b!=='object'||typeof b.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,b){var c,d,f=a[b];if(f&&typeof f==='object'){for(c in f){if(Object.hasOwnProperty.call(f,c)){d=h(f,c);if(d!==undefined){f[c]=d}else{delete f[c]}}}}return k.call(a,b,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');}}}());
@@ -0,0 +1,868 @@
1
+ if (!this.Faye) Faye = {};
2
+
3
+ Faye.extend = function(dest, source, overwrite) {
4
+ if (!source) return dest;
5
+ for (var key in source) {
6
+ if (source.hasOwnProperty(key) && dest[key] !== source[key]) {
7
+ if (!dest.hasOwnProperty(key) || overwrite !== false)
8
+ dest[key] = source[key];
9
+ }
10
+ }
11
+ return dest;
12
+ };
13
+
14
+ Faye.extend(Faye, {
15
+ BAYEUX_VERSION: '1.0',
16
+ VERSION: '0.2.2',
17
+ JSONP_CALLBACK: 'jsonpcallback',
18
+ ID_LENGTH: 128,
19
+ CONNECTION_TYPES: ["long-polling", "callback-polling"],
20
+
21
+ ENV: this,
22
+
23
+ Grammar: {
24
+
25
+ LOWALPHA: /^[a-z]$/,
26
+
27
+ UPALPHA: /^[A-Z]$/,
28
+
29
+ ALPHA: /^([a-z]|[A-Z])$/,
30
+
31
+ DIGIT: /^[0-9]$/,
32
+
33
+ ALPHANUM: /^(([a-z]|[A-Z])|[0-9])$/,
34
+
35
+ MARK: /^(\-|\_|\!|\~|\(|\)|\$|\@)$/,
36
+
37
+ STRING: /^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*$/,
38
+
39
+ TOKEN: /^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+$/,
40
+
41
+ INTEGER: /^([0-9])+$/,
42
+
43
+ CHANNEL_SEGMENT: /^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+$/,
44
+
45
+ CHANNEL_SEGMENTS: /^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+(\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+)*$/,
46
+
47
+ CHANNEL_NAME: /^\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+(\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+)*$/,
48
+
49
+ WILD_CARD: /^\*{1,2}$/,
50
+
51
+ CHANNEL_PATTERN: /^(\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+)*\/\*{1,2}$/,
52
+
53
+ VERSION_ELEMENT: /^(([a-z]|[A-Z])|[0-9])(((([a-z]|[A-Z])|[0-9])|\-|\_))*$/,
54
+
55
+ VERSION: /^([0-9])+(\.(([a-z]|[A-Z])|[0-9])(((([a-z]|[A-Z])|[0-9])|\-|\_))*)*$/,
56
+
57
+ CLIENT_ID: /^((([a-z]|[A-Z])|[0-9]))+$/,
58
+
59
+ ID: /^((([a-z]|[A-Z])|[0-9]))+$/,
60
+
61
+ ERROR_MESSAGE: /^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*$/,
62
+
63
+ ERROR_ARGS: /^(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*(,(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*)*$/,
64
+
65
+ ERROR_CODE: /^[0-9][0-9][0-9]$/,
66
+
67
+ 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])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*)$/
68
+
69
+ },
70
+
71
+ commonElement: function(lista, listb) {
72
+ for (var i = 0, n = lista.length; i < n; i++) {
73
+ if (this.indexOf(listb, lista[i]) !== -1)
74
+ return lista[i];
75
+ }
76
+ return null;
77
+ },
78
+
79
+ indexOf: function(list, needle) {
80
+ for (var i = 0, n = list.length; i < n; i++) {
81
+ if (list[i] === needle) return i;
82
+ }
83
+ return -1;
84
+ },
85
+
86
+ each: function(object, callback, scope) {
87
+ if (object instanceof Array) {
88
+ for (var i = 0, n = object.length; i < n; i++) {
89
+ if (object[i] !== undefined)
90
+ callback.call(scope || null, object[i], i);
91
+ }
92
+ } else {
93
+ for (var key in object) {
94
+ if (object.hasOwnProperty(key))
95
+ callback.call(scope || null, key, object[key]);
96
+ }
97
+ }
98
+ },
99
+
100
+ size: function(object) {
101
+ var size = 0;
102
+ this.each(object, function() { size += 1 });
103
+ return size;
104
+ },
105
+
106
+ random: function() {
107
+ var field = Math.pow(2, this.ID_LENGTH);
108
+ return (Math.random() * field).toString(16).replace(/0*$/, '');
109
+ },
110
+
111
+ enumEqual: function(actual, expected) {
112
+ if (expected instanceof Array) {
113
+ if (!(actual instanceof Array)) return false;
114
+ var i = actual.length;
115
+ if (i !== expected.length) return false;
116
+ while (i--) {
117
+ if (actual[i] !== expected[i]) return false;
118
+ }
119
+ return true;
120
+ } else {
121
+ if (!(actual instanceof Object)) return false;
122
+ if (this.size(expected) !== this.size(actual)) return false;
123
+ var result = true;
124
+ this.each(actual, function(key, value) {
125
+ result = result && (expected[key] === value);
126
+ });
127
+ return result;
128
+ }
129
+ }
130
+ });
131
+
132
+
133
+ Faye.Class = function(parent, methods) {
134
+ if (typeof parent !== 'function') {
135
+ methods = parent;
136
+ parent = Object;
137
+ }
138
+
139
+ var klass = function() {
140
+ if (!this.initialize) return this;
141
+ return this.initialize.apply(this, arguments) || this;
142
+ };
143
+
144
+ var bridge = function() {};
145
+ bridge.prototype = parent.prototype;
146
+
147
+ klass.prototype = new bridge();
148
+ Faye.extend(klass.prototype, methods);
149
+
150
+ return klass;
151
+ };
152
+
153
+
154
+ Faye.Observable = {
155
+ on: function(eventType, block, scope) {
156
+ this._observers = this._observers || {};
157
+ var list = this._observers[eventType] = this._observers[eventType] || [];
158
+ list.push([block, scope]);
159
+ },
160
+
161
+ stopObserving: function(eventType, block, scope) {
162
+ if (!this._observers || !this._observers[eventType]) return;
163
+
164
+ if (!block) {
165
+ delete this._observers[eventType];
166
+ return;
167
+ }
168
+ var list = this._observers[eventType],
169
+ i = list.length;
170
+
171
+ while (i--) {
172
+ if (block && list[i][0] !== block) continue;
173
+ if (scope && list[i][1] !== scope) continue;
174
+ list.splice(i,1);
175
+ }
176
+ },
177
+
178
+ fire: function() {
179
+ var args = Array.prototype.slice.call(arguments),
180
+ eventType = args.shift();
181
+
182
+ if (!this._observers || !this._observers[eventType]) return;
183
+
184
+ Faye.each(this._observers[eventType], function(listener) {
185
+ listener[0].apply(listener[1], args.slice());
186
+ });
187
+ }
188
+ };
189
+
190
+
191
+ Faye.Channel = Faye.Class({
192
+ initialize: function(name) {
193
+ this.__id = this._name = name;
194
+ },
195
+
196
+ push: function(message) {
197
+ this.fire('message', message);
198
+ }
199
+ });
200
+
201
+ Faye.extend(Faye.Channel.prototype, Faye.Observable);
202
+
203
+ Faye.extend(Faye.Channel, {
204
+ HANDSHAKE: '/meta/handshake',
205
+ CONNECT: '/meta/connect',
206
+ SUBSCRIBE: '/meta/subscribe',
207
+ UNSUBSCRIBE: '/meta/unsubscribe',
208
+ DISCONNECT: '/meta/disconnect',
209
+
210
+ META: 'meta',
211
+ SERVICE: 'service',
212
+
213
+ isValid: function(name) {
214
+ return Faye.Grammar.CHANNEL_NAME.test(name) ||
215
+ Faye.Grammar.CHANNEL_PATTERN.test(name);
216
+ },
217
+
218
+ parse: function(name) {
219
+ if (!this.isValid(name)) return null;
220
+ return name.split('/').slice(1);
221
+ },
222
+
223
+ isMeta: function(name) {
224
+ var segments = this.parse(name);
225
+ return segments ? (segments[0] === this.META) : null;
226
+ },
227
+
228
+ isService: function(name) {
229
+ var segments = this.parse(name);
230
+ return segments ? (segments[0] === this.SERVICE) : null;
231
+ },
232
+
233
+ isSubscribable: function(name) {
234
+ if (!this.isValid(name)) return null;
235
+ return !this.isMeta(name) && !this.isService(name);
236
+ },
237
+
238
+ Tree: Faye.Class({
239
+ initialize: function(value) {
240
+ this._value = value;
241
+ this._children = {};
242
+ },
243
+
244
+ eachChild: function(block, context) {
245
+ Faye.each(this._children, function(key, subtree) {
246
+ block.call(context, key, subtree);
247
+ });
248
+ },
249
+
250
+ each: function(prefix, block, context) {
251
+ this.eachChild(function(path, subtree) {
252
+ path = prefix.concat(path);
253
+ subtree.each(path, block, context);
254
+ });
255
+ if (this._value !== undefined) block.call(context, prefix, this._value);
256
+ },
257
+
258
+ map: function(block, context) {
259
+ var result = [];
260
+ this.each([], function(path, value) {
261
+ result.push(block.call(context, path, value));
262
+ });
263
+ return result;
264
+ },
265
+
266
+ get: function(name) {
267
+ var tree = this.traverse(name);
268
+ return tree ? tree._value : null;
269
+ },
270
+
271
+ set: function(name, value) {
272
+ var subtree = this.traverse(name, true);
273
+ if (subtree) subtree._value = value;
274
+ },
275
+
276
+ traverse: function(path, createIfAbsent) {
277
+ if (typeof path === 'string') path = Faye.Channel.parse(path);
278
+
279
+ if (path === null) return null;
280
+ if (path.length === 0) return this;
281
+
282
+ var subtree = this._children[path[0]];
283
+ if (!subtree && !createIfAbsent) return null;
284
+ if (!subtree) subtree = this._children[path[0]] = new Faye.Channel.Tree();
285
+
286
+ return subtree.traverse(path.slice(1), createIfAbsent);
287
+ },
288
+
289
+ findOrCreate: function(channel) {
290
+ var existing = this.get(channel);
291
+ if (existing) return existing;
292
+ existing = new Faye.Channel(channel);
293
+ this.set(channel, existing);
294
+ return existing;
295
+ },
296
+
297
+ glob: function(path) {
298
+ if (typeof path === 'string') path = Faye.Channel.parse(path);
299
+
300
+ if (path === null) return [];
301
+ if (path.length === 0) return (this._value === undefined) ? [] : [this._value];
302
+
303
+ var list = [];
304
+
305
+ if (Faye.enumEqual(path, ['*'])) {
306
+ Faye.each(this._children, function(key, subtree) {
307
+ if (subtree._value !== undefined) list.push(subtree._value);
308
+ });
309
+ return list;
310
+ }
311
+
312
+ if (Faye.enumEqual(path, ['**'])) {
313
+ list = this.map(function(key, value) { return value });
314
+ list.pop();
315
+ return list;
316
+ }
317
+
318
+ Faye.each(this._children, function(key, subtree) {
319
+ if (key !== path[0] && key !== '*') return;
320
+ var sublist = subtree.glob(path.slice(1));
321
+ Faye.each(sublist, function(channel) { list.push(channel) });
322
+ });
323
+
324
+ if (this._children['**']) list.push(this._children['**']._value);
325
+ return list;
326
+ }
327
+
328
+ /**
329
+ Tests
330
+
331
+ glob = new Faye.Channel.Tree();
332
+ list = '/foo/bar /foo/boo /foo /foobar /foo/bar/boo /foobar/boo /foo/* /foo/**'.split(' ');
333
+
334
+ Faye.each(list, function(c, i) {
335
+ glob.set(c, i + 1);
336
+ });
337
+
338
+ console.log(glob.glob('/foo/*').sort()); // 1,2,7,8
339
+ console.log(glob.glob('/foo/bar').sort()); // 1,7,8
340
+ console.log(glob.glob('/foo/**').sort()); // 1,2,5,7,8
341
+ console.log(glob.glob('/foo/bar/boo').sort()); // 5,8
342
+ **/
343
+ })
344
+ });
345
+
346
+
347
+ Faye.Set = Faye.Class({
348
+ initialize: function() {
349
+ this._index = {};
350
+ },
351
+
352
+ add: function(item) {
353
+ var key = (item.__id !== undefined) ? item.__id : item;
354
+ if (this._index.hasOwnProperty(key)) return false;
355
+ this._index[key] = item;
356
+ return true;
357
+ },
358
+
359
+ forEach: function(block, scope) {
360
+ for (var key in this._index) {
361
+ if (this._index.hasOwnProperty(key))
362
+ block.call(scope, this._index[key]);
363
+ }
364
+ },
365
+
366
+ isEmpty: function() {
367
+ for (var key in this._index) {
368
+ if (this._index.hasOwnProperty(key)) return false;
369
+ }
370
+ return true;
371
+ },
372
+
373
+ member: function(item) {
374
+ for (var key in this._index) {
375
+ if (this._index[key] === item) return true;
376
+ }
377
+ return false;
378
+ },
379
+
380
+ remove: function(item) {
381
+ var key = (item.__id !== undefined) ? item.__id : item;
382
+ delete this._index[key];
383
+ },
384
+
385
+ toArray: function() {
386
+ var array = [];
387
+ this.forEach(function(item) { array.push(item) });
388
+ return array;
389
+ }
390
+ });
391
+
392
+
393
+ Faye.Server = Faye.Class({
394
+ initialize: function(options) {
395
+ this._options = options || {};
396
+ this._channels = new Faye.Channel.Tree();
397
+ this._clients = {};
398
+ },
399
+
400
+ clientIds: function() {
401
+ var ids = [];
402
+ Faye.each(this._clients, function(key, value) { ids.push(key) });
403
+ return ids;
404
+ },
405
+
406
+ process: function(messages, local, callback) {
407
+ messages = (messages instanceof Array) ? messages : [messages];
408
+ var processed = 0, responses = [];
409
+
410
+ Faye.each(messages, function(message) {
411
+ this._handle(message, local, function(reply) {
412
+ responses = responses.concat(reply);
413
+ processed += 1;
414
+ if (processed === messages.length) callback(responses);
415
+ });
416
+ }, this);
417
+ },
418
+
419
+ flushConnection: function(messages) {
420
+ messages = (messages instanceof Array) ? messages : [messages];
421
+ Faye.each(messages, function(message) {
422
+ var client = this._clients[message.clientId];
423
+ if (client) client.flush();
424
+ }, this);
425
+ },
426
+
427
+ _generateId: function() {
428
+ var id = Faye.random();
429
+ while (this._clients.hasOwnProperty(id)) id = Faye.random();
430
+ return this._connection(id).id;
431
+ },
432
+
433
+ _connection: function(id) {
434
+ if (this._clients.hasOwnProperty(id)) return this._clients[id];
435
+ var client = new Faye.Connection(id, this._options);
436
+ client.on('stale', this._destroyClient, this);
437
+ return this._clients[id] = client;
438
+ },
439
+
440
+ _destroyClient: function(client) {
441
+ client.disconnect();
442
+ client.stopObserving('stale', this._destroyClient, this);
443
+ delete this._clients[client.id];
444
+ },
445
+
446
+ _handle: function(message, local, callback) {
447
+ var clientId = message.clientId,
448
+ channel = message.channel,
449
+ response;
450
+
451
+ if (Faye.Channel.isMeta(channel)) {
452
+ response = this[Faye.Channel.parse(channel)[1]](message, local);
453
+
454
+ clientId = clientId || response.clientId;
455
+ response.advice = response.advice || {};
456
+ Faye.extend(response.advice, {
457
+ reconnect: this._clients.hasOwnProperty(clientId) ? 'retry' : 'handshake',
458
+ interval: Faye.Connection.INTERVAL * 1000
459
+ }, false);
460
+
461
+ response.id = message.id;
462
+
463
+ if (response.channel !== Faye.Channel.CONNECT ||
464
+ response.successful !== true)
465
+ return callback(response);
466
+
467
+ return this._connection(response.clientId).connect(function(events) {
468
+ Faye.each(events, function(e) { delete e.__id });
469
+ callback([response].concat(events));
470
+ });
471
+ }
472
+
473
+ if (!message.clientId || Faye.Channel.isService(channel))
474
+ return callback([]);
475
+
476
+ message.__id = Faye.random();
477
+ Faye.each(this._channels.glob(channel), function(c) { c.push(message) });
478
+
479
+ callback( { channel: channel,
480
+ successful: true,
481
+ id: message.id } );
482
+ },
483
+
484
+ handshake: function(message, local) {
485
+ var response = { channel: Faye.Channel.HANDSHAKE,
486
+ version: Faye.BAYEUX_VERSION,
487
+ supportedConnectionTypes: Faye.CONNECTION_TYPES,
488
+ id: message.id };
489
+
490
+ if (!message.version)
491
+ response.error = Faye.Error.parameterMissing('version');
492
+
493
+ var clientConns = message.supportedConnectionTypes,
494
+ commonConns;
495
+
496
+ if (clientConns) {
497
+ commonConns = clientConns.filter(function(conn) {
498
+ return Faye.CONNECTION_TYPES.indexOf(conn) !== -1;
499
+ });
500
+ if (commonConns.length === 0)
501
+ response.error = Faye.Error.conntypeMismatch(clientConns);
502
+ } else {
503
+ response.error = Faye.Error.parameterMissing('supportedConnectionTypes');
504
+ }
505
+
506
+ response.successful = !response.error;
507
+ if (!response.successful) return response;
508
+
509
+ response.clientId = this._generateId();
510
+ return response;
511
+ },
512
+
513
+ connect: function(message, local) {
514
+ var response = { channel: Faye.Channel.CONNECT,
515
+ id: message.id };
516
+
517
+ var clientId = message.clientId,
518
+ client = clientId ? this._clients[clientId] : null,
519
+ connectionType = message.connectionType;
520
+
521
+ if (!client) response.error = Faye.Error.clientUnknown(clientId);
522
+ if (!clientId) response.error = Faye.Error.parameterMissing('clientId');
523
+ if (!connectionType) response.error = Faye.Error.parameterMissing('connectionType');
524
+
525
+ response.successful = !response.error;
526
+ if (!response.successful) return response;
527
+
528
+ response.clientId = client.id;
529
+ return response;
530
+ },
531
+
532
+ disconnect: function(message, local) {
533
+ var response = { channel: Faye.Channel.DISCONNECT,
534
+ id: message.id };
535
+
536
+ var clientId = message.clientId,
537
+ client = clientId ? this._clients[clientId] : null;
538
+
539
+ if (!client) response.error = Faye.Error.clientUnknown(clientId);
540
+ if (!clientId) response.error = Faye.Error.parameterMissing('clientId');
541
+
542
+ response.successful = !response.error;
543
+ if (!response.successful) return response;
544
+
545
+ this._destroyClient(client);
546
+
547
+ response.clientId = clientId;
548
+ return response;
549
+ },
550
+
551
+ subscribe: function(message, local) {
552
+ var response = { channel: Faye.Channel.SUBSCRIBE,
553
+ clientId: message.clientId,
554
+ id: message.id };
555
+
556
+ var clientId = message.clientId,
557
+ client = clientId ? this._clients[clientId] : null,
558
+ subscription = message.subscription;
559
+
560
+ subscription = (subscription instanceof Array) ? subscription : [subscription];
561
+
562
+ if (!client) response.error = Faye.Error.clientUnknown(clientId);
563
+ if (!clientId) response.error = Faye.Error.parameterMissing('clientId');
564
+ if (!message.subscription) response.error = Faye.Error.parameterMissing('subscription');
565
+
566
+ response.subscription = subscription;
567
+
568
+ Faye.each(subscription, function(channel) {
569
+ if (response.error) return;
570
+ if (!Faye.Channel.isSubscribable(channel)) response.error = Faye.Error.channelForbidden(channel);
571
+ if (!Faye.Channel.isValid(channel)) response.error = Faye.Error.channelInvalid(channel);
572
+
573
+ if (response.error) return;
574
+ channel = this._channels.findOrCreate(channel);
575
+ client.subscribe(channel);
576
+ }, this);
577
+
578
+ response.successful = !response.error;
579
+ return response;
580
+ },
581
+
582
+ unsubscribe: function(message, local) {
583
+ var response = { channel: Faye.Channel.UNSUBSCRIBE,
584
+ clientId: message.clientId,
585
+ id: message.id };
586
+
587
+ var clientId = message.clientId,
588
+ client = clientId ? this._clients[clientId] : null,
589
+ subscription = message.subscription;
590
+
591
+ subscription = (subscription instanceof Array) ? subscription : [subscription];
592
+
593
+ if (!client) response.error = Faye.Error.clientUnknown(clientId);
594
+ if (!clientId) response.error = Faye.Error.parameterMissing('clientId');
595
+ if (!message.subscription) response.error = Faye.Error.parameterMissing('subscription');
596
+
597
+ Faye.each(subscription, function(channel) {
598
+ if (response.error) return;
599
+
600
+ if (!Faye.Channel.isValid(channel))
601
+ return response.error = Faye.Error.channelInvalid(channel);
602
+
603
+ channel = this._channels.get(channel);
604
+ if (channel) client.unsubscribe(channel);
605
+ }, this);
606
+
607
+ response.successful = !response.error;
608
+ return response;
609
+ }
610
+ });
611
+
612
+
613
+ Faye.Connection = Faye.Class({
614
+ initialize: function(id, options) {
615
+ this.id = id;
616
+ this._options = options;
617
+ this._observers = {};
618
+ this._channels = new Faye.Set();
619
+ this._inbox = new Faye.Set();
620
+ },
621
+
622
+ timeout: function() {
623
+ return this._options.timeout || Faye.Connection.TIMEOUT;
624
+ },
625
+
626
+ _onMessage: function(event) {
627
+ this._inbox.add(event);
628
+ this._beginDeliveryTimeout();
629
+ },
630
+
631
+ subscribe: function(channel) {
632
+ if (!this._channels.add(channel)) return;
633
+ channel.on('message', this._onMessage, this);
634
+ },
635
+
636
+ unsubscribe: function(channel) {
637
+ if (channel === 'all') return this._channels.forEach(this.unsubscribe, this);
638
+ if (!this._channels.member(channel)) return;
639
+ this._channels.remove(channel);
640
+ channel.stopObserving('message', this._onMessage, this);
641
+ },
642
+
643
+ connect: function(callback) {
644
+ this.on('flush', callback);
645
+ if (this._connected) return;
646
+
647
+ this._markForDeletion = false;
648
+ this._connected = true;
649
+
650
+ if (!this._inbox.isEmpty()) this._beginDeliveryTimeout();
651
+ this._beginConnectionTimeout();
652
+ },
653
+
654
+ flush: function() {
655
+ if (!this._connected) return;
656
+ this._releaseConnection();
657
+
658
+ var events = this._inbox.toArray();
659
+ this._inbox = new Faye.Set();
660
+
661
+ this.fire('flush', events);
662
+ this.stopObserving('flush');
663
+ },
664
+
665
+ disconnect: function() {
666
+ this.unsubscribe('all');
667
+ this.flush();
668
+ },
669
+
670
+ _beginDeliveryTimeout: function() {
671
+ if (this._deliveryTimeout || !this._connected || this._inbox.isEmpty())
672
+ return;
673
+
674
+ var self = this;
675
+ this._deliveryTimeout = setTimeout(function () { self.flush() },
676
+ Faye.Connection.MAX_DELAY * 1000);
677
+ },
678
+
679
+ _beginConnectionTimeout: function() {
680
+ if (this._connectionTimeout || !this._connected)
681
+ return;
682
+
683
+ var self = this;
684
+ this._connectionTimeout = setTimeout(function() { self.flush() },
685
+ this.timeout() * 1000);
686
+ },
687
+
688
+ _releaseConnection: function() {
689
+ if (this._connectionTimeout) {
690
+ clearTimeout(this._connectionTimeout);
691
+ delete this._connectionTimeout;
692
+ }
693
+
694
+ if (this._deliveryTimeout) {
695
+ clearTimeout(this._deliveryTimeout);
696
+ delete this._deliveryTimeout;
697
+ }
698
+
699
+ this._connected = false;
700
+ this._scheduleForDeletion();
701
+ },
702
+
703
+ _scheduleForDeletion: function() {
704
+ if (this._markForDeletion) return;
705
+ this._markForDeletion = true;
706
+ var self = this;
707
+
708
+ setTimeout(function() {
709
+ if (!self._markForDeletion) return;
710
+ self.fire('stale', self);
711
+ }, 10000 * Faye.Connection.INTERVAL);
712
+ }
713
+ });
714
+
715
+ Faye.extend(Faye.Connection.prototype, Faye.Observable);
716
+
717
+ Faye.extend(Faye.Connection, {
718
+ MAX_DELAY: 0.1,
719
+ INTERVAL: 1.0,
720
+ TIMEOUT: 60.0
721
+ });
722
+
723
+
724
+ Faye.Error = Faye.Class({
725
+ initialize: function(code, args, message) {
726
+ this.code = code;
727
+ this.args = Array.prototype.slice.call(args);
728
+ this.message = message;
729
+ },
730
+
731
+ toString: function() {
732
+ return this.code + ':' +
733
+ this.args.join(',') + ':' +
734
+ this.message;
735
+ }
736
+ });
737
+
738
+
739
+ Faye.Error.versionMismatch = function() {
740
+ return new this(300, arguments, "Version mismatch").toString();
741
+ };
742
+
743
+ Faye.Error.conntypeMismatch = function() {
744
+ return new this(301, arguments, "Connection types not supported").toString();
745
+ };
746
+
747
+ Faye.Error.extMismatch = function() {
748
+ return new this(302, arguments, "Extension mismatch").toString();
749
+ };
750
+
751
+ Faye.Error.badRequest = function() {
752
+ return new this(400, arguments, "Bad request").toString();
753
+ };
754
+
755
+ Faye.Error.clientUnknown = function() {
756
+ return new this(401, arguments, "Unknown client").toString();
757
+ };
758
+
759
+ Faye.Error.parameterMissing = function() {
760
+ return new this(402, arguments, "Missing required parameter").toString();
761
+ };
762
+
763
+ Faye.Error.channelForbidden = function() {
764
+ return new this(403, arguments, "Forbidden channel").toString();
765
+ };
766
+
767
+ Faye.Error.channelUnknown = function() {
768
+ return new this(404, arguments, "Unknown channel").toString();
769
+ };
770
+
771
+ Faye.Error.channelInvalid = function() {
772
+ return new this(405, arguments, "Invalid channel").toString();
773
+ };
774
+
775
+ Faye.Error.extUnknown = function() {
776
+ return new this(406, arguments, "Unknown extension").toString();
777
+ };
778
+
779
+ Faye.Error.publishFailed = function() {
780
+ return new this(407, arguments, "Failed to publish").toString();
781
+ };
782
+
783
+ Faye.Error.serverError = function() {
784
+ return new this(500, arguments, "Internal server error").toString();
785
+ };
786
+
787
+
788
+
789
+ var path = require('path'),
790
+ posix = require('posix'),
791
+ sys = require('sys'),
792
+ url = require('url'),
793
+ querystring = require('querystring');
794
+
795
+ Faye.NodeAdapter = Faye.Class({
796
+ initialize: function(options) {
797
+ this._options = options || {};
798
+ this._endpoint = this._options.mount || Faye.NodeAdapter.DEFAULT_ENDPOINT;
799
+ this._script = this._endpoint + '.js';
800
+ this._server = new Faye.Server(this._options);
801
+ },
802
+
803
+ call: function(request, response) {
804
+ var requestUrl = url.parse(request.url, true);
805
+ switch (requestUrl.pathname) {
806
+
807
+ case this._endpoint:
808
+ var isGet = (request.method === 'GET'),
809
+ self = this;
810
+
811
+ if (isGet)
812
+ this._callWithParams(request, response, requestUrl.query);
813
+
814
+ else
815
+ request.addListener('body', function(chunk) {
816
+ self._callWithParams(request, response, querystring.parse(chunk));
817
+ });
818
+
819
+ return true;
820
+ break;
821
+
822
+ case this._script:
823
+ posix.cat(Faye.NodeAdapter.SCRIPT_PATH).addCallback(function(content) {
824
+ response.sendHeader(200, Faye.NodeAdapter.TYPE_SCRIPT);
825
+ response.sendBody(content);
826
+ response.finish();
827
+ });
828
+ return true;
829
+ break;
830
+
831
+ default: return false;
832
+ }
833
+ },
834
+
835
+ _callWithParams: function(request, response, params) {
836
+ try {
837
+ var message = JSON.parse(params.message),
838
+ jsonp = params.jsonp || Faye.JSONP_CALLBACK,
839
+ isGet = (request.method === 'GET'),
840
+ type = isGet ? Faye.NodeAdapter.TYPE_SCRIPT : Faye.NodeAdapter.TYPE_JSON;
841
+
842
+ if (isGet) this._server.flushConnection(message);
843
+
844
+ this._server.process(message, false, function(replies) {
845
+ var body = JSON.stringify(replies);
846
+ if (isGet) body = jsonp + '(' + body + ');';
847
+ response.sendHeader(200, type);
848
+ response.sendBody(body);
849
+ response.finish();
850
+ });
851
+ } catch (e) {
852
+ response.sendHeader(400, {'Content-Type': 'text/plain'});
853
+ response.sendBody('Bad request');
854
+ response.finish();
855
+ }
856
+ }
857
+ });
858
+
859
+ Faye.extend(Faye.NodeAdapter, {
860
+ DEFAULT_ENDPOINT: '/bayeux',
861
+ SCRIPT_PATH: path.dirname(__filename) + '/faye-client-min.js',
862
+
863
+ TYPE_JSON: {'Content-Type': 'text/json'},
864
+ TYPE_SCRIPT: {'Content-Type': 'text/javascript'},
865
+ TYPE_TEXT: {'Content-Type': 'text/plain'}
866
+ });
867
+
868
+ exports.NodeAdapter = Faye.NodeAdapter;