The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── .gitignore
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Gemfile
├── LICENSE.md
├── Makefile
├── README.md
├── Rakefile
├── examples
    ├── automate.rb
    ├── haproxy.conf
    ├── node
    │   ├── benchmark.js
    │   ├── client.js
    │   ├── pingpong.js
    │   ├── server.js
    │   └── ticker.js
    ├── public
    │   ├── index.html
    │   ├── jquery.js
    │   ├── json2.js
    │   ├── mootools.js
    │   ├── prototype.js
    │   ├── soapbox.js
    │   ├── style.css
    │   └── ticker.html
    ├── ruby
    │   ├── app.rb
    │   ├── benchmark.rb
    │   ├── client.rb
    │   ├── config.ru
    │   ├── pingpong.rb
    │   ├── rainbows.conf
    │   ├── server.rb
    │   └── ticker.rb
    ├── server.crt
    └── server.key
├── faye.gemspec
├── lib
    ├── faye.rb
    └── faye
    │   ├── adapters
    │       ├── rack_adapter.rb
    │       └── static_server.rb
    │   ├── engines
    │       ├── connection.rb
    │       ├── memory.rb
    │       └── proxy.rb
    │   ├── error.rb
    │   ├── mixins
    │       ├── deferrable.rb
    │       ├── logging.rb
    │       ├── publisher.rb
    │       └── timeouts.rb
    │   ├── protocol
    │       ├── channel.rb
    │       ├── client.rb
    │       ├── dispatcher.rb
    │       ├── extensible.rb
    │       ├── grammar.rb
    │       ├── publication.rb
    │       ├── scheduler.rb
    │       ├── server.rb
    │       ├── socket.rb
    │       └── subscription.rb
    │   ├── transport
    │       ├── http.rb
    │       ├── local.rb
    │       ├── transport.rb
    │       └── web_socket.rb
    │   └── util
    │       └── namespace.rb
├── package.json
├── site
    ├── config
    │   ├── compass.rb
    │   └── site.rb
    ├── site
    │   ├── images
    │   │   ├── aha.png
    │   │   ├── buster.png
    │   │   ├── chaxpert.png
    │   │   ├── cloudblocks.png
    │   │   ├── faye-cluster.png
    │   │   ├── faye-internals.png
    │   │   ├── faye-logo.gif
    │   │   ├── gitter.png
    │   │   ├── groupme.png
    │   │   ├── ineda.png
    │   │   ├── medeo.png
    │   │   ├── myspace.png
    │   │   ├── nokia_mix_party.png
    │   │   ├── pathient.png
    │   │   ├── podio.png
    │   │   └── xydo.png
    │   ├── javascripts
    │   │   ├── analytics.js
    │   │   └── prettify.js
    │   └── stylesheets
    │   │   └── github.css
    └── src
    │   ├── layouts
    │       └── default.haml
    │   ├── pages
    │       ├── architecture.haml
    │       ├── browser.haml
    │       ├── browser
    │       │   ├── dispatch.haml
    │       │   ├── extensions.haml
    │       │   ├── publishing.haml
    │       │   ├── subscribing.haml
    │       │   └── transport.haml
    │       ├── download.haml
    │       ├── index.haml
    │       ├── node.haml
    │       ├── node
    │       │   ├── clients.haml
    │       │   ├── engines.haml
    │       │   ├── extensions.haml
    │       │   ├── monitoring.haml
    │       │   └── websockets.haml
    │       ├── ruby.haml
    │       ├── ruby
    │       │   ├── clients.haml
    │       │   ├── engines.haml
    │       │   ├── extensions.haml
    │       │   ├── monitoring.haml
    │       │   └── websockets.haml
    │       ├── security.haml
    │       └── security
    │       │   ├── authentication.haml
    │       │   ├── csrf.haml
    │       │   ├── headers.haml
    │       │   ├── javascript.haml
    │       │   ├── publication.haml
    │       │   ├── push.haml
    │       │   ├── subscription.haml
    │       │   └── summary.haml
    │   ├── partials
    │       ├── browser_navigation.haml
    │       ├── node_navigation.haml
    │       ├── ruby_navigation.haml
    │       └── security_navigation.haml
    │   └── stylesheets
    │       └── screen.sass
├── spec
    ├── browser.js
    ├── index.html
    ├── javascript
    │   ├── channel_spec.js
    │   ├── client_spec.js
    │   ├── dispatcher_spec.js
    │   ├── engine
    │   │   └── memory_spec.js
    │   ├── engine_spec.js
    │   ├── grammar_spec.js
    │   ├── node_adapter_spec.js
    │   ├── publisher_spec.js
    │   ├── server
    │   │   ├── connect_spec.js
    │   │   ├── disconnect_spec.js
    │   │   ├── extensions_spec.js
    │   │   ├── handshake_spec.js
    │   │   ├── integration_spec.js
    │   │   ├── publish_spec.js
    │   │   ├── subscribe_spec.js
    │   │   └── unsubscribe_spec.js
    │   ├── server_spec.js
    │   ├── transport_spec.js
    │   ├── uri_spec.js
    │   └── util
    │   │   ├── copy_object_spec.js
    │   │   └── random_spec.js
    ├── phantom.js
    ├── ruby
    │   ├── channel_spec.rb
    │   ├── client_spec.rb
    │   ├── dispatcher_spec.rb
    │   ├── encoding_helper.rb
    │   ├── engine
    │   │   └── memory_spec.rb
    │   ├── engine_examples.rb
    │   ├── faye_spec.rb
    │   ├── grammar_spec.rb
    │   ├── publisher_spec.rb
    │   ├── rack_adapter_spec.rb
    │   ├── server
    │   │   ├── connect_spec.rb
    │   │   ├── disconnect_spec.rb
    │   │   ├── extensions_spec.rb
    │   │   ├── handshake_spec.rb
    │   │   ├── integration_spec.rb
    │   │   ├── publish_spec.rb
    │   │   ├── subscribe_spec.rb
    │   │   └── unsubscribe_spec.rb
    │   ├── server_proxy.rb
    │   ├── server_spec.rb
    │   └── transport_spec.rb
    └── spec_helper.rb
├── src
    ├── adapters
    │   ├── content_types.js
    │   ├── node_adapter.js
    │   └── static_server.js
    ├── engines
    │   ├── connection.js
    │   ├── memory.js
    │   └── proxy.js
    ├── faye_browser.js
    ├── faye_node.js
    ├── mixins
    │   ├── deferrable.js
    │   ├── logging.js
    │   ├── publisher.js
    │   └── timeouts.js
    ├── protocol
    │   ├── channel.js
    │   ├── client.js
    │   ├── dispatcher.js
    │   ├── error.js
    │   ├── extensible.js
    │   ├── grammar.js
    │   ├── publication.js
    │   ├── scheduler.js
    │   ├── server.js
    │   ├── socket.js
    │   └── subscription.js
    ├── transport
    │   ├── browser_transports.js
    │   ├── cors.js
    │   ├── event_source.js
    │   ├── jsonp.js
    │   ├── node_http.js
    │   ├── node_local.js
    │   ├── node_transports.js
    │   ├── package.json
    │   ├── transport.js
    │   ├── web_socket.js
    │   └── xhr.js
    └── util
    │   ├── array.js
    │   ├── assign.js
    │   ├── browser
    │       ├── event.js
    │       ├── node_shim.js
    │       └── package.json
    │   ├── class.js
    │   ├── constants.js
    │   ├── cookies
    │       ├── browser_cookies.js
    │       ├── node_cookies.js
    │       └── package.json
    │   ├── copy_object.js
    │   ├── event_emitter.js
    │   ├── id_from_messages.js
    │   ├── namespace.js
    │   ├── promise.js
    │   ├── random.js
    │   ├── set.js
    │   ├── to_json.js
    │   ├── uri.js
    │   ├── validate_options.js
    │   └── websocket
    │       ├── browser_websocket.js
    │       ├── node_websocket.js
    │       └── package.json
└── webpack.config.js


/.gitignore:
--------------------------------------------------------------------------------
1 | *.gem
2 | build
3 | Gemfile.lock
4 | lib/client
5 | node_modules
6 | package-lock.json
7 | spec/*_bundle.js
8 | spec/*.map
9 | 


--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Code of Conduct
2 | 
3 | All projects under the [Faye](https://github.com/faye) umbrella are covered by
4 | the [Code of Conduct](https://github.com/faye/code-of-conduct).
5 | 


--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
 1 | # Contributing to Faye
 2 | 
 3 | Faye is implemented in both JavaScript and Ruby. You should be able to hack on
 4 | each implementation independently, although since both implementations include a
 5 | build of the JS client, you will need Node if you want to build the Ruby gem.
 6 | 
 7 | To get the code:
 8 | 
 9 |     git clone git://github.com/faye/faye.git
10 |     cd faye
11 | 
12 | ## Working on the JavaScript codebase
13 | 
14 | To install the dependencies (you will need to do this if you need to build the
15 | Ruby gem as well):
16 | 
17 |     npm install
18 | 
19 | To run the tests on Node:
20 | 
21 |     npm test
22 | 
23 | To run the tests in the browser, you should run
24 | 
25 |     make test
26 | 
27 | which starts a process to continuously rebuild the source code and tests as you
28 | edit them. Open `spec/index.html` to run the tests.
29 | 
30 | To build the package that we release to npm, run:
31 | 
32 |     make
33 | 
34 | ## Working on the Ruby codebase
35 | 
36 | To install the dependencies:
37 | 
38 |     bundle install
39 | 
40 | To run the tests:
41 | 
42 |     bundle exec rspec
43 | 
44 | To build the gem (you will need to install the Node dependencies for this):
45 | 
46 |     make gem
47 | 


--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 | gemspec
3 | 


--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
 1 | Copyright 2009-2025 James Coglan
 2 | 
 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use
 4 | this file except in compliance with the License. You may obtain a copy of the
 5 | License at
 6 | 
 7 |     http://www.apache.org/licenses/LICENSE-2.0
 8 | 
 9 | Unless required by applicable law or agreed to in writing, software distributed
10 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
11 | CONDITIONS OF ANY KIND, either express or implied. See the License for the
12 | specific language governing permissions and limitations under the License.
13 | 


--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
 1 | PATH  := node_modules/.bin:$(PATH)
 2 | SHELL := /bin/bash
 3 | 
 4 | source_files   := $(shell find src -name '*.js')
 5 | spec_files     := $(shell find spec -name '*_spec.js')
 6 | webpack_config := webpack.config.js
 7 | 
 8 | name           := faye-browser
 9 | bundles        := $(name).js $(name)-min.js
10 | 
11 | client_dir     := build/client
12 | client_bundles := $(bundles:%=$(client_dir)/%)
13 | top_files      := CHANGELOG.md LICENSE.md README.md package.json src
14 | top_level      := $(top_files:%=build/%)
15 | 
16 | .PHONY: all gem clean
17 | 
18 | all: $(client_bundles) $(top_level)
19 | 
20 | gem: all
21 | 	gem build faye.gemspec
22 | 
23 | clean:
24 | 	rm -rf build *.gem spec/*_bundle.js{,.map}
25 | 
26 | $(client_dir)/$(name).js: $(webpack_config) $(source_files)
27 | 	webpack;
28 | 
29 | $(client_dir)/$(name)-min.js: $(webpack_config) $(source_files)
30 | 	NODE_ENV=production webpack;
31 | 
32 | build/src: $(source_files) build
33 | 	rsync -a src/ $@/
34 | 
35 | build/%: % build
36 | 	cp 
lt; $@
37 | 
38 | build:
39 | 	mkdir -p $@
40 | 


--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
 1 | # Faye
 2 | 
 3 | Faye is a set of tools for simple publish-subscribe messaging between web
 4 | clients. It ships with easy-to-use message routing servers for Node.js and Rack
 5 | applications, and clients that can be used on the server and in the browser.
 6 | 
 7 | - Documentation: http://faye.jcoglan.com
 8 | - Mailing list: http://groups.google.com/group/faye-users
 9 | - Bug tracker: http://github.com/faye/faye/issues
10 | - Source code: http://github.com/faye/faye
11 | 


--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
 1 | require 'rubygems'
 2 | require './lib/faye'
 3 | 
 4 | task :example, :port, :ssl do |t, args|
 5 |   exec "ruby examples/ruby/server.rb #{args[:port]} #{args[:ssl] && 'ssl'}"
 6 | end
 7 | 
 8 | task :handshake, :port, :n, :c do |t, args|
 9 |   require 'cgi'
10 |   require 'json'
11 |   
12 |   message = {:channel => '/meta/handshake',
13 |              :version => '1.0',
14 |              :supportedConnectionTypes => ['long-polling']}
15 |   
16 |   message = CGI.escape(JSON.dump message)
17 |   url = "http://127.0.0.1:#{args[:port]}/bayeux?jsonp=callback&message=#{message}"
18 |   puts "Request URL:\n#{url}\n\n"
19 |   
20 |   exec "ab -n #{args[:n]} -c #{args[:c]} '#{url}'"
21 | end
22 | 


--------------------------------------------------------------------------------
/examples/automate.rb:
--------------------------------------------------------------------------------
 1 | # Load and configure Capybara
 2 | 
 3 | require 'capybara/dsl'
 4 | require 'terminus'
 5 | Capybara.current_driver = :terminus
 6 | Capybara.app_host = 'http://localhost:9292'
 7 | extend Capybara::DSL
 8 | 
 9 | # Acquire some browsers and log into each with a username
10 | 
11 | NAMES = %w[alice bob carol dan erica frank gemma harold ingrid james]
12 | BROWSERS = {}
13 | Terminus.ensure_browsers 5
14 | 
15 | Terminus.browsers.each_with_index do |browser, i|
16 |   name = NAMES[i]
17 |   puts "#{ name } is using #{ browser }"
18 |   BROWSERS[name] = browser
19 |   Terminus.browser = browser
20 |   visit '/'
21 |   fill_in 'username', :with => name
22 |   click_button 'Go'
23 | end
24 | 
25 | # Send a message from each browser to every other browser, and check that it
26 | # arrived. If it doesn't arrive, send all the browsers back to the dock and
27 | # raise an exception
28 | 
29 | BROWSERS.each do |name, sender|
30 |   BROWSERS.each do |at, target|
31 |     next if at == name
32 | 
33 |     Terminus.browser = sender
34 |     fill_in 'message', :with => "@#{ at } Hello, world!"
35 |     click_button 'Send'
36 | 
37 |     Terminus.browser = target
38 |     unless page.has_content?("#{ name }: @#{ at } Hello, world!")
39 |       Terminus.return_to_dock
40 |       raise "Message did not make it from #{ sender } to #{ target }"
41 |     end
42 |   end
43 | end
44 | 
45 | # Re-dock all the browsers when we're finished
46 | 
47 | Terminus.return_to_dock
48 | 


--------------------------------------------------------------------------------
/examples/haproxy.conf:
--------------------------------------------------------------------------------
 1 | listen stats 127.0.0.1:7000
 2 | 	option httpchk
 3 | 	mode http
 4 | 	stats uri /
 5 | 
 6 | listen soapbox 127.0.0.1:3000
 7 | 	option httpchk GET /
 8 | 	server soapbox1 127.0.0.1:7070 weight 1 maxconn 1000 check port 7070
 9 | 	server soapbox2 127.0.0.1:8080 weight 1 maxconn 1000 check port 8080
10 | 	server soapbox3 127.0.0.1:9090 weight 1 maxconn 1000 check port 9090
11 | 


--------------------------------------------------------------------------------
/examples/node/benchmark.js:
--------------------------------------------------------------------------------
 1 | var faye = require('../../build'),
 2 | 
 3 |     port   = process.argv[2] || 8000,
 4 |     path   = process.argv[3] || 'bayeux',
 5 |     scheme = process.argv[4] === 'tls' ? 'https' : 'http';
 6 | 
 7 | var A = new faye.Client(scheme + '://localhost:' + port + '/' + path),
 8 |     B = new faye.Client(scheme + '://localhost:' + port + '/' + path);
 9 | 
10 | A.connect(function() {
11 |   B.connect(function() {
12 | 
13 |     var time = new Date().getTime(),
14 |         MAX  = 1000;
15 | 
16 |     var stop = function() {
17 |       console.log(new Date().getTime() - time);
18 |       process.exit();
19 |     };
20 | 
21 |     var handle = function(client, channel) {
22 |       return function(n) {
23 |         if (n === MAX) return stop();
24 |         client.publish(channel, n + 1);
25 |       };
26 |     };
27 | 
28 |     var subA = A.subscribe('/chat/a', handle(A, '/chat/b')),
29 |         subB = B.subscribe('/chat/b', handle(B, '/chat/a'));
30 | 
31 |     subA.callback(function() {
32 |       subB.callback(function() {
33 |         console.log('START');
34 |         A.publish('/chat/b', 0);
35 |       });
36 |     });
37 |   });
38 | });
39 | 


--------------------------------------------------------------------------------
/examples/node/client.js:
--------------------------------------------------------------------------------
 1 | // This script demonstrates a logger for the chat app. First, start the chat
 2 | // server in one terminal then run this in another:
 3 | //
 4 | //   $ node examples/node/server.js
 5 | //   $ node examples/node/client.js
 6 | //
 7 | // The client connects to the chat server and logs all messages sent by all
 8 | // connected users.
 9 | 
10 | var fs      = require('fs'),
11 |     deflate = require('permessage-deflate'),
12 |     faye    = require('../../build'),
13 | 
14 |     port     = process.argv[2] || 8000,
15 |     path     = process.argv[3] || 'bayeux',
16 |     scheme   = process.argv[4] === 'tls' ? 'https' : 'http',
17 |     endpoint = scheme + '://localhost:' + port + '/' + path,
18 |     cert     = fs.readFileSync(__dirname + '/../server.crt'),
19 |     proxy    = { headers: { 'User-Agent': 'Faye' }, tls: { ca: cert }};
20 | 
21 | console.log('Connecting to ' + endpoint);
22 | 
23 | var client = new faye.Client(endpoint, { proxy: proxy, tls: { ca: cert }});
24 | client.addWebsocketExtension(deflate);
25 | 
26 | var subscription = client.subscribe('/chat/*', function(message) {
27 |   var user = message.user;
28 | 
29 |   var publication = client.publish('/members/' + user, {
30 |     user:     'node-logger',
31 |     message:  ' Got your message, ' + user + '!'
32 |   });
33 |   publication.callback(function() {
34 |     console.log('[PUBLISH SUCCEEDED]');
35 |   });
36 |   publication.errback(function(error) {
37 |     console.log('[PUBLISH FAILED]', error);
38 |   });
39 | });
40 | 
41 | subscription.callback(function() {
42 |   console.log('[SUBSCRIBE SUCCEEDED]');
43 | });
44 | subscription.errback(function(error) {
45 |   console.log('[SUBSCRIBE FAILED]', error);
46 | });
47 | 
48 | client.bind('transport:down', function() {
49 |   console.log('[CONNECTION DOWN]');
50 | });
51 | client.bind('transport:up', function() {
52 |   console.log('[CONNECTION UP]');
53 | });
54 | 


--------------------------------------------------------------------------------
/examples/node/pingpong.js:
--------------------------------------------------------------------------------
 1 | var faye = require('../../build');
 2 | 
 3 | ENDPOINT = 'http://localhost:8000/bayeux';
 4 | console.log('Connecting to ' + ENDPOINT);
 5 | 
 6 | var ping = new faye.Client(ENDPOINT);
 7 | ping.subscribe('/ping', function() {
 8 |   console.log('PING');
 9 |   setTimeout(function() { ping.publish('/pong', {}) }, 1000);
10 | });
11 | 
12 | var pong = new faye.Client(ENDPOINT);
13 | pong.subscribe('/pong', function() {
14 |   console.log('PONG');
15 |   setTimeout(function() { pong.publish('/ping', {}) }, 1000);
16 | });
17 | 
18 | setTimeout(function() { ping.publish('/pong', {}) }, 500);
19 | 


--------------------------------------------------------------------------------
/examples/node/server.js:
--------------------------------------------------------------------------------
 1 | var fs      = require('fs'),
 2 |     path    = require('path'),
 3 |     http    = require('http'),
 4 |     https   = require('https'),
 5 |     mime    = require('mime'),
 6 |     deflate = require('permessage-deflate'),
 7 |     faye    = require('../../build');
 8 | 
 9 | var SHARED_DIR = __dirname + '/..',
10 |     PUBLIC_DIR = SHARED_DIR + '/public',
11 | 
12 |     bayeux     = new faye.NodeAdapter({ mount: '/bayeux', timeout: 20 }),
13 |     port       = process.argv[2] || '8000',
14 |     secure     = process.argv[3] === 'tls',
15 |     key        = fs.readFileSync(SHARED_DIR + '/server.key'),
16 |     cert       = fs.readFileSync(SHARED_DIR + '/server.crt');
17 | 
18 | bayeux.addWebsocketExtension(deflate);
19 | 
20 | var handleRequest = function(request, response) {
21 |   var path = (request.url === '/') ? '/index.html' : request.url;
22 | 
23 |   fs.readFile(PUBLIC_DIR + path, function(err, content) {
24 |     var status = err ? 404 : 200;
25 |     try {
26 |       response.writeHead(status, { 'Content-Type': mime.lookup(path) });
27 |       response.end(content || 'Not found');
28 |     } catch (e) {}
29 |   });
30 | };
31 | 
32 | var server = secure
33 |            ? https.createServer({ cert: cert, key: key }, handleRequest)
34 |            : http.createServer(handleRequest);
35 | 
36 | bayeux.attach(server);
37 | server.listen(Number(port));
38 | 
39 | bayeux.getClient().subscribe('/chat/*', function(message) {
40 |   console.log('[' + message.user + ']: ' + message.message);
41 | });
42 | 
43 | bayeux.on('subscribe', function(clientId, channel) {
44 |   console.log('[  SUBSCRIBE] ' + clientId + ' -> ' + channel);
45 | });
46 | 
47 | bayeux.on('unsubscribe', function(clientId, channel) {
48 |   console.log('[UNSUBSCRIBE] ' + clientId + ' -> ' + channel);
49 | });
50 | 
51 | bayeux.on('disconnect', function(clientId) {
52 |   console.log('[ DISCONNECT] ' + clientId);
53 | });
54 | 
55 | console.log('Listening on ' + port + (secure? ' (https)' : ''));
56 | 


--------------------------------------------------------------------------------
/examples/node/ticker.js:
--------------------------------------------------------------------------------
 1 | var faye = require('../../build');
 2 | 
 3 | var endpoint = process.argv[2] || 'http://localhost:8000/bayeux',
 4 |     client   = new faye.Client(endpoint),
 5 |     n        = 0;
 6 | 
 7 | setInterval(function() {
 8 |   client.publish('/chat/tick', { n: ++n });
 9 | }, 1000);
10 | 


--------------------------------------------------------------------------------
/examples/public/index.html:
--------------------------------------------------------------------------------
 1 | <!doctype html>
 2 | 
 3 | <html>
 4 |   <head>
 5 |     <meta charset="utf-8">
 6 |     <title>Faye demo: chat client</title>
 7 |     <link rel="stylesheet" href="/style.css">
 8 |     <script src="/mootools.js"></script>
 9 |     <script src="/jquery.js"></script>
10 |     <script src="/json2.js"></script>
11 |     <script src="/bayeux/client.js"></script>
12 |     <script src="/soapbox.js"></script>
13 |   </head>
14 |   <body>
15 | 
16 |     <div class="container">
17 |       <h1><em>Soapbox</em> | a Twitter-style chat app</h1>
18 | 
19 |       <form id="enterUsername">
20 |         <label for="username">Username</label>
21 |         <input type="text" name="username" id="username">
22 |         <input type="submit" value="Go">
23 |       </form>
24 | 
25 |       <div id="app">
26 |         <form id="addFollowee">
27 |           <label for="followee">Follow</label>
28 |           <input type="text" name="followee" id="followee">
29 |           <input type="submit" value="Follow">
30 |         </form>
31 | 
32 |         <form id="postMessage">
33 |           <label for="message">Post a message</label> <span id="transport"></span><br>
34 |           <textarea name="message" id="message" rows="3" cols="40"></textarea>
35 |           <input type="submit" value="Send">
36 |         </form>
37 | 
38 |         <ul id="stream">
39 |         </ul>
40 |       </div>
41 | 
42 |       <script>
43 |         var bayeux = new Faye.Client('/bayeux');
44 |         Faye.logger = window.console;
45 |         Soapbox.init(bayeux);
46 |       </script>
47 |     </div>
48 | 
49 |   </body>
50 | </html>
51 | 


--------------------------------------------------------------------------------
/examples/public/soapbox.js:
--------------------------------------------------------------------------------
  1 | Soapbox = {
  2 |   /**
  3 |    * Initializes the application, passing in the globally shared Bayeux client.
  4 |    * Apps on the same page should share a Bayeux client so that they may share
  5 |    * an open HTTP connection with the server.
  6 |    */
  7 |   init: function(bayeux) {
  8 |     var self = this;
  9 |     this._bayeux = bayeux;
 10 | 
 11 |     this._login   = $('#enterUsername');
 12 |     this._app     = $('#app');
 13 |     this._follow  = $('#addFollowee');
 14 |     this._post    = $('#postMessage');
 15 |     this._stream  = $('#stream');
 16 | 
 17 |     this._app.hide();
 18 | 
 19 |     // When the user enters a username, store it and start the app
 20 |     this._login.submit(function() {
 21 |       self._username = $('#username').val();
 22 |       self.launch();
 23 |       return false;
 24 |     });
 25 | 
 26 |     this._bayeux.addExtension({
 27 |       outgoing: function(message, callback) {
 28 |         var type = message.connectionType;
 29 |         if (type) $('#transport').html('(' + type + ')');
 30 |         callback(message);
 31 |       }
 32 |     });
 33 |   },
 34 | 
 35 |   /**
 36 |    * Starts the application after a username has been entered. A subscription is
 37 |    * made to receive messages that mention this user, and forms are set up to
 38 |    * accept new followers and send messages.
 39 |    */
 40 |   launch: function() {
 41 |     var self = this;
 42 |     this._bayeux.subscribe('/members/' + this._username, this.accept, this);
 43 | 
 44 |     // Hide login form, show main application UI
 45 |     this._login.fadeOut('slow', function() {
 46 |       self._app.fadeIn('slow');
 47 |     });
 48 | 
 49 |     // When we add a follower, subscribe to a channel to which the followed user
 50 |     // will publish messages
 51 |     this._follow.submit(function() {
 52 |       var follow = $('#followee'),
 53 |           name   = follow.val();
 54 | 
 55 |       self._bayeux.subscribe('/chat/' + name, self.accept, self);
 56 |       follow.val('');
 57 |       return false;
 58 |     });
 59 | 
 60 |     // When we enter a message, send it and clear the message field.
 61 |     this._post.submit(function() {
 62 |       var msg = $('#message');
 63 |       self.post(msg.val());
 64 |       msg.val('');
 65 |       return false;
 66 |     });
 67 | 
 68 |     // Detect network problems and disable the form when offline
 69 |     this._bayeux.bind('transport:down', function() {
 70 |       this._post.find('textarea,input').attr('disabled', true);
 71 |     }, this);
 72 |     this._bayeux.bind('transport:up', function() {
 73 |       this._post.find('textarea,input').attr('disabled', false);
 74 |     }, this);
 75 |   },
 76 | 
 77 |   /**
 78 |    * Sends messages that the user has entered. The message is scanned for
 79 |    * @reply-style mentions of other users, and the message is sent to those
 80 |    * users' channels.
 81 |    */
 82 |   post: function(message) {
 83 |     var mentions = [],
 84 |         words    = message.split(/\s+/),
 85 |         self     = this,
 86 |         pattern  = /\@[a-z0-9]+/i;
 87 | 
 88 |     // Extract @replies from the message
 89 |     $.each(words, function(i, word) {
 90 |       if (!pattern.test(word)) return;
 91 |       word = word.replace(/[^a-z0-9]/ig, '');
 92 |       if (word !== self._username) mentions.push(word);
 93 |     });
 94 | 
 95 |     // Message object to transmit over Bayeux channels
 96 |     message = { user: this._username, message: message };
 97 | 
 98 |     // Publish to this user's 'from' channel, and to channels for any @replies
 99 |     // found in the message
100 |     this._bayeux.publish('/chat/' + this._username, message);
101 |     $.each(mentions, function(i, name) {
102 |       self._bayeux.publish('/members/' + name, message);
103 |     });
104 |   },
105 | 
106 |   /**
107 |    * Handler for messages received over subscribed channels. Takes the message
108 |    * object sent by the post() method and displays it in the user's message list.
109 |    */
110 |   accept: function(message) {
111 |     this._stream.prepend('<li><b>' + message.user + ':</b> ' +
112 |                                      message.message + '</li>');
113 |   }
114 | };
115 | 


--------------------------------------------------------------------------------
/examples/public/style.css:
--------------------------------------------------------------------------------
 1 | body {
 2 |   margin:         0;
 3 |   padding:        0;
 4 |   font:           16px/1.5 FreeSans, Helvetica, Arial, sans-serif;
 5 |   text-align:     center;
 6 | }
 7 | 
 8 | .container {
 9 |   text-align:     left;
10 |   width:          400px;
11 |   margin:         0 auto;
12 | }
13 | 
14 | h1, form, ul li {
15 |   padding:        24px 0;
16 |   border-bottom:  1px solid #c0c0c0;
17 | }
18 | 
19 | h1 {
20 |   font-size:      20px;
21 | }
22 | 
23 | h1 em {
24 |   font-style:     normal;
25 |   font-weight:    normal;
26 |   color:          #444;
27 |   text-transform: uppercase;
28 |   letter-spacing: -0.06em;
29 | }
30 | 
31 | label {
32 |   color:          #444;
33 |   font-weight:    bold;
34 | }
35 | 
36 | ul, li {
37 |   list-style:     none;
38 |   margin:         0;
39 |   padding:        0;
40 | }
41 | 
42 | ul li {
43 |   padding: 12px 0;
44 | }
45 | 


--------------------------------------------------------------------------------
/examples/public/ticker.html:
--------------------------------------------------------------------------------
 1 | <!doctype html>
 2 | 
 3 | <html>
 4 |   <head>
 5 |     <meta charset="utf-8">
 6 |     <title>Ticker</title>
 7 |     <script src="/bayeux/client.js"></script>
 8 |   </head>
 9 |   <body>
10 | 
11 |     <p id="transport"></p>
12 |     <h1 id="ticker"></h1>
13 | 
14 |     <script>
15 |       var client    = new Faye.Client('/bayeux'),
16 |           ticker    = document.getElementById('ticker'),
17 |           transport = document.getElementById('transport');
18 | 
19 |       client.addExtension({
20 |         outgoing: function(message, callback) {
21 |           if (message.channel === '/meta/connect') {
22 |             transport.innerHTML = message.connectionType;
23 |           }
24 |           callback(message);
25 |         }
26 |       });
27 | 
28 |       client.subscribe('/chat/tick', function(message) {
29 |         ticker.innerHTML = message.n;
30 |       });
31 |     </script>
32 | 
33 |   </body>
34 | </html>
35 | 


--------------------------------------------------------------------------------
/examples/ruby/app.rb:
--------------------------------------------------------------------------------
 1 | require 'sinatra'
 2 | require 'faye'
 3 | require 'permessage_deflate'
 4 | 
 5 | ROOT_DIR = File.expand_path('../..', __FILE__)
 6 | set :root, ROOT_DIR
 7 | set :logging, false
 8 | 
 9 | get '/' do
10 |   File.read(ROOT_DIR + '/public/index.html')
11 | end
12 | 
13 | get '/post' do
14 |   env['faye.client'].publish('/mentioning/*', {
15 |     :user => 'sinatra',
16 |     :message => params[:message]
17 |   })
18 |   params[:message]
19 | end
20 | 
21 | App = Faye::RackAdapter.new(Sinatra::Application,
22 |   :mount   => '/bayeux',
23 |   :timeout => 25
24 | )
25 | 
26 | App.add_websocket_extension(PermessageDeflate)
27 | 
28 | def App.log(message)
29 | end
30 | 
31 | App.on(:subscribe) do |client_id, channel|
32 |   puts "[  SUBSCRIBE] #{ client_id } -> #{ channel }"
33 | end
34 | 
35 | App.on(:unsubscribe) do |client_id, channel|
36 |   puts "[UNSUBSCRIBE] #{ client_id } -> #{ channel }"
37 | end
38 | 
39 | App.on(:disconnect) do |client_id|
40 |   puts "[ DISCONNECT] #{ client_id }"
41 | end
42 | 


--------------------------------------------------------------------------------
/examples/ruby/benchmark.rb:
--------------------------------------------------------------------------------
 1 | require 'rubygems'
 2 | require 'bundler/setup'
 3 | require 'faye'
 4 | 
 5 | port   = ARGV[0] || 9292
 6 | path   = ARGV[1] || 'bayeux'
 7 | scheme = ARGV[2] == 'tls' ? 'https' : 'http'
 8 | 
 9 | EM.run {
10 |   A = Faye::Client.new("#{ scheme }://0.0.0.0:#{ port }/#{ path }")
11 |   B = Faye::Client.new("#{ scheme }://0.0.0.0:#{ port }/#{ path }")
12 | 
13 |   A.connect do
14 |     B.connect do
15 | 
16 |       time = Time.now.to_f * 1000
17 |       MAX  = 1000
18 | 
19 |       stop = lambda do
20 |         puts Time.now.to_f * 1000 - time
21 |         EM.stop
22 |       end
23 | 
24 |       handle = lambda do |client, channel|
25 |         lambda do |n|
26 |           if n == MAX
27 |             stop.call
28 |           else
29 |             client.publish(channel, n + 1)
30 |           end
31 |         end
32 |       end
33 | 
34 |       sub_a = A.subscribe('/chat/a', &handle.call(A, '/chat/b'))
35 |       sub_b = B.subscribe('/chat/b', &handle.call(B, '/chat/a'))
36 | 
37 |       sub_a.callback do
38 |         sub_b.callback do
39 |           puts 'START'
40 |           A.publish('/chat/b', 0)
41 |         end
42 |       end
43 |     end
44 |   end
45 | }
46 | 


--------------------------------------------------------------------------------
/examples/ruby/client.rb:
--------------------------------------------------------------------------------
 1 | # This script demonstrates a logger for the chat app. First, start the chat
 2 | # server in one terminal then run this in another:
 3 | #
 4 | #   $ ruby examples/ruby/server.rb
 5 | #   $ ruby examples/ruby/client.rb
 6 | #
 7 | # The client connects to the chat server and logs all messages sent by all
 8 | # connected users.
 9 | 
10 | require 'rubygems'
11 | require 'bundler/setup'
12 | require 'faye'
13 | require 'permessage_deflate'
14 | 
15 | port     = ARGV[0] || 9292
16 | path     = ARGV[1] || 'bayeux'
17 | scheme   = ARGV[2] == 'tls' ? 'https' : 'http'
18 | endpoint = "#{ scheme }://user:pass@0.0.0.0:#{ port }/#{ path }"
19 | proxy    = { :headers => { 'User-Agent' => 'Faye' }}
20 | 
21 | EM.run {
22 |   puts "Connecting to #{ endpoint }"
23 | 
24 |   client = Faye::Client.new(endpoint, :proxy => proxy)
25 |   client.add_websocket_extension(PermessageDeflate)
26 | 
27 |   subscription = client.subscribe '/chat/*' do |message|
28 |     user = message['user']
29 | 
30 |     publication = client.publish("/members/#{ user }", {
31 |       "user"    => "ruby-logger",
32 |       "message" => "Got your message, #{ user }!"
33 |     })
34 |     publication.callback do
35 |       puts "[PUBLISH SUCCEEDED]"
36 |     end
37 |     publication.errback do |error|
38 |       puts "[PUBLISH FAILED] #{ error.inspect }"
39 |     end
40 |   end
41 | 
42 |   subscription.callback do
43 |     puts "[SUBSCRIBE SUCCEEDED]"
44 |   end
45 |   subscription.errback do |error|
46 |     puts "[SUBSCRIBE FAILED] #{ error.inspect }"
47 |   end
48 | 
49 |   client.bind 'transport:down' do
50 |     puts "[CONNECTION DOWN]"
51 |   end
52 |   client.bind 'transport:up' do
53 |     puts "[CONNECTION UP]"
54 |   end
55 | }
56 | 


--------------------------------------------------------------------------------
/examples/ruby/config.ru:
--------------------------------------------------------------------------------
 1 | # Run using your favourite async server:
 2 | #
 3 | #     thin start -R examples/ruby/config.ru -p 9292
 4 | #     rainbows -c examples/ruby/rainbows.conf -E production examples/ruby/config.ru -p 9292
 5 | #
 6 | # If you run using one of these commands, the webserver is loaded before this
 7 | # file, so Faye::WebSocket can figure out which adapter to load. If instead you
 8 | # run using `rackup`, you need the `load_adapter` line below.
 9 | #
10 | #     rackup -E production -s thin examples/ruby/config.ru -p 9292
11 | 
12 | require 'rubygems'
13 | require 'bundler/setup'
14 | require File.expand_path('../app', __FILE__)
15 | Faye::WebSocket.load_adapter('thin')
16 | 
17 | run App
18 | 


--------------------------------------------------------------------------------
/examples/ruby/pingpong.rb:
--------------------------------------------------------------------------------
 1 | require 'rubygems'
 2 | require 'bundler/setup'
 3 | require 'faye'
 4 | 
 5 | EM.run {
 6 |   ENDPOINT = 'http://0.0.0.0:9292/bayeux'
 7 |   puts 'Connecting to ' + ENDPOINT
 8 | 
 9 |   ping = Faye::Client.new(ENDPOINT)
10 |   ping.subscribe('/ping') do
11 |     puts 'PING'
12 |     EM.add_timer(1) { ping.publish('/pong', {}) }
13 |   end
14 | 
15 |   pong = Faye::Client.new(ENDPOINT)
16 |   pong.subscribe('/pong') do
17 |     puts 'PONG'
18 |     EM.add_timer(1) { ping.publish('/ping', {}) }
19 |   end
20 | 
21 |   EM.add_timer(0.5) { ping.publish('/pong', {}) }
22 | }
23 | 


--------------------------------------------------------------------------------
/examples/ruby/rainbows.conf:
--------------------------------------------------------------------------------
1 | Rainbows! { use :EventMachine }
2 | 


--------------------------------------------------------------------------------
/examples/ruby/server.rb:
--------------------------------------------------------------------------------
 1 | require 'rubygems'
 2 | require 'bundler/setup'
 3 | 
 4 | port   = ARGV[0] || 9292
 5 | secure = ARGV[1] == 'tls'
 6 | engine = ARGV[2] || 'thin'
 7 | shared = File.expand_path('../..', __FILE__)
 8 | 
 9 | require File.expand_path('../app', __FILE__)
10 | Faye::WebSocket.load_adapter(engine)
11 | 
12 | case engine
13 | 
14 | when 'goliath'
15 |   class FayeServer < Goliath::API
16 |     def response(env)
17 |       App.call(env)
18 |     end
19 |   end
20 | 
21 | when 'puma'
22 |   require 'puma/events'
23 |   events = Puma::Events.new($stdout, $stderr)
24 | 
25 |   require 'puma/binder'
26 |   binder = Puma::Binder.new(events)
27 |   binder.parse(["tcp://0.0.0.0:#{ port }"], App)
28 | 
29 |   server = Puma::Server.new(App, events)
30 |   server.binder = binder
31 |   server.run.join
32 | 
33 | when 'rainbows'
34 |   rackup = Unicorn::Configurator::RACKUP
35 |   rackup[:port] = port
36 |   rackup[:set_listener] = true
37 |   options = rackup[:options]
38 |   options[:config_file] = File.expand_path('../rainbows.conf', __FILE__)
39 |   Rainbows::HttpServer.new(App, options).start.join
40 | 
41 | when 'thin'
42 |   thin = Rack::Handler.get('thin')
43 |   thin.run(App, :Host => '0.0.0.0', :Port => port) do |server|
44 |     if secure
45 |       server.ssl_options = {
46 |         :private_key_file => shared + '/server.key',
47 |         :cert_chain_file  => shared + '/server.crt'
48 |       }
49 |       server.ssl = true
50 |     end
51 |   end
52 | end
53 | 


--------------------------------------------------------------------------------
/examples/ruby/ticker.rb:
--------------------------------------------------------------------------------
 1 | require 'rubygems'
 2 | require 'bundler/setup'
 3 | require 'faye'
 4 | 
 5 | EM.run {
 6 |   endpoint = ARGV.first || 'http://0.0.0.0:9292/bayeux'
 7 |   client   = Faye::Client.new(endpoint)
 8 |   n        = 0
 9 | 
10 |   EM.add_periodic_timer 1 do
11 |     n += 1
12 |     client.publish('/chat/tick', 'n' => n)
13 |   end
14 | }
15 | 


--------------------------------------------------------------------------------
/examples/server.crt:
--------------------------------------------------------------------------------
 1 | -----BEGIN CERTIFICATE-----
 2 | MIIDETCCAfkCFFVA1RW+x/RHvxEUYndBOz6n0ZweMA0GCSqGSIb3DQEBCwUAMEUx
 3 | CzAJBgNVBAYTAlVLMRMwEQYDVQQIDApTb21lLVN0YXRlMQ0wCwYDVQQKDARGYXll
 4 | MRIwEAYDVQQDDAlsb2NhbGhvc3QwHhcNMjQwNjA1MTYwOTQ2WhcNMjUwNjA1MTYw
 5 | OTQ2WjBFMQswCQYDVQQGEwJVSzETMBEGA1UECAwKU29tZS1TdGF0ZTENMAsGA1UE
 6 | CgwERmF5ZTESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOC
 7 | AQ8AMIIBCgKCAQEAsZz0XWvSsgYabtZ7xipxUhCI64FtOUZ8QsM+naq7TDSCX/bp
 8 | 89UJUFLVZIupI1UkIowkfADMAZ4KOJnuSfkucZfOW2Od0EUxZd2/leh7zJdm4dNx
 9 | bIOGhGitnzHV3L/lx6Zx+ai1TWJRQuJ+Ik8qOHDIrWUgw5nLeI1RnsRHbq1un7Vf
10 | swzC3y1vSdl+h2CEg+TIe7HtiIgmt4+AR4JoVEuwmqTSeloFrk6M8jN2Cvmt9QdG
11 | qUKFgxde+UonaRzK/yL/YBVg5oJs2kqS2Yf8lT8PIZ4xiP+28Z/ySYphZjFj5sSE
12 | S3xcdJNaxKbzpcrxsvN+UIzJ1qvsNO7Rm473IwIDAQABMA0GCSqGSIb3DQEBCwUA
13 | A4IBAQAmuriySwIL2casMSzh4PPm3qLrNXOMQf472/jzftM4DqwNY7Zq0hxp89W0
14 | mG7SZAQiv3gqKbgX4g5+EhSTDAjrTSbQsalgueF50DXInaR/a8fETFZVpd+O8HWA
15 | OiL1DX70INef8zap2yt5RMkFZT/RrZt8V/0/4PxePSXCmvnT/DhyLx6xyeoE48HL
16 | S/sG8oSr0xENj+O28X/fAS7eL/iQIUwHUcjcS+NyRYxcLJM70ZJjvvOQhUVlFKjV
17 | Ic+GxmGXDNozopFfgaGT0S2C3ZeLCEbqaFuJg9XSnO6UKM91euc82dfEhgiWE2OU
18 | TKQfFmSxTDHoDeG4TqR6jC1xRqEu
19 | -----END CERTIFICATE-----
20 | 


--------------------------------------------------------------------------------
/examples/server.key:
--------------------------------------------------------------------------------
 1 | -----BEGIN PRIVATE KEY-----
 2 | MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCxnPRda9KyBhpu
 3 | 1nvGKnFSEIjrgW05RnxCwz6dqrtMNIJf9unz1QlQUtVki6kjVSQijCR8AMwBngo4
 4 | me5J+S5xl85bY53QRTFl3b+V6HvMl2bh03Fsg4aEaK2fMdXcv+XHpnH5qLVNYlFC
 5 | 4n4iTyo4cMitZSDDmct4jVGexEdurW6ftV+zDMLfLW9J2X6HYISD5Mh7se2IiCa3
 6 | j4BHgmhUS7CapNJ6WgWuTozyM3YK+a31B0apQoWDF175SidpHMr/Iv9gFWDmgmza
 7 | SpLZh/yVPw8hnjGI/7bxn/JJimFmMWPmxIRLfFx0k1rEpvOlyvGy835QjMnWq+w0
 8 | 7tGbjvcjAgMBAAECggEABgocwxp6ASS0/GTds5jY3p4CUePGR4bOjeSeufTGxqoY
 9 | btPyE6EAXpNafz9CgpmQD36tdOwAA+QQW+lcEXbgLeuoEDJ8eMsJiXm3XI0ZvJS/
10 | Yllyx2pXhiQbF0k2CPobgaT2xjMG6zk3IyuZd2gyutWW9VJ1gUE3CoPfrSLmfOxp
11 | JFOTrpmukcaBnsVUAKLj0fk1HZbHFCa/N+p0QgdgYavHFfReqgJ6zY1jY5czOBkI
12 | 5Hrzcizi2zDHMbgJ37hIB5dNUSep4WzbC9ZnlBUhXXqpBAvh9k1N4H9PieVYMFZ8
13 | 2ftnxbLFyt5UnPk7Ord0xZmum04kdxG0AKRjBuweAQKBgQD40kLP3gL9suDrbJXe
14 | 4OQ72awnHCpwCcM8FKQkU/xcQp1D9Uth48ggilzY6nXrUmo2/+OwqCEj2o5jzf02
15 | dqfd7L7nmeZ6NUFf+dw3y1c7AlvdptBFCGVoBolsb3jaGXuri+k1uUC8VJbl90kr
16 | rEYmVTUutIf6Vd0pPJ62J6uNIwKBgQC2vMPzPI59RBHErf+zabqNJXq7IO80GREm
17 | wUi7pP2kj5ELUdcK4TjyD2q7Nl6egLOmzS4puELdMrcAgt3J3Bh65nhvt13S3b3A
18 | O4hxK6HMtEGBsk0IVqSC+s3q3G/U4IjINhEngGwL61vrfbfR+Y4LDuL9auGDQkvL
19 | mnjtEJ6OAQKBgFbWSqrw+GpB+20uQD/AjOa2WPZtRgJD5fcZ3Q8woGoydWA6Q0yu
20 | ijGRGEY7zVuLL7ZyJ6yHgMlahUcfpLdVQdCZxyZc96q+20n7kXeHZ7IYaKc6iIUP
21 | IRTk8yD85lh3fEmqUoGFXapcey1W2Bp9zR2jryPVrX8YaE7z8Q/xWFWxAoGAQXx4
22 | RGzJK37/Vxp77hHPttFdoD33OxZYnSjbJdPEyfphIktb4xw/Sg/YUer0EZ1RxE73
23 | YiAUZizMhDRhwvtLEpARTQfLacvpOkCbbuMSAsf+SbpZ/Mj//6hdrvL8aK9mlUk6
24 | 8IsHLWZU9JmDDI6AJtpY4jQxSNazTu22tE4mZAECgYAg1/OFoMBJ+VfvWhxJGgsQ
25 | z1G6f//OXFk6wQsyMV+aEsegn9YvM+3sI9c2fn9GJR47Gg0xvYJkwhzutyadI8q2
26 | /cRHl9Im8ak5oWk+afrhFmF+eNsrPFEOVj9RVIRoviscWq6inSvA1+44xzYxcPbM
27 | X6pI3ThzDU/59HnWClMsMw==
28 | -----END PRIVATE KEY-----
29 | 


--------------------------------------------------------------------------------
/faye.gemspec:
--------------------------------------------------------------------------------
 1 | Gem::Specification.new do |s|
 2 |   s.name     = 'faye'
 3 |   s.version  = '1.4.1'
 4 |   s.summary  = 'Simple pub/sub messaging for the web'
 5 |   s.author   = 'James Coglan'
 6 |   s.email    = 'jcoglan@gmail.com'
 7 |   s.homepage = 'https://faye.jcoglan.com'
 8 |   s.license  = 'Apache-2.0'
 9 | 
10 |   s.extra_rdoc_files = %w[README.md]
11 |   s.rdoc_options     = %w[--main README.md --markup markdown]
12 |   s.require_paths    = %w[lib]
13 | 
14 |   # It is important that the JavaScript files listed here are not removed: they
15 |   # contain the browser client and the gem should fail to build without them.
16 |   # You should generate them by running `make` in the project root.
17 |   client_suffix = %w[.js .js.map -min.js -min.js.map]
18 |   client_files  = client_suffix.map { |ext| "build/client/faye-browser#{ext}" }
19 | 
20 |   s.files = %w[CHANGELOG.md LICENSE.md README.md] +
21 |             Dir.glob('lib/**/*.rb') +
22 |             client_files
23 |   
24 |   s.add_dependency 'cookiejar', '>= 0.3.0'
25 |   s.add_dependency 'em-http-request', '>= 1.1.6'
26 |   s.add_dependency 'eventmachine', '>= 0.12.0'
27 |   s.add_dependency 'faye-websocket', '>= 0.11.0'
28 |   s.add_dependency 'multi_json', '>= 1.0.0'
29 |   s.add_dependency 'rack', '>= 1.0.0'
30 |   s.add_dependency 'websocket-driver', '>= 0.5.1'
31 | 
32 |   s.add_development_dependency 'compass', '~> 0.11.0'
33 |   s.add_development_dependency 'haml', '~> 3.1.0'
34 |   s.add_development_dependency 'permessage_deflate', '>= 0.1.0'
35 |   s.add_development_dependency 'puma', '>= 2.0.0'
36 |   s.add_development_dependency 'rack-proxy', '~> 0.4.0'
37 |   s.add_development_dependency 'rack-test'
38 |   s.add_development_dependency 'rake'
39 |   s.add_development_dependency 'RedCloth', '~> 3.0.0'
40 |   s.add_development_dependency 'rspec', '~> 2.99.0'
41 |   s.add_development_dependency 'rspec-eventmachine', '>= 0.2.0'
42 |   s.add_development_dependency 'sass', '~> 3.2.0'
43 |   s.add_development_dependency 'sinatra'
44 |   s.add_development_dependency 'staticmatic'
45 | 
46 |   jruby = RUBY_PLATFORM =~ /java/
47 |   rbx   = defined?(RUBY_ENGINE) && RUBY_ENGINE =~ /rbx/
48 | 
49 |   unless jruby
50 |     s.add_development_dependency 'rainbows', '~> 4.4.0'
51 |     s.add_development_dependency 'thin', '>= 1.2.0'
52 |   end
53 | 
54 |   unless rbx or RUBY_VERSION < '1.9'
55 |     s.add_development_dependency 'goliath'
56 |   end
57 | 
58 |   unless jruby or rbx
59 |     s.add_development_dependency 'passenger', '>= 4.0.0'
60 |   end
61 | end
62 | 


--------------------------------------------------------------------------------
/lib/faye.rb:
--------------------------------------------------------------------------------
  1 | require 'cgi'
  2 | require 'cookiejar'
  3 | require 'digest/sha1'
  4 | require 'em-http'
  5 | require 'em-http/version'
  6 | require 'eventmachine'
  7 | require 'faye/websocket'
  8 | require 'forwardable'
  9 | require 'multi_json'
 10 | require 'rack'
 11 | require 'securerandom'
 12 | require 'set'
 13 | require 'time'
 14 | require 'uri'
 15 | 
 16 | module Faye
 17 |   VERSION = '1.4.1'
 18 | 
 19 |   ROOT = File.expand_path(File.dirname(__FILE__))
 20 | 
 21 |   autoload :Deferrable,   File.join(ROOT, 'faye', 'mixins', 'deferrable')
 22 |   autoload :Logging,      File.join(ROOT, 'faye', 'mixins', 'logging')
 23 |   autoload :Publisher,    File.join(ROOT, 'faye', 'mixins', 'publisher')
 24 |   autoload :Timeouts,     File.join(ROOT, 'faye', 'mixins', 'timeouts')
 25 | 
 26 |   autoload :Namespace,    File.join(ROOT, 'faye', 'util', 'namespace')
 27 | 
 28 |   autoload :Engine,       File.join(ROOT, 'faye', 'engines', 'proxy')
 29 | 
 30 |   autoload :Channel,      File.join(ROOT, 'faye', 'protocol', 'channel')
 31 |   autoload :Client,       File.join(ROOT, 'faye', 'protocol', 'client')
 32 |   autoload :Dispatcher,   File.join(ROOT, 'faye', 'protocol', 'dispatcher')
 33 |   autoload :Scheduler,    File.join(ROOT, 'faye', 'protocol', 'scheduler')
 34 |   autoload :Extensible,   File.join(ROOT, 'faye', 'protocol', 'extensible')
 35 |   autoload :Grammar,      File.join(ROOT, 'faye', 'protocol', 'grammar')
 36 |   autoload :Publication,  File.join(ROOT, 'faye', 'protocol', 'publication')
 37 |   autoload :Server,       File.join(ROOT, 'faye', 'protocol', 'server')
 38 |   autoload :Subscription, File.join(ROOT, 'faye', 'protocol', 'subscription')
 39 | 
 40 |   autoload :Error,        File.join(ROOT, 'faye', 'error')
 41 |   autoload :Transport,    File.join(ROOT, 'faye', 'transport', 'transport')
 42 | 
 43 |   autoload :RackAdapter,  File.join(ROOT, 'faye', 'adapters', 'rack_adapter')
 44 |   autoload :StaticServer, File.join(ROOT, 'faye', 'adapters', 'static_server')
 45 | 
 46 |   BAYEUX_VERSION   = '1.0'
 47 |   JSONP_CALLBACK   = 'jsonpcallback'
 48 |   CONNECTION_TYPES = %w[long-polling cross-origin-long-polling callback-polling websocket eventsource in-process]
 49 | 
 50 |   MANDATORY_CONNECTION_TYPES = %w[long-polling callback-polling in-process]
 51 | 
 52 |   class << self
 53 |     attr_accessor :logger
 54 |   end
 55 | 
 56 |   def self.ensure_reactor_running!
 57 |     Engine.ensure_reactor_running!
 58 |   end
 59 | 
 60 |   def self.random(*args)
 61 |     Engine.random(*args)
 62 |   end
 63 | 
 64 |   def self.client_id_from_messages(messages)
 65 |     first = [messages].flatten.find { |m| m['channel'] == '/meta/connect' }
 66 |     first && first['clientId']
 67 |   end
 68 | 
 69 |   def self.copy_object(object)
 70 |     case object
 71 |     when Hash
 72 |       clone = {}
 73 |       object.each { |k,v| clone[k] = copy_object(v) }
 74 |       clone
 75 |     when Array
 76 |       clone = []
 77 |       object.each { |v| clone << copy_object(v) }
 78 |       clone
 79 |     else
 80 |       object
 81 |     end
 82 |   end
 83 | 
 84 |   def self.to_json(value)
 85 |     case value
 86 |       when Hash, Array then MultiJson.dump(value)
 87 |       when String, NilClass then value.inspect
 88 |       else value.to_s
 89 |     end
 90 |   end
 91 | 
 92 |   def self.async_each(list, iterator, callback)
 93 |     n       = list.size
 94 |     i       = -1
 95 |     calls   = 0
 96 |     looping = false
 97 | 
 98 |     loop, resume = nil, nil
 99 | 
100 |     iterate = lambda do
101 |       calls -= 1
102 |       i += 1
103 |       if i == n
104 |         callback.call if callback
105 |       else
106 |         iterator.call(list[i], resume)
107 |       end
108 |     end
109 | 
110 |     loop = lambda do
111 |       unless looping
112 |         looping = true
113 |         iterate.call while calls > 0
114 |         looping = false
115 |       end
116 |     end
117 | 
118 |     resume = lambda do
119 |       calls += 1
120 |       loop.call
121 |     end
122 |     resume.call
123 |   end
124 | end
125 | 


--------------------------------------------------------------------------------
/lib/faye/adapters/static_server.rb:
--------------------------------------------------------------------------------
 1 | module Faye
 2 |   class StaticServer
 3 | 
 4 |     def initialize(directory, path_regex)
 5 |       @directory  = directory
 6 |       @path_regex = path_regex
 7 |       @path_map   = {}
 8 |       @index      = {}
 9 |     end
10 | 
11 |     def map(request_path, filename)
12 |       @path_map[request_path] = filename
13 |     end
14 | 
15 |     def =~(pathname)
16 |       @path_regex =~ pathname
17 |     end
18 | 
19 |     def call(env)
20 |       filename = File.basename(env['PATH_INFO'])
21 |       filename = @path_map[filename] || filename
22 | 
23 |       cache = @index[filename] ||= {}
24 |       fullpath = File.join(@directory, filename)
25 | 
26 |       begin
27 |         cache[:content] ||= File.read(fullpath)
28 |         cache[:digest]  ||= Digest::SHA1.hexdigest(cache[:content])
29 |         cache[:mtime]   ||= File.mtime(fullpath)
30 |       rescue
31 |         return [404, {}, []]
32 |       end
33 | 
34 |       type = /\.js$/ =~ fullpath ? RackAdapter::TYPE_SCRIPT : RackAdapter::TYPE_JSON
35 |       ims  = env['HTTP_IF_MODIFIED_SINCE']
36 | 
37 |       no_content_length = env[RackAdapter::HTTP_X_NO_CONTENT_LENGTH]
38 | 
39 |       headers = {
40 |         'ETag'          => cache[:digest],
41 |         'Last-Modified' => cache[:mtime].httpdate
42 |       }
43 | 
44 |       if env['HTTP_IF_NONE_MATCH'] == cache[:digest]
45 |         [304, headers, ['']]
46 |       elsif ims and cache[:mtime] <= Time.httpdate(ims)
47 |         [304, headers, ['']]
48 |       else
49 |         headers['Content-Length'] = cache[:content].bytesize.to_s unless no_content_length
50 |         headers.update(type)
51 |         [200, headers, [cache[:content]]]
52 |       end
53 |     end
54 | 
55 |   end
56 | end
57 | 


--------------------------------------------------------------------------------
/lib/faye/engines/connection.rb:
--------------------------------------------------------------------------------
 1 | module Faye
 2 |   module Engine
 3 | 
 4 |     class Connection
 5 |       include Deferrable
 6 |       include Timeouts
 7 | 
 8 |       attr_accessor :socket
 9 | 
10 |       def initialize(engine, id, options = {})
11 |         @engine  = engine
12 |         @id      = id
13 |         @options = options
14 |         @inbox   = Set.new
15 |       end
16 | 
17 |       def deliver(message)
18 |         message.delete('clientId')
19 |         return @socket.send(message) if @socket
20 |         return unless @inbox.add?(message)
21 |         begin_delivery_timeout
22 |       end
23 | 
24 |       def connect(options, &block)
25 |         options = options || {}
26 |         timeout = options['timeout'] ? options['timeout'] / 1000.0 : @engine.timeout
27 | 
28 |         set_deferred_status(:unknown)
29 |         callback(&block)
30 | 
31 |         begin_delivery_timeout
32 |         begin_connection_timeout(timeout)
33 |       end
34 | 
35 |       def flush
36 |         remove_timeout(:connection)
37 |         remove_timeout(:delivery)
38 | 
39 |         set_deferred_status(:succeeded, @inbox.entries)
40 |         @inbox = []
41 | 
42 |         @engine.close_connection(@id) unless @socket
43 |       end
44 | 
45 |     private
46 | 
47 |       def begin_delivery_timeout
48 |         return if @inbox.empty?
49 |         add_timeout(:delivery, MAX_DELAY) { flush }
50 |       end
51 | 
52 |       def begin_connection_timeout(timeout)
53 |         add_timeout(:connection, timeout) { flush }
54 |       end
55 |     end
56 | 
57 |   end
58 | end
59 | 


--------------------------------------------------------------------------------
/lib/faye/engines/memory.rb:
--------------------------------------------------------------------------------
  1 | module Faye
  2 |   module Engine
  3 | 
  4 |     class Memory
  5 |       include Timeouts
  6 | 
  7 |       def self.create(server, options)
  8 |         new(server, options)
  9 |       end
 10 | 
 11 |       def initialize(server, options)
 12 |         @server    = server
 13 |         @options   = options
 14 |         reset
 15 |       end
 16 | 
 17 |       def disconnect
 18 |         reset
 19 |         remove_all_timeouts
 20 |       end
 21 | 
 22 |       def reset
 23 |         @namespace = Namespace.new
 24 |         @clients   = {}
 25 |         @channels  = {}
 26 |         @messages  = {}
 27 |       end
 28 | 
 29 |       def create_client(&callback)
 30 |         client_id = @namespace.generate
 31 |         @server.debug('Created new client ?', client_id)
 32 |         ping(client_id)
 33 |         @server.trigger(:handshake, client_id)
 34 |         callback.call(client_id)
 35 |       end
 36 | 
 37 |       def destroy_client(client_id, &callback)
 38 |         return unless @namespace.exists?(client_id)
 39 | 
 40 |         if @clients.has_key?(client_id)
 41 |           @clients[client_id].each { |channel| unsubscribe(client_id, channel) }
 42 |         end
 43 | 
 44 |         remove_timeout(client_id)
 45 |         @namespace.release(client_id)
 46 |         @messages.delete(client_id)
 47 |         @server.debug('Destroyed client ?', client_id)
 48 |         @server.trigger(:disconnect, client_id)
 49 |         @server.trigger(:close, client_id)
 50 |         callback.call if callback
 51 |       end
 52 | 
 53 |       def client_exists(client_id, &callback)
 54 |         callback.call(@namespace.exists?(client_id))
 55 |       end
 56 | 
 57 |       def ping(client_id)
 58 |         timeout = @server.timeout
 59 |         return unless Numeric === timeout
 60 |         @server.debug('Ping ?, ?', client_id, timeout)
 61 |         remove_timeout(client_id)
 62 |         add_timeout(client_id, 2 * timeout) { destroy_client(client_id) }
 63 |       end
 64 | 
 65 |       def subscribe(client_id, channel, &callback)
 66 |         @clients[client_id] ||= Set.new
 67 |         should_trigger = @clients[client_id].add?(channel)
 68 | 
 69 |         @channels[channel] ||= Set.new
 70 |         @channels[channel].add(client_id)
 71 | 
 72 |         @server.debug('Subscribed client ? to channel ?', client_id, channel)
 73 |         @server.trigger(:subscribe, client_id, channel) if should_trigger
 74 |         callback.call(true) if callback
 75 |       end
 76 | 
 77 |       def unsubscribe(client_id, channel, &callback)
 78 |         if @clients.has_key?(client_id)
 79 |           should_trigger = @clients[client_id].delete?(channel)
 80 |           @clients.delete(client_id) if @clients[client_id].empty?
 81 |         end
 82 | 
 83 |         if @channels.has_key?(channel)
 84 |           @channels[channel].delete(client_id)
 85 |           @channels.delete(channel) if @channels[channel].empty?
 86 |         end
 87 | 
 88 |         @server.debug('Unsubscribed client ? from channel ?', client_id, channel)
 89 |         @server.trigger(:unsubscribe, client_id, channel) if should_trigger
 90 |         callback.call(true) if callback
 91 |       end
 92 | 
 93 |       def publish(message, channels)
 94 |         @server.debug('Publishing message ?', message)
 95 | 
 96 |         clients = Set.new
 97 | 
 98 |         channels.each do |channel|
 99 |           next unless subs = @channels[channel]
100 |           subs.each(&clients.method(:add))
101 |         end
102 | 
103 |         clients.each do |client_id|
104 |           @server.debug('Queueing for client ?: ?', client_id, message)
105 |           @messages[client_id] ||= []
106 |           @messages[client_id] << Faye.copy_object(message)
107 |           empty_queue(client_id)
108 |         end
109 | 
110 |         @server.trigger(:publish, message['clientId'], message['channel'], message['data'])
111 |       end
112 | 
113 |       def empty_queue(client_id)
114 |         return unless @server.has_connection?(client_id)
115 |         @server.deliver(client_id, @messages[client_id])
116 |         @messages.delete(client_id)
117 |       end
118 |     end
119 | 
120 |   end
121 | end
122 | 


--------------------------------------------------------------------------------
/lib/faye/engines/proxy.rb:
--------------------------------------------------------------------------------
  1 | module Faye
  2 |   module Engine
  3 | 
  4 |     METHODS   = %w[create_client client_exists destroy_client ping subscribe unsubscribe]
  5 |     MAX_DELAY = 0.0
  6 |     INTERVAL  = 0.0
  7 |     TIMEOUT   = 60.0
  8 |     ID_LENGTH = 160
  9 | 
 10 |     autoload :Connection, File.expand_path('../connection', __FILE__)
 11 |     autoload :Memory,     File.expand_path('../memory', __FILE__)
 12 | 
 13 |     def self.ensure_reactor_running!
 14 |       Thread.new { EventMachine.run } unless EventMachine.reactor_running?
 15 |       Thread.pass until EventMachine.reactor_running?
 16 |     end
 17 | 
 18 |     def self.get(options)
 19 |       Proxy.new(options)
 20 |     end
 21 | 
 22 |     def self.random(bitlength = ID_LENGTH)
 23 |       limit    = 2 ** bitlength
 24 |       max_size = (bitlength * Math.log(2) / Math.log(36)).ceil
 25 |       string   = SecureRandom.random_number(limit).to_s(36)
 26 |       string   = '0' + string while string.size < max_size
 27 |       string
 28 |     end
 29 | 
 30 |     class Proxy
 31 |       include Publisher
 32 |       include Logging
 33 | 
 34 |       attr_reader :interval, :timeout
 35 | 
 36 |       extend Forwardable
 37 |       def_delegators :@engine, *METHODS
 38 | 
 39 |       def initialize(options)
 40 |         super()
 41 | 
 42 |         @options     = options
 43 |         @connections = {}
 44 |         @interval    = @options[:interval] || INTERVAL
 45 |         @timeout     = @options[:timeout]  || TIMEOUT
 46 | 
 47 |         engine_class = @options[:type] || Memory
 48 |         @engine      = engine_class.create(self, @options)
 49 | 
 50 |         bind :close do |client_id|
 51 |           EventMachine.next_tick { flush_connection(client_id) }
 52 |         end
 53 | 
 54 |         debug('Created new engine: ?', @options)
 55 |       end
 56 | 
 57 |       def connect(client_id, options = {}, &callback)
 58 |         debug('Accepting connection from ?', client_id)
 59 |         @engine.ping(client_id)
 60 |         conn = connection(client_id, true)
 61 |         conn.connect(options, &callback)
 62 |         @engine.empty_queue(client_id)
 63 |       end
 64 | 
 65 |       def has_connection?(client_id)
 66 |         @connections.has_key?(client_id)
 67 |       end
 68 | 
 69 |       def connection(client_id, create)
 70 |         conn = @connections[client_id]
 71 |         return conn if conn or not create
 72 |         @connections[client_id] = Connection.new(self, client_id)
 73 |         trigger('connection:open', client_id)
 74 |         @connections[client_id]
 75 |       end
 76 | 
 77 |       def close_connection(client_id)
 78 |         debug('Closing connection for ?', client_id)
 79 |         return unless conn = @connections[client_id]
 80 |         conn.socket.close if conn.socket
 81 |         trigger('connection:close', client_id)
 82 |         @connections.delete(client_id)
 83 |       end
 84 | 
 85 |       def open_socket(client_id, socket)
 86 |         conn = connection(client_id, true)
 87 |         conn.socket = socket
 88 |       end
 89 | 
 90 |       def deliver(client_id, messages)
 91 |         return if !messages || messages.empty?
 92 |         return false unless conn = connection(client_id, false)
 93 |         messages.each(&conn.method(:deliver))
 94 |         true
 95 |       end
 96 | 
 97 |       def generate_id
 98 |         Engine.random
 99 |       end
100 | 
101 |       def flush_connection(client_id, close = true)
102 |         return unless client_id
103 |         debug('Flushing connection for ?', client_id)
104 |         return unless conn = connection(client_id, false)
105 |         conn.socket = nil unless close
106 |         conn.flush
107 |         close_connection(client_id)
108 |       end
109 | 
110 |       def close
111 |         @connections.keys.each { |client_id| flush_connection(client_id) }
112 |         @engine.disconnect
113 |       end
114 | 
115 |       def disconnect
116 |         @engine.disconnect if @engine.respond_to?(:disconnect)
117 |       end
118 | 
119 |       def publish(message)
120 |         channels = Channel.expand(message['channel'])
121 |         @engine.publish(message, channels)
122 |       end
123 |     end
124 | 
125 |   end
126 | end
127 | 


--------------------------------------------------------------------------------
/lib/faye/error.rb:
--------------------------------------------------------------------------------
 1 | module Faye
 2 |   class Error
 3 | 
 4 |     def self.method_missing(type, *args)
 5 |       code = const_get(type.to_s.upcase)
 6 |       new(code[0], args, code[1]).to_s
 7 |     end
 8 | 
 9 |     def self.parse(message)
10 |       message ||= ''
11 |       return new(nil, [], message) unless Grammar::ERROR =~ message
12 | 
13 |       parts   = message.split(':')
14 |       code    = parts[0].to_i
15 |       params  = parts[1].split(',')
16 |       message = parts[2]
17 | 
18 |       new(code, params, message)
19 |     end
20 | 
21 |     attr_reader :code, :params, :message
22 | 
23 |     def initialize(code, params, message)
24 |       @code     = code
25 |       @params   = params
26 |       @message  = message
27 |     end
28 | 
29 |     def to_s
30 |       "#{ @code }:#{ @params * ',' }:#{ @message }"
31 |     end
32 | 
33 |     # http://code.google.com/p/cometd/wiki/BayeuxCodes
34 |     VERSION_MISMATCH    = [300, 'Version mismatch']
35 |     CONNTYPE_MISMATCH   = [301, 'Connection types not supported']
36 |     EXT_MISMATCH        = [302, 'Extension mismatch']
37 |     BAD_REQUEST         = [400, 'Bad request']
38 |     CLIENT_UNKNOWN      = [401, 'Unknown client']
39 |     PARAMETER_MISSING   = [402, 'Missing required parameter']
40 |     CHANNEL_FORBIDDEN   = [403, 'Forbidden channel']
41 |     CHANNEL_UNKNOWN     = [404, 'Unknown channel']
42 |     CHANNEL_INVALID     = [405, 'Invalid channel']
43 |     EXT_UNKNOWN         = [406, 'Unknown extension']
44 |     PUBLISH_FAILED      = [407, 'Failed to publish']
45 |     SERVER_ERROR        = [500, 'Internal server error']
46 | 
47 |   end
48 | end
49 | 


--------------------------------------------------------------------------------
/lib/faye/mixins/deferrable.rb:
--------------------------------------------------------------------------------
 1 | module Faye
 2 |   module Deferrable
 3 | 
 4 |     include EventMachine::Deferrable
 5 | 
 6 |     def set_deferred_status(status, *args)
 7 |       if status == :unknown
 8 |         @deferred_status = @deferred_args = @callbacks = @errbacks = nil
 9 |       end
10 |       super
11 |     end
12 | 
13 |   end
14 | end
15 | 


--------------------------------------------------------------------------------
/lib/faye/mixins/logging.rb:
--------------------------------------------------------------------------------
 1 | module Faye
 2 |   module Logging
 3 | 
 4 |     LOG_LEVELS = {
 5 |       :fatal  => 4,
 6 |       :error  => 3,
 7 |       :warn   => 2,
 8 |       :info   => 1,
 9 |       :debug  => 0
10 |     }
11 | 
12 |     LOG_LEVELS.each do |level, value|
13 |       define_method(level) { |*args| write_log(args, level) }
14 |     end
15 | 
16 |   private
17 | 
18 |     def write_log(message_args, level)
19 |       return unless Faye.logger
20 | 
21 |       message = message_args.shift.gsub(/\?/) do
22 |         Faye.to_json(message_args.shift)
23 |       end
24 | 
25 |       banner = "[#{ self.class.name }] "
26 | 
27 |       if Faye.logger.respond_to?(level)
28 |         Faye.logger.__send__(level, banner + message)
29 |       elsif Faye.logger.respond_to?(:call)
30 |         Faye.logger.call(banner + message)
31 |       end
32 |     end
33 | 
34 |   end
35 | end
36 | 


--------------------------------------------------------------------------------
/lib/faye/mixins/publisher.rb:
--------------------------------------------------------------------------------
 1 | module Faye
 2 |   module Publisher
 3 | 
 4 |     include ::WebSocket::Driver::EventEmitter
 5 | 
 6 |     alias :bind    :add_listener
 7 |     alias :trigger :emit
 8 | 
 9 |     def unbind(event, &listener)
10 |       if listener
11 |         remove_listener(event, &listener)
12 |       else
13 |         remove_all_listeners(event)
14 |       end
15 |     end
16 | 
17 |   end
18 | end
19 | 


--------------------------------------------------------------------------------
/lib/faye/mixins/timeouts.rb:
--------------------------------------------------------------------------------
 1 | module Faye
 2 |   module Timeouts
 3 |     def add_timeout(name, delay, &block)
 4 |       Engine.ensure_reactor_running!
 5 |       @timeouts ||= {}
 6 |       return if @timeouts.has_key?(name)
 7 |       @timeouts[name] = EventMachine.add_timer(delay) do
 8 |         @timeouts.delete(name)
 9 |         block.call
10 |       end
11 |     end
12 | 
13 |     def remove_timeout(name)
14 |       @timeouts ||= {}
15 |       timeout = @timeouts[name]
16 |       return if timeout.nil?
17 |       EventMachine.cancel_timer(timeout)
18 |       @timeouts.delete(name)
19 |     end
20 | 
21 |     def remove_all_timeouts
22 |       @timeouts ||= {}
23 |       @timeouts.keys.each { |name| remove_timeout(name) }
24 |     end
25 |   end
26 | end
27 | 


--------------------------------------------------------------------------------
/lib/faye/protocol/channel.rb:
--------------------------------------------------------------------------------
  1 | module Faye
  2 |   class Channel
  3 | 
  4 |     include Publisher
  5 |     attr_reader :name
  6 | 
  7 |     def initialize(name)
  8 |       super()
  9 |       @name = name
 10 |     end
 11 | 
 12 |     def <<(message)
 13 |       trigger(:message, message)
 14 |     end
 15 | 
 16 |     def unused?
 17 |       listener_count(:message).zero?
 18 |     end
 19 | 
 20 |     HANDSHAKE   = '/meta/handshake'
 21 |     CONNECT     = '/meta/connect'
 22 |     SUBSCRIBE   = '/meta/subscribe'
 23 |     UNSUBSCRIBE = '/meta/unsubscribe'
 24 |     DISCONNECT  = '/meta/disconnect'
 25 | 
 26 |     META        = 'meta'
 27 |     SERVICE     = 'service'
 28 | 
 29 |     class << self
 30 |       def expand(name)
 31 |         segments = parse(name)
 32 |         channels = ['/**', name]
 33 | 
 34 |         copy = segments.dup
 35 |         copy[copy.size - 1] = '*'
 36 |         channels << unparse(copy)
 37 | 
 38 |         1.upto(segments.size - 1) do |i|
 39 |           copy = segments[0...i]
 40 |           copy << '**'
 41 |           channels << unparse(copy)
 42 |         end
 43 | 
 44 |         channels
 45 |       end
 46 | 
 47 |       def valid?(name)
 48 |         Grammar::CHANNEL_NAME =~ name or
 49 |         Grammar::CHANNEL_PATTERN =~ name
 50 |       end
 51 | 
 52 |       def parse(name)
 53 |         return nil unless valid?(name)
 54 |         name.split('/')[1..-1]
 55 |       end
 56 | 
 57 |       def unparse(segments)
 58 |         '/' + segments.join('/')
 59 |       end
 60 | 
 61 |       def meta?(name)
 62 |         segments = parse(name)
 63 |         segments ? (segments.first == META) : nil
 64 |       end
 65 | 
 66 |       def service?(name)
 67 |         segments = parse(name)
 68 |         segments ? (segments.first == SERVICE) : nil
 69 |       end
 70 | 
 71 |       def subscribable?(name)
 72 |         return nil unless valid?(name)
 73 |         not meta?(name) and not service?(name)
 74 |       end
 75 |     end
 76 | 
 77 |     class Set
 78 |       def initialize
 79 |         @channels = {}
 80 |       end
 81 | 
 82 |       def keys
 83 |         @channels.keys
 84 |       end
 85 | 
 86 |       def remove(name)
 87 |         @channels.delete(name)
 88 |       end
 89 | 
 90 |       def has_subscription?(name)
 91 |         @channels.has_key?(name)
 92 |       end
 93 | 
 94 |       def subscribe(names, subscription)
 95 |         names.each do |name|
 96 |           channel = @channels[name] ||= Channel.new(name)
 97 |           channel.bind(:message, &subscription)
 98 |         end
 99 |       end
100 | 
101 |       def unsubscribe(name, subscription)
102 |         channel = @channels[name]
103 |         return false unless channel
104 |         channel.unbind(:message, &subscription)
105 |         if channel.unused?
106 |           remove(name)
107 |           true
108 |         else
109 |           false
110 |         end
111 |       end
112 | 
113 |       def distribute_message(message)
114 |         channels = Channel.expand(message['channel'])
115 |         channels.each do |name|
116 |           channel = @channels[name]
117 |           channel.trigger(:message, message) if channel
118 |         end
119 |       end
120 |     end
121 | 
122 |   end
123 | end
124 | 


--------------------------------------------------------------------------------
/lib/faye/protocol/extensible.rb:
--------------------------------------------------------------------------------
 1 | module Faye
 2 |   module Extensible
 3 |     include Logging
 4 | 
 5 |     def add_extension(extension)
 6 |       @extensions ||= []
 7 |       @extensions << extension
 8 |       extension.added(self) if extension.respond_to?(:added)
 9 |     end
10 | 
11 |     def remove_extension(extension)
12 |       return unless @extensions
13 |       @extensions.delete_if do |ext|
14 |         next false unless ext == extension
15 |         extension.removed(self) if extension.respond_to?(:removed)
16 |         true
17 |       end
18 |     end
19 | 
20 |     def pipe_through_extensions(stage, message, env, &callback)
21 |       debug('Passing through ? extensions: ?', stage, message)
22 | 
23 |       return callback.call(message) unless @extensions
24 |       extensions = @extensions.dup
25 | 
26 |       pipe = lambda do |message|
27 |         next callback.call(message) unless message
28 | 
29 |         extension = extensions.shift
30 |         next callback.call(message) unless extension
31 | 
32 |         next pipe.call(message) unless extension.respond_to?(stage)
33 | 
34 |         arity = extension.method(stage).arity
35 |         if arity >= 3
36 |           extension.__send__(stage, message, env, pipe)
37 |         else
38 |           extension.__send__(stage, message, pipe)
39 |         end
40 |       end
41 |       pipe.call(message)
42 |     end
43 | 
44 |   end
45 | end
46 | 


--------------------------------------------------------------------------------
/lib/faye/protocol/grammar.rb:
--------------------------------------------------------------------------------
 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 | 


--------------------------------------------------------------------------------
/lib/faye/protocol/publication.rb:
--------------------------------------------------------------------------------
1 | module Faye
2 |   class Publication
3 |     include Deferrable
4 |   end
5 | end


--------------------------------------------------------------------------------
/lib/faye/protocol/scheduler.rb:
--------------------------------------------------------------------------------
 1 | module Faye
 2 |   class Scheduler
 3 | 
 4 |     def initialize(message, options)
 5 |       @message  = message
 6 |       @options  = options
 7 |       @attempts = 0
 8 |     end
 9 | 
10 |     def interval
11 |       @options[:interval]
12 |     end
13 | 
14 |     def timeout
15 |       @options[:timeout]
16 |     end
17 | 
18 |     def deliverable?
19 |       attempts = @options[:attempts]
20 |       deadline = @options[:deadline]
21 |       now      = Time.now.to_f
22 | 
23 |       return false if attempts and @attempts >= attempts
24 |       return false if deadline and now > deadline
25 | 
26 |       true
27 |     end
28 | 
29 |     def send!
30 |       @attempts += 1
31 |     end
32 | 
33 |     def succeed!
34 |     end
35 | 
36 |     def fail!
37 |     end
38 | 
39 |     def abort!
40 |     end
41 | 
42 |   end
43 | end
44 | 


--------------------------------------------------------------------------------
/lib/faye/protocol/socket.rb:
--------------------------------------------------------------------------------
 1 | module Faye
 2 |   class Server
 3 | 
 4 |     class Socket
 5 |       def initialize(server, socket, env)
 6 |         @server = server
 7 |         @socket = socket
 8 |         @env    = env
 9 |       end
10 | 
11 |       def send(message)
12 |         @server.pipe_through_extensions(:outgoing, message, @env) do |piped_message|
13 |           @socket.send(Faye.to_json([piped_message])) if @socket
14 |         end
15 |       end
16 | 
17 |       def close
18 |         @socket.close if @socket
19 |         @socket = nil
20 |       end
21 |     end
22 | 
23 |   end
24 | end
25 | 


--------------------------------------------------------------------------------
/lib/faye/protocol/subscription.rb:
--------------------------------------------------------------------------------
 1 | module Faye
 2 |   class Subscription
 3 |     include Deferrable
 4 | 
 5 |     def initialize(client, channels, callback)
 6 |       @client    = client
 7 |       @channels  = channels
 8 |       @callback  = callback
 9 |       @cancelled = false
10 |     end
11 | 
12 |     def with_channel(&callback)
13 |       @with_channel = callback
14 |       self
15 |     end
16 | 
17 |     def call(*args)
18 |       message = args.first
19 | 
20 |       @callback.call(message['data']) if @callback
21 |       @with_channel.call(message['channel'], message['data']) if @with_channel
22 |     end
23 | 
24 |     def to_proc
25 |       @to_proc ||= lambda { |*a| call(*a) }
26 |     end
27 | 
28 |     def cancel
29 |       return if @cancelled
30 |       @client.unsubscribe(@channels, self)
31 |       @cancelled = true
32 |     end
33 | 
34 |     def unsubscribe
35 |       cancel
36 |     end
37 | 
38 |   end
39 | end
40 | 


--------------------------------------------------------------------------------
/lib/faye/transport/http.rb:
--------------------------------------------------------------------------------
 1 | module Faye
 2 | 
 3 |   class Transport::Http < Transport
 4 |     def self.usable?(dispatcher, endpoint, &callback)
 5 |       callback.call(URI === endpoint)
 6 |     end
 7 | 
 8 |     def encode(messages)
 9 |       Faye.to_json(messages)
10 |     end
11 | 
12 |     def request(messages)
13 |       content = encode(messages)
14 |       params  = build_params(content)
15 |       request = create_request(params)
16 | 
17 |       request.callback do
18 |         handle_response(messages, request.response)
19 |         store_cookies(request.response_header['SET_COOKIE'])
20 |       end
21 | 
22 |       request.errback do
23 |         handle_error(messages)
24 |       end
25 | 
26 |       request
27 |     end
28 | 
29 |   private
30 | 
31 |     def build_params(content)
32 |       headers = {
33 |         'Content-Length' => content.bytesize,
34 |         'Content-Type'   => 'application/json',
35 |         'Host'           => @endpoint.host + (@endpoint.port ? ":#{ @endpoint.port }" : '')
36 |       }
37 | 
38 |       params = {
39 |         :head => headers.merge(@dispatcher.headers),
40 |         :body => content
41 |       }
42 | 
43 |       cookie = get_cookies
44 |       params[:head]['Cookie'] = cookie unless cookie == ''
45 | 
46 |       params
47 |     end
48 | 
49 |     def create_request(params)
50 |       options = {
51 |         :inactivity_timeout => 0,
52 |         :tls => @dispatcher.tls
53 |       }
54 | 
55 |       if @proxy[:origin]
56 |         uri = URI(@proxy[:origin])
57 |         options[:proxy] = { :host => uri.host, :port => uri.port }
58 |         if uri.user
59 |           options[:proxy][:authorization] = [uri.user, uri.password]
60 |         end
61 |       end
62 | 
63 |       client = EventMachine::HttpRequest.new(@endpoint.to_s, options)
64 |       client.post(params)
65 |     end
66 | 
67 |     def handle_response(messages, response)
68 |       replies = MultiJson.load(response) rescue nil
69 |       if replies
70 |         receive(replies)
71 |       else
72 |         handle_error(messages)
73 |       end
74 |     end
75 |   end
76 | 
77 |   Transport.register 'long-polling', Transport::Http
78 | 
79 | end
80 | 


--------------------------------------------------------------------------------
/lib/faye/transport/local.rb:
--------------------------------------------------------------------------------
 1 | module Faye
 2 | 
 3 |   class Transport::Local < Transport
 4 |     def self.usable?(dispatcher, endpoint, &callback)
 5 |       callback.call(Server === endpoint)
 6 |     end
 7 | 
 8 |     def batching?
 9 |       false
10 |     end
11 | 
12 |     def request(messages)
13 |       EventMachine.next_tick do
14 |         @endpoint.process(messages, nil) do |replies|
15 |           receive(Faye.copy_object(replies))
16 |         end
17 |       end
18 |     end
19 |   end
20 | 
21 |   Transport.register 'in-process', Transport::Local
22 | 
23 | end
24 | 


--------------------------------------------------------------------------------
/lib/faye/transport/web_socket.rb:
--------------------------------------------------------------------------------
  1 | module Faye
  2 | 
  3 |   class Transport::WebSocket < Transport
  4 |     UNCONNECTED = 1
  5 |     CONNECTING  = 2
  6 |     CONNECTED   = 3
  7 | 
  8 |     PROTOCOLS = {
  9 |       'http'  => 'ws',
 10 |       'https' => 'wss'
 11 |     }
 12 | 
 13 |     include Deferrable
 14 | 
 15 |     class Request
 16 |       include Deferrable
 17 | 
 18 |       def close
 19 |         callback { |socket| socket.close }
 20 |       end
 21 |     end
 22 | 
 23 |     def self.usable?(dispatcher, endpoint, &callback)
 24 |       create(dispatcher, endpoint).usable?(&callback)
 25 |     end
 26 | 
 27 |     def self.create(dispatcher, endpoint)
 28 |       sockets = dispatcher.transports[:websocket] ||= {}
 29 |       sockets[endpoint.to_s] ||= new(dispatcher, endpoint)
 30 |     end
 31 | 
 32 |     def batching?
 33 |       false
 34 |     end
 35 | 
 36 |     def usable?(&callback)
 37 |       self.callback { callback.call(true) }
 38 |       self.errback { callback.call(false) }
 39 |       connect
 40 |     end
 41 | 
 42 |     def request(messages)
 43 |       @pending ||= Set.new
 44 |       messages.each { |message| @pending.add(message) }
 45 | 
 46 |       promise = Request.new
 47 | 
 48 |       callback do |socket|
 49 |         next unless socket and socket.ready_state == 1
 50 |         socket.send(Faye.to_json(messages))
 51 |         promise.succeed(socket)
 52 |       end
 53 | 
 54 |       connect
 55 |       promise
 56 |     end
 57 | 
 58 |     def connect
 59 |       @state ||= UNCONNECTED
 60 |       return unless @state == UNCONNECTED
 61 |       @state = CONNECTING
 62 | 
 63 |       url        = @endpoint.dup
 64 |       headers    = @dispatcher.headers.dup
 65 |       extensions = @dispatcher.ws_extensions
 66 |       cookie     = get_cookies
 67 | 
 68 |       url.scheme = PROTOCOLS[url.scheme]
 69 |       headers['Cookie'] = cookie unless cookie == ''
 70 | 
 71 |       options = {
 72 |         :extensions => extensions,
 73 |         :headers    => headers,
 74 |         :proxy      => @proxy,
 75 |         :tls        => @dispatcher.tls
 76 |       }
 77 | 
 78 |       socket = Faye::WebSocket::Client.new(url.to_s, [], options)
 79 | 
 80 |       socket.onopen = lambda do |*args|
 81 |         store_cookies(socket.headers['Set-Cookie'])
 82 |         @socket = socket
 83 |         @state = CONNECTED
 84 |         @ever_connected = true
 85 |         set_deferred_status(:succeeded, socket)
 86 |       end
 87 | 
 88 |       closed = false
 89 |       socket.onclose = socket.onerror = lambda do |*args|
 90 |         next if closed
 91 |         closed = true
 92 | 
 93 |         was_connected = (@state == CONNECTED)
 94 |         socket.onopen = socket.onclose = socket.onerror = socket.onmessage = nil
 95 | 
 96 |         @socket = nil
 97 |         @state = UNCONNECTED
 98 | 
 99 |         pending  = @pending ? @pending.to_a : []
100 |         @pending = nil
101 | 
102 |         if was_connected or @ever_connected
103 |           set_deferred_status(:unknown)
104 |           handle_error(pending, was_connected)
105 |         else
106 |           set_deferred_status(:failed)
107 |         end
108 |       end
109 | 
110 |       socket.onmessage = lambda do |event|
111 |         replies = MultiJson.load(event.data) rescue nil
112 |         next if replies.nil?
113 |         replies = [replies].flatten
114 | 
115 |         replies.each do |reply|
116 |           next unless reply.has_key?('successful')
117 |           next unless message = @pending.find { |m| m['id'] == reply['id'] }
118 |           @pending.delete(message)
119 |         end
120 |         receive(replies)
121 |       end
122 |     end
123 | 
124 |     def close
125 |       return unless @socket
126 |       @socket.close
127 |     end
128 |   end
129 | 
130 |   Transport.register 'websocket', Transport::WebSocket
131 | 
132 | end
133 | 


--------------------------------------------------------------------------------
/lib/faye/util/namespace.rb:
--------------------------------------------------------------------------------
 1 | module Faye
 2 |   class Namespace
 3 | 
 4 |     extend Forwardable
 5 |     def_delegator :@used, :delete, :release
 6 |     def_delegator :@used, :has_key?, :exists?
 7 | 
 8 |     def initialize
 9 |       @used = {}
10 |     end
11 | 
12 |     def generate
13 |       name = Engine.random
14 |       name = Engine.random while @used.has_key?(name)
15 |       @used[name] = name
16 |     end
17 | 
18 |   end
19 | end
20 | 


--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |           "name": "faye",
 3 |           "description": "Simple pub/sub messaging for the web",
 4 |           "homepage": "https://faye.jcoglan.com",
 5 |           "author": "James Coglan <jcoglan@gmail.com> (http://jcoglan.com/)",
 6 |           "keywords": [
 7 |                     "comet",
 8 |                     "websocket",
 9 |                     "pubsub",
10 |                     "bayeux",
11 |                     "ajax",
12 |                     "http"
13 |           ],
14 |           "license": "Apache-2.0",
15 |           "version": "1.4.1",
16 |           "engines": {
17 |                     "node": ">=0.8.0"
18 |           },
19 |           "main": "src/faye_node",
20 |           "browser": "src/faye_browser",
21 |           "dependencies": {
22 |                     "asap": "*",
23 |                     "csprng": "*",
24 |                     "faye-websocket": ">=0.9.1",
25 |                     "safe-buffer": "*",
26 |                     "tough-cookie": "*",
27 |                     "tunnel-agent": "*"
28 |           },
29 |           "devDependencies": {
30 |                     "jstest": "~1.0.0",
31 |                     "mime": "~1.2.0",
32 |                     "permessage-deflate": ">=0.1.0",
33 |                     "promises-aplus-tests": "~2.1.0",
34 |                     "webpack": "~4",
35 |                     "webpack-cli": "~4",
36 |                     "imports-loader": "<1.0.0"
37 |           },
38 |           "scripts": {
39 |                     "start": "webpack --watch",
40 |                     "test": "find spec -name '*_spec.js' | xargs jstest",
41 |                     "promise": "promises-aplus-tests src/util/promise.js"
42 |           },
43 |           "repository": {
44 |                     "type": "git",
45 |                     "url": "git://github.com/faye/faye.git"
46 |           },
47 |           "bugs": "https://github.com/faye/faye/issues"
48 | }
49 | 


--------------------------------------------------------------------------------
/site/config/compass.rb:
--------------------------------------------------------------------------------
1 | require "staticmatic/compass"
2 | 
3 | project_type = :staticmatic


--------------------------------------------------------------------------------
/site/config/site.rb:
--------------------------------------------------------------------------------
 1 | # Default is 3000
 2 | # configuration.preview_server_port = 3000
 3 |  
 4 | # Default is localhost
 5 | # configuration.preview_server_host = "localhost"
 6 |  
 7 | # Default is true
 8 | # When false .html & index.html get stripped off generated urls
 9 | # configuration.use_extensions_for_page_links = true
10 |  
11 | # Default is an empty hash
12 | # configuration.sass_options = {}
13 |  
14 | # Default is an empty hash
15 | # http://haml-lang.com/docs/yardoc/file.HAML_REFERENCE.html#options
16 | # configuration.haml_options = {}


--------------------------------------------------------------------------------
/site/site/images/aha.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/faye/faye/5230ad568baf4d9d7b7b18fd990e4c068b8473ee/site/site/images/aha.png


--------------------------------------------------------------------------------
/site/site/images/buster.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/faye/faye/5230ad568baf4d9d7b7b18fd990e4c068b8473ee/site/site/images/buster.png


--------------------------------------------------------------------------------
/site/site/images/chaxpert.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/faye/faye/5230ad568baf4d9d7b7b18fd990e4c068b8473ee/site/site/images/chaxpert.png


--------------------------------------------------------------------------------
/site/site/images/cloudblocks.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/faye/faye/5230ad568baf4d9d7b7b18fd990e4c068b8473ee/site/site/images/cloudblocks.png


--------------------------------------------------------------------------------
/site/site/images/faye-cluster.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/faye/faye/5230ad568baf4d9d7b7b18fd990e4c068b8473ee/site/site/images/faye-cluster.png


--------------------------------------------------------------------------------
/site/site/images/faye-internals.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/faye/faye/5230ad568baf4d9d7b7b18fd990e4c068b8473ee/site/site/images/faye-internals.png


--------------------------------------------------------------------------------
/site/site/images/faye-logo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/faye/faye/5230ad568baf4d9d7b7b18fd990e4c068b8473ee/site/site/images/faye-logo.gif


--------------------------------------------------------------------------------
/site/site/images/gitter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/faye/faye/5230ad568baf4d9d7b7b18fd990e4c068b8473ee/site/site/images/gitter.png


--------------------------------------------------------------------------------
/site/site/images/groupme.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/faye/faye/5230ad568baf4d9d7b7b18fd990e4c068b8473ee/site/site/images/groupme.png


--------------------------------------------------------------------------------
/site/site/images/ineda.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/faye/faye/5230ad568baf4d9d7b7b18fd990e4c068b8473ee/site/site/images/ineda.png


--------------------------------------------------------------------------------
/site/site/images/medeo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/faye/faye/5230ad568baf4d9d7b7b18fd990e4c068b8473ee/site/site/images/medeo.png


--------------------------------------------------------------------------------
/site/site/images/myspace.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/faye/faye/5230ad568baf4d9d7b7b18fd990e4c068b8473ee/site/site/images/myspace.png


--------------------------------------------------------------------------------
/site/site/images/nokia_mix_party.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/faye/faye/5230ad568baf4d9d7b7b18fd990e4c068b8473ee/site/site/images/nokia_mix_party.png


--------------------------------------------------------------------------------
/site/site/images/pathient.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/faye/faye/5230ad568baf4d9d7b7b18fd990e4c068b8473ee/site/site/images/pathient.png


--------------------------------------------------------------------------------
/site/site/images/podio.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/faye/faye/5230ad568baf4d9d7b7b18fd990e4c068b8473ee/site/site/images/podio.png


--------------------------------------------------------------------------------
/site/site/images/xydo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/faye/faye/5230ad568baf4d9d7b7b18fd990e4c068b8473ee/site/site/images/xydo.png


--------------------------------------------------------------------------------
/site/site/javascripts/analytics.js:
--------------------------------------------------------------------------------
 1 | var _gaq = _gaq || [];
 2 | _gaq.push(['_setAccount', 'UA-873493-8']);
 3 | _gaq.push(['_trackPageview']);
 4 | 
 5 | (function() {
 6 |   var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
 7 |   ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
 8 |   var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
 9 | })();
10 | 


--------------------------------------------------------------------------------
/site/site/stylesheets/github.css:
--------------------------------------------------------------------------------
 1 | .atn { color:#008080 } 
 2 | .atv { color:#008080 } 
 3 | .com { color:#999988 } 
 4 | .dec { color:#000000; font-weight:bold } 
 5 | .kwd { color:#000000; font-weight:bold } 
 6 | .lit { color:#009999 } 
 7 | .pln { color:#000000 } 
 8 | .pun { color:#666666 } 
 9 | .str { color:#dd1144 } 
10 | .tag { color:#000080 } 
11 | .typ { color:#445588 } 
12 | 


--------------------------------------------------------------------------------
/site/src/layouts/default.haml:
--------------------------------------------------------------------------------
 1 | !!! 5
 2 | %html
 3 |   %head
 4 |     %meta{:'http-equiv' => "Content-type", :content => "text/html; charset=utf-8"}
 5 |     %title Faye: Simple pub/sub messaging for the web
 6 |     = stylesheets
 7 |     %link{'rel' => 'stylesheet', 'type' => 'text/css', 'href' => '//fonts.googleapis.com/css?family=Inconsolata:400,700|Open+Sans:300italic,400italic,700italic,400,300,700'}
 8 |   %body{:onload => 'prettyPrint()'}
 9 | 
10 |     .header
11 |       .container
12 |         %h1
13 |           =link "Faye", "/"
14 |         %h2 Simple pub/sub messaging for the web
15 |         .docs
16 |           %h3 Documentation
17 |           %ul
18 |             %li
19 |               =link "Node.js server", "/node.html"
20 |             %li
21 |               =link "Ruby server", "/ruby.html"
22 |             %li
23 |               =link "Browser client", "/browser.html"
24 |             %li
25 |               =link "Security advice", "/security.html"
26 |         .community
27 |           %h3 Developers
28 |           %ul
29 |             %li
30 |               =link "Architecture"
31 |             %li
32 |               =link "GitHub", "https://github.com/faye/faye"
33 |             %li
34 |               =link "Mailing list", "http://groups.google.com/group/faye-users"
35 |         .download
36 |           %h3 Download
37 |           %ul
38 |             %li
39 |               =link "Packages for Node.js, Ruby and browsers", "/download.html"
40 | 
41 |     .main
42 |       .container
43 |         = yield
44 |         .clear
45 | 
46 |     .footer
47 |       .container
48 |         :textile
49 |           &copy; 2009&ndash;2025 "James Coglan":http://jcoglan.com.
50 |           Released under the Apache 2.0 license.
51 | 
52 |     = javascripts 'prettify'
53 |     :plain
54 |       <script type="text/javascript">
55 |         (function() {
56 |           var pre = document.getElementsByTagName('pre'), n = pre.length
57 |           while (n--) {
58 |             if (!pre[n].className) pre[n].className = 'prettyprint'
59 |           }
60 |           prettyPrint()
61 |         })()
62 |       </script>
63 | 


--------------------------------------------------------------------------------
/site/src/pages/browser/extensions.haml:
--------------------------------------------------------------------------------
 1 | .content
 2 |   = partial 'browser_navigation'
 3 | 
 4 |   :textile
 5 |     h4. Extensions
 6 | 
 7 |     Faye clients support an extension system that lets you intercept messages as
 8 |     they pass between the client and the server. To add an extension to a client,
 9 |     just call:
10 | 
11 |     <pre>client.addExtension(extension);</pre>
12 | 
13 |     @extension@ should be an object with an @incoming()@ or @outgoing()@ method
14 |     (or both). These methods accept a message and a callback function, and
15 |     should call the callback with the message after any necessary modifications
16 |     have been made. For example, a simple logging extension would look like:
17 | 
18 |     <pre>Logger = {
19 |       incoming: function(message, callback) {
20 |         console.log('incoming', message);
21 |         callback(message);
22 |       },
23 |       outgoing: function(message, callback) {
24 |         console.log('outgoing', message);
25 |         callback(message);
26 |       }
27 |     };
28 | 
29 |     client.addExtension(Logger);</pre>
30 | 
31 |     For more information on writing extensions, see the "Node server":/node.html
32 |     or "Ruby server":/ruby.html documentation.
33 | 


--------------------------------------------------------------------------------
/site/src/pages/browser/publishing.haml:
--------------------------------------------------------------------------------
 1 | .content
 2 |   = partial 'browser_navigation'
 3 | 
 4 |   :textile
 5 |     h4. Sending messages
 6 | 
 7 |     Clients do not send each other messages directly, instead they send their
 8 |     messages to channels, and the server figures out which clients need to
 9 |     receive the message. You can send a message using the @#publish()@ method,
10 |     passing in the channel name and a message object.
11 | 
12 |     <pre>client.publish('/foo', {text: 'Hi there'});</pre>
13 | 
14 |     The message object can be any arbitrary JavaScript object that can be
15 |     serialized to JSON, so it can contain strings, numbers, booleans, arrays and
16 |     other objects. There are no required fields, and the object will be
17 |     delivered verbatim to any subscriber functions listening to that channel.
18 | 
19 |     Just like @subscribe()@, the @publish()@ method returns a
20 |     "promise":http://promisesaplus.com/ that is fulfilled when the server
21 |     acknowledges the message. This just means the server received and routed the
22 |     message successfully, not that it has been received by all other clients.
23 |     The promise is rejected if the server explcitly returns an error saying it
24 |     could not publish the message to other clients; network errors are therefore
25 |     not covered by this API.
26 | 
27 |     <pre>var publication = client.publish('/foo', {text: 'Hi there'});
28 | 
29 |     publication.then(function() {
30 |       alert('Message received by server!');
31 |     }, function(error) {
32 |       alert('There was a problem: ' + error.message);
33 |     });</pre>
34 | 
35 |     The Faye client will automatically try to resend your messages if it
36 |     encounters a network error. It will attempt to resend the message until it
37 |     receives confirmation that the server has processed it. Sometimes, you might
38 |     not want messages to be retried indefinitely, and Faye gives you two ways to
39 |     limit this behaviour; see the "dispatch options":/browser/dispatch.html.
40 | 
41 | 


--------------------------------------------------------------------------------
/site/src/pages/browser/subscribing.haml:
--------------------------------------------------------------------------------
 1 | .content
 2 |   = partial 'browser_navigation'
 3 | 
 4 |   :textile
 5 |     h4. Subscribing to channels
 6 | 
 7 |     Clients receive data from other clients by subscribing to channels. Whenever
 8 |     any client sends a message to a channel you're subscribed to, Faye will
 9 |     notify your client with the new message.
10 | 
11 |     Channel names must be formatted as absolute path names whose segments may
12 |     contain only letters, numbers, and the symbols @-@, @_@, @!@, @~@, @(@, @)@,
13 |     @$@ and @@@. Channel names may also end with wildcards:
14 | 
15 |     * The @*@ wildcard matches any channel segment. So @/foo/*@ matches @/foo/bar@
16 |       and @/foo/thing@ but not @/foo/bar/thing@.
17 |     * The @**@ wildcard matches any channel name recursively. So @/foo/**@
18 |       matches @/foo/bar@, @/foo/thing@ and @/foo/bar/thing@.
19 | 
20 |     So for example if you subscribe to @/foo/*@ and someone sends a message to
21 |     @/foo/bar@, you will receive that message.
22 | 
23 |     Clients should subscribe to channels using the @#subscribe()@ method:
24 | 
25 |     <pre>var subscription = client.subscribe('/foo', function(message) {
26 |       // handle message
27 |     });</pre>
28 | 
29 |     The subscriber function will be invoked when anybody sends a message to
30 |     @/foo@, and the @message@ parameter will contain the sent message object. A
31 |     client may bind multiple listeners to a channel, and the Faye client handles
32 |     all the management of those listeners and makes sure the server sends it the
33 |     right messages.
34 | 
35 |     The @subscribe()@ method returns a @Subscription@ object, which you can
36 |     cancel if you want to remove that listener from the channel.
37 | 
38 |     <pre>subscription.cancel();</pre>
39 | 
40 |     The @Subscription@ object is a "promise":http://promisesaplus.com/ that is
41 |     fulfilled when the subscription has been acknowledged by the server:
42 | 
43 |     <pre>subscription.then(function() {
44 |       alert('Subscription is now active!');
45 |     });</pre>
46 | 
47 |     If you're subscribing to a wildcard channel, you may want to receive the
48 |     specific channel the message was published to in your subscriber function.
49 |     You can do this using the `withChannel()` method:
50 | 
51 |     <pre>client.subscribe('/foo/*').withChannel(function(channel, message) {
52 |       // handle message
53 |     });</pre>
54 | 
55 | 


--------------------------------------------------------------------------------
/site/src/pages/browser/transport.haml:
--------------------------------------------------------------------------------
 1 | .content
 2 |   = partial 'browser_navigation'
 3 | 
 4 |   :textile
 5 |     h4. Network errors
 6 | 
 7 |     As explained in the "architecture documentation":/architecture.html, the
 8 |     Faye client does not talk to the network directly but uses a 'transport'
 9 |     object based on WebSocket, XMLHttpRequest, and other network APIs.
10 | 
11 |     The client exposes an abstract interface for checking the status of
12 |     whichever connection type is in use, which is useful for giving the user
13 |     feedback about the connection, or making your application deal with being
14 |     offline. You can listen to the @transport:up@ and @transport:down@ events to
15 |     be notified that the client is online or offline.
16 | 
17 |     <pre>client.on('transport:down', function() {
18 |       // the client is offline
19 |     });
20 | 
21 |     client.on('transport:up', function() {
22 |       // the client is online
23 |     });</pre>
24 | 
25 |     Note that these events _do not_ reflect the status of the client's session,
26 |     or whether there is literally a network connection active. For example, you
27 |     are not told that the transport is down just because a long-polling request
28 |     just completed. The transport is only considered down if a request or
29 |     WebSocket connection explicitly fails, or times out, indicating the server
30 |     is unreachable.
31 | 
32 |     Also remember that Faye deals with buffering and re-sending messages for you,
33 |     so you don't need to deal with that. These events are simply for providing
34 |     feedback on the health of the connection.
35 | 


--------------------------------------------------------------------------------
/site/src/pages/download.haml:
--------------------------------------------------------------------------------
 1 | .content
 2 |   :textile
 3 |     h3. Download Faye
 4 |     
 5 |     The latest version is 1.4.1, released June 17 2025. It is open-source
 6 |     software, released under the Apache 2.0 license. You can follow development
 7 |     on Faye's "GitHub page":https://github.com/faye/faye.
 8 | 
 9 |     h4. Download for Node.js and web browsers
10 | 
11 |     The Node.js version is available through "npm":https://www.npmjs.com/. This
12 |     package contains a copy of the browser client, which is served up by the
13 |     Faye server when running.
14 | 
15 |     <pre>npm install faye</pre>
16 | 
17 |     If you're using "Browserify":http://browserify.org/,
18 |     "Webpack":https://webpack.github.io/ or a similar build tool, then
19 |     requiring @faye@ will get you the client-side package, for example:
20 | 
21 |     <pre>var faye = require('faye');
22 | 
23 |     var client = new faye.Client('http://localhost:8000/faye');</pre>
24 | 
25 |     If you're not using such a toolchain, you can get the client bundle from
26 |     the @client@ directory within the npm install.
27 | 
28 |     h4. Download for Ruby
29 | 
30 |     For Ruby platforms, Faye is installable through RubyGems.
31 | 
32 |     <pre>gem install faye</pre>
33 | 
34 |     This package also includes the browser client which is served up by the Faye
35 |     server when running.
36 | 


--------------------------------------------------------------------------------
/site/src/pages/index.haml:
--------------------------------------------------------------------------------
 1 | .front-matter
 2 |   .intro
 3 |     :textile
 4 |       h3. What is it?
 5 | 
 6 |       Faye is a publish-subscribe messaging system based on the
 7 |       "Bayeux":https://docs.cometd.org/reference/index.html#_bayeux protocol.
 8 |       It provides message servers for "Node.js":http://nodejs.org and
 9 |       "Ruby":http://www.ruby-lang.org, and clients for use on the server and in
10 |       all major web browsers.
11 | 
12 |       h3. Who uses it?
13 | 
14 |       "!/images/aha.png!":http://www.aha.io/
15 |       "!/images/buster.png!":http://busterjs.org/
16 |       "!/images/chaxpert.png!":https://community.chaxpert.net/
17 |       "!/images/cloudblocks.png!":http://www.cloud66.com/
18 |       "!/images/gitter.png!":https://gitter.im/
19 |       "!/images/groupme.png!":http://groupme.com/
20 |       "!/images/ineda.png!":http://www.i-neda.com/
21 |       "!/images/medeo.png!":https://medeo.ca/
22 |       "!/images/myspace.png!":http://new.myspace.com/
23 |       "!/images/nokia_mix_party.png!":http://mixparty.nokia.com/
24 |       "!/images/pathient.png!":http://www.pathient.com/
25 |       "!/images/podio.png!":https://podio.com/
26 |       "!/images/xydo.png!":http://www.xydo.com/
27 | 
28 |   :textile
29 |     h3. %(number)1.% Start a server
30 | 
31 |     <pre>var http = require('http'),
32 |         faye = require('faye');
33 | 
34 |     var server = http.createServer(),
35 |         bayeux = new faye.NodeAdapter({mount: '/'});
36 | 
37 |     bayeux.attach(server);
38 |     server.listen(8000);</pre>
39 | 
40 |     h3. %(number)2.% Create a client
41 | 
42 |     <pre>var client = new Faye.Client('http://localhost:8000/');
43 | 
44 |     client.subscribe('/messages', function(message) {
45 |       alert('Got a message: ' + message.text);
46 |     });
47 |     </pre>
48 | 
49 |     h3. %(number)3.% Send messages
50 | 
51 |     <pre>client.publish('/messages', {
52 |       text: 'Hello world'
53 |     });
54 |     </pre>
55 | 


--------------------------------------------------------------------------------
/site/src/pages/node.haml:
--------------------------------------------------------------------------------
 1 | .content
 2 |   = partial 'node_navigation'
 3 | 
 4 |   :textile
 5 |     h4. Setting up
 6 | 
 7 |     All Faye clients need a central messaging server to communicate with; the
 8 |     server records which clients are subscribed to which channels and handles
 9 |     routing of messages between clients. Setting up a server in Node.js is
10 |     simple:
11 | 
12 |     <pre>var http = require('http'),
13 |         faye = require('faye');
14 | 
15 |     var server = http.createServer(),
16 |         bayeux = new faye.NodeAdapter({mount: '/faye', timeout: 45});
17 | 
18 |     bayeux.attach(server);
19 |     server.listen(8000);</pre>
20 | 
21 |     The @NodeAdapter@ class supports these options during setup:
22 | 
23 |     * *@mount@* - the path on the host at which the Faye service is available.
24 |       In this example, clients would connect to @http://localhost:8000/faye@ to
25 |       talk to the server. The server will handle _any_ request whose path begins
26 |       with the @mount@ path; this is so that it can interoperate with clients
27 |       that use different request paths for different channels.
28 |     * *@timeout@* - the maximum time to hold a connection open before returning
29 |       the response. This is given in seconds and must be smaller than the
30 |       timeout on your frontend webserver.
31 |     * *@engine@* - (optional) the type and parameters for the engine you want to
32 |       use - see the "engines documentation":/node/engines.html
33 |     * *@ping@* - (optional) how often, in seconds, to send keep-alive ping
34 |       messages over WebSocket and EventSource connections. Use this if your Faye
35 |       server will be accessed through a proxy that kills idle connections.
36 | 
37 |     It also allows WebSocket extensions to be plugged in. For example, to enable
38 |     "permessage-deflate":https://github.com/faye/permessage-deflate-node for
39 |     supporting clients:
40 | 
41 |     <pre>var faye    = require('faye'),
42 |         deflate = require('permessage-deflate');
43 | 
44 |     var bayeux = new faye.NodeAdapter({mount: '/faye', timeout: 45});
45 |     bayeux.addWebsocketExtension(deflate);</pre>
46 | 
47 |     You can use any extension that's compatible with the
48 |     "websocket-extensions":https://github.com/faye/websocket-extensions-node
49 |     framework.
50 | 
51 |     Faye should be attached to an existing HTTP server using the @attach()@
52 |     method. It will handle all requests to paths matching the @mount@ path and
53 |     delegate all other requests to your handlers.
54 | 
55 |     <pre>var http = require('http'),
56 |         faye = require('faye');
57 | 
58 |     var bayeux = new faye.NodeAdapter({mount: '/faye', timeout: 45});
59 | 
60 |     // Handle non-Bayeux requests
61 |     var server = http.createServer(function(request, response) {
62 |       response.writeHead(200, {'Content-Type': 'text/plain'});
63 |       response.end('Hello, non-Bayeux request');
64 |     });
65 | 
66 |     bayeux.attach(server);
67 |     server.listen(8000);</pre>
68 | 


--------------------------------------------------------------------------------
/site/src/pages/node/clients.haml:
--------------------------------------------------------------------------------
 1 | .content
 2 |   = partial 'node_navigation'
 3 | 
 4 |   :textile
 5 |     h4. Server-side Node.js clients
 6 | 
 7 |     You can use Faye clients on the server side to send messages to in-browser
 8 |     clients or to other server-side processes. The API is identical to the
 9 |     "browser client":/browser.html.
10 | 
11 |     To create a client, just supply the host you want to connect to:
12 | 
13 |     <pre>var client = new faye.Client('http://localhost:8000/faye');</pre>
14 | 
15 |     You can then use @client.subscribe()@ and @client.publish()@ to send
16 |     messages to other clients; see the "browser client":/browser.html
17 |     documentation for more information.
18 | 
19 |     The server has its own client attached to it so you can use the server to
20 |     send messages to browsers. This client has direct access to the server
21 |     without going over HTTP, and is thus more efficient. To send messages
22 |     through the server just use the @#getClient()@ method.
23 | 
24 |     <pre>bayeux.getClient().publish('/email/new', {
25 |       text:       'New email has arrived!',
26 |       inboxSize:  34
27 |     });</pre>
28 | 
29 |     h4. Transport control
30 | 
31 |     When using the client on the server, you can control parts of the transport
32 |     layer that the browser doesn't provide access to. For a start, headers
33 |     added using the @client.setHeader()@ method will be added to WebSocket
34 |     connections, not just regular HTTP requests.
35 | 
36 |     For the transport layer, the server-side client uses the Node.js
37 |     "https":https://nodejs.org/api/https.html and
38 |     "tls":https://nodejs.org/api/tls.html modules to handle HTTPS endpoints. If
39 |     you need to configure anything about the TLS connection, use the `tls`
40 |     option which is passed through to
41 |     "@tls.connect()@":https://nodejs.org/api/tls.html#tls_tls_connect_options_callback.
42 |     For example, to set your own root certificate instead of using the system
43 |     defaults:
44 | 
45 |     <pre>var client = new faye.Client(url, {
46 |       tls: {
47 |         ca: fs.readFileSync('path/to/certificate.pem')
48 |       }
49 |     });</pre>
50 | 
51 |     You can also request that all connections go via an HTTP proxy:
52 | 
53 |     <pre>var client = new faye.Client(url, {
54 |       proxy: 'http://username:password@proxy.example.com'
55 |     });</pre>
56 | 
57 |     You can also set the @http_proxy@ or @https_proxy@ environment variables,
58 |     which will make all Faye client connections use the given proxy by default;
59 |     @http_proxy@ for @http:@ and @ws:@ requests, and @https_proxy@ for @https:@
60 |     and @wss:@ requests.
61 | 
62 |     Finally, the WebSocket transport can be configured to use protocol
63 |     extensions; any extension library compatible with
64 |     "websocket-extensions":https://github.com/faye/websocket-extensions-node
65 |     will work. For example, to add
66 |     "permessage-deflate":https://github.com/faye/permessage-deflate-node:
67 | 
68 |     <pre>var deflate = require('permessage-deflate');
69 | 
70 |     client.addWebsocketExtension(deflate);</pre>
71 | 


--------------------------------------------------------------------------------
/site/src/pages/node/websockets.haml:
--------------------------------------------------------------------------------
 1 | .content
 2 |   = partial 'node_navigation'
 3 | 
 4 |   :textile
 5 |     h4. WebSockets for Node
 6 | 
 7 |     Since version 0.5, Faye has supported WebSockets as a network transport for
 8 |     sending messages to the browser. The code that handles this is decoupled
 9 |     from the rest of the library and can be used to make your own WebSocket
10 |     applications.
11 | 
12 |     These classes are available as a stand-alone library,
13 |     "faye-websocket":https://github.com/faye/faye-websocket-node
14 | 


--------------------------------------------------------------------------------
/site/src/pages/ruby/clients.haml:
--------------------------------------------------------------------------------
 1 | .content
 2 |   = partial 'ruby_navigation'
 3 | 
 4 |   :textile
 5 |     h4. Server-side Ruby clients
 6 | 
 7 |     You can use Faye clients on the server side to send messages to in-browser
 8 |     clients or to other server-side processes. The API is identical to the
 9 |     "browser client":/browser.html.
10 | 
11 |     To create a client, just supply the host you want to connect to:
12 | 
13 |     <pre>client = Faye::Client.new('http://localhost:9292/faye')</pre>
14 | 
15 |     You can then use @client.subscribe()@ and @client.publish()@ to send
16 |     messages to other clients; the API is similar to the "browser client":/browser.html
17 |     only you need to run the client inside EventMachine:
18 | 
19 |     <pre>require 'eventmachine'
20 | 
21 |     EM.run {
22 |       client = Faye::Client.new('http://localhost:9292/faye')
23 | 
24 |       client.subscribe('/foo') do |message|
25 |         puts message.inspect
26 |       end
27 | 
28 |       client.publish('/foo', 'text' => 'Hello world')
29 |     }</pre>
30 | 
31 |     Note that the Ruby client uses @EventMachine::Deferrable@ instead of
32 |     "promises":http://promisesaplus.com/, for example you detect success and
33 |     failure of a publication like so:
34 | 
35 |     <pre>publication = client.publish('/foo', 'text' => 'Hello world')
36 | 
37 |     publication.callback do
38 |       puts 'Message received by server!'
39 |     end
40 | 
41 |     publication.errback do |error|
42 |       puts 'There was a problem: ' + error.message
43 |     end</pre>
44 | 
45 |     If you need to set custom headers to talk to your Bayeux server, use the
46 |     @set_header@ method:
47 | 
48 |     <pre>client.set_header('Authorization', 'OAuth abcd-1234')</pre>
49 | 
50 |     The server has its own client attached to it so you can use the server to
51 |     send messages to browsers. This client has direct access to the server
52 |     without going over HTTP, and is thus more efficient. To send messages
53 |     through the server just use the @#get_client@ method.
54 | 
55 |     <pre>bayeux.get_client.publish('/email/new', {
56 |       'text'      => 'New email has arrived!',
57 |       'inboxSize' => 34
58 |     })</pre>
59 | 
60 |     h4. Transport control
61 | 
62 |     When using the client on the server, you can control parts of the transport
63 |     layer that the browser doesn't provide access to. For a start, headers
64 |     added using the @client.set_header@ method will be added to WebSocket
65 |     connections, not just regular HTTP requests.
66 | 
67 |     From version 1.4.0 onwards, @Faye::Client@ uses the underlying transport
68 |     libraries ("em-http-request":https://rubygems.org/gems/em-http-request and
69 |     "faye-websocket":https://rubygems.org/gems/em-http-request, both based on
70 |     "EventMachine":https://rubygems.org/gems/eventmachine) to perform TLS
71 |     certificate validation by default for HTTPS endpoints. If you don't want
72 |     this behaviour, you can turn it off via the @:tls@ option:
73 | 
74 |     <pre>client = Faye::Client.new(url, {
75 |       :tls => { :verify_peer => false }
76 |     })</pre>
77 | 
78 |     You can also request that all connections go via an HTTP proxy:
79 | 
80 |     <pre>client = Faye::Client.new(url, {
81 |       :proxy => 'http://username:password@proxy.example.com'
82 |     })</pre>
83 | 
84 |     You can also set the @http_proxy@ or @https_proxy@ environment variables,
85 |     which will make all Faye client connections use the given proxy by default;
86 |     @http_proxy@ for @http:@ and @ws:@ requests, and @https_proxy@ for @https:@
87 |     and @wss:@ requests.
88 | 
89 |     Finally, the WebSocket transport can be configured to use protocol
90 |     extensions; any extension library compatible with
91 |     "websocket-extensions":https://github.com/faye/websocket-extensions-ruby
92 |     will work. For example, to add
93 |     "permessage-deflate":https://github.com/faye/permessage-deflate-ruby:
94 | 
95 |     <pre>require 'permessage_deflate'
96 | 
97 |     client.add_websocket_extension(PermessageDeflate)</pre>
98 | 


--------------------------------------------------------------------------------
/site/src/pages/ruby/websockets.haml:
--------------------------------------------------------------------------------
 1 | .content
 2 |   = partial 'ruby_navigation'
 3 | 
 4 |   :textile
 5 |     h4. WebSockets for Ruby
 6 | 
 7 |     Since version 0.5, Faye has supported WebSockets as a network transport for
 8 |     sending messages to the browser. The code that handles this is decoupled
 9 |     from the rest of the library and can be used to make your own WebSocket
10 |     applications.
11 | 
12 |     These classes are available as a stand-alone library,
13 |     "faye-websocket":https://github.com/faye/faye-websocket-ruby
14 | 


--------------------------------------------------------------------------------
/site/src/pages/security.haml:
--------------------------------------------------------------------------------
 1 | .content
 2 |   = partial 'security_navigation'
 3 | 
 4 |   :textile
 5 |     h4. Securing your realtime applications
 6 | 
 7 |     Like any web-accessible service, Faye must be protected against malicious
 8 |     usage by attackers. Out of the box, it is a cross-domain-accessible server
 9 |     with no restrictions on subscribing and publishing, but its extension system
10 |     allows you to easily impose restrictions appropriate to your application.
11 | 
12 |     Though written primarily for Faye, this guide contains advice that affects
13 |     many realtime and socket-based applications. The core concerns with such
14 |     applications are:
15 | 
16 |     * Can a client get access to data it should not have access to?
17 |     * Can a client trust the origin of the messages it receives?
18 | 
19 |     The details of these questions will depend on your application, but the
20 |     following is a set of general guidelines for avoiding broad classes of
21 |     mistakes. It is not prescriptive, in that the solutions presented here are
22 |     not how you _have to_ implement things. They simply try to illustrate
23 |     patterns of security risks and possible solutions.
24 | 
25 |     As with all web applications, it is crucial to remember that any program
26 |     with Internet access, including server-side scripts and in-browser
27 |     JavaScript code on other domains, can send requests to your server. It is up
28 |     to you to protect it and your users from harm.
29 | 
30 |     h4. What is Faye?
31 | 
32 |     Faye is an implementation of the "Bayeux":https://docs.cometd.org/current/reference/#_bayeux
33 |     protocol. Clients send messages to each other via a central server, by
34 |     sending JSON messages over various flavours of HTTP-based transport,
35 |     including WebSocket, EventSource, XMLHttpRequest, CORS and JSON-P. Clients
36 |     that run in the browser are not constrained by the same-origin policy.
37 | 
38 |     Messages are routed using subscriptions. When a client publishes a message,
39 |     the server determines which clients are subscribed to the message's
40 |     @channel@ and forwards the message to them verbatim. This means the
41 |     _whole wire message is forwarded_, not just the @data@ field containing the
42 |     application payload.
43 | 
44 |     A special set of channels whose names begin with @/meta/@ are used for
45 |     operating the protocol itself, and messages sent to these channels are
46 |     never forwarded to other clients.
47 | 
48 |     Messages pass through extensions on their way into and out of the clients
49 |     and server. Data required by extensions is typically sent in the message's
50 |     @ext@ field. Any changes made to a message by a server-side extension will
51 |     be reflected in the messages forward to subscribed clients.
52 | 
53 |     Authorization credentials embedded in messages should be deleted from them
54 |     by server-side extensions to prevent these credentials being forwarded to
55 |     subscribed clients.
56 | 
57 |     h4. Transport layer security
58 | 
59 |     It should go without saying that if you want to keep the messages or any
60 |     aspect of the HTTP transport layer secret from potential eavesdroppers, you
61 |     should access the Faye server over a secure connection. You should use
62 |     Node's @https@ module to create a server, or run Thin in SSL mode, or you
63 |     can use an SSL terminator like STunnel in front of your Faye server.
64 | 
65 |     On the client side, you should make sure you use an @https:@ URL for the
66 |     client to connect to.
67 | 


--------------------------------------------------------------------------------
/site/src/pages/security/headers.haml:
--------------------------------------------------------------------------------
 1 | .content
 2 |   = partial 'security_navigation'
 3 | 
 4 |   :textile
 5 |     h4. Can I use cookies?
 6 | 
 7 |     As of version 1.0, Faye allows server-side extensions to access the request
 8 |     data for the current message. We have introduced this capability in order to
 9 |     support integration with various other HTTP-based authorization mechanisms,
10 |     but still recommend that this task is done within the messaging protocol
11 |     using signed or encrypted data.
12 |     
13 |     However some users will want to use HTTP-based methods, in particular the
14 |     @Cookie@ header that carries the user's session. There are several caveats
15 |     you must be aware of to use cookies safely.
16 | 
17 |     First, the browser will send cookies regardless of which site the request
18 |     came from, so to make sure you're only granting access to your own pages,
19 |     you must "implement CSRF protection":/security/csrf.html. If you don't do
20 |     this, any site the user has open will get privileged access to your Faye
21 |     server by impersonating the user.
22 | 
23 |     Second, some transports like WebSocket and EventSource use a very long-lived
24 |     request and only send one @Cookie@ header on first connection. This means
25 |     the information you're using to authorize messages may have been sent a long
26 |     time ago and may therefore be stale. If the session has changed or been
27 |     invalidated since the initial connection, relying on stale data can cause
28 |     security holes.
29 |     
30 |     Instead of cookies that contain the session data, we recommend keeping a
31 |     session ID in the cookies and storing the data on the server, either in
32 |     memory or on disk or in a database. This way, you will look up the session
33 |     afresh on every message instead of using stale data.
34 | 
35 |     h4. Can I use Origin, Referer, etc.?
36 | 
37 |     Although the @Origin@ header was introduced to combat CSRF, these headers
38 |     can be easily guessed and spoofed by server-side clients, browser
39 |     extensions and malicious JavaScript applications. They are not
40 |     cryptographically secure proof that you can trust where the request claims
41 |     to have come from.
42 | 
43 |     This still allows third parties to inject messages into your application,
44 |     and is especially bad if you have clients that receive JavaScript and
45 |     @eval()@ it.
46 | 
47 |     The @Origin@ header is also not sent by most browser transports that Faye
48 |     uses, so filtering based on it will actually block most legit traffic,
49 |     including the initial handshake request.
50 | 


--------------------------------------------------------------------------------
/site/src/pages/security/javascript.haml:
--------------------------------------------------------------------------------
 1 | .content
 2 |   = partial 'security_navigation'
 3 | 
 4 |   :textile
 5 |     h4. Publishing JavaScript
 6 | 
 7 |     Most realtime applications work by pushing data to the client for it to act
 8 |     on. Assuming the data can be trusted by the client, this is a good setup:
 9 |     the client's behaviour is somewhat constrained. It can only do what its code
10 |     allows it to do, with the caveat that some crafted inputs may lead to
11 |     unexpected behaviour.
12 | 
13 |     However some realtime applications directly script the client by pushing
14 |     JavaScript code that the client runs with @eval()@. This is extremely
15 |     dangerous unless you make sure that nobody but your own private servers can
16 |     publish to your Faye server. I recommend that realtime apps operate by
17 |     exchanging data, not sending code. If anyone but your own server-side
18 |     applications can push JavaScript unchecked, your site has a serious XSS
19 |     problem that can allow an attacker to easily steal the user's session and
20 |     other private data.
21 | 
22 |     To illustrate how easy this is, I have in the past hijacked the browsers of
23 |     all the attendees at a conference that were running a demo app hosted by the
24 |     speaker. The speaker put the app's publishing key on the screen and the
25 |     application ran any JavaScript pushed to it, so was trivial to exploit. Of
26 |     course, normally this key would have been kept private, but if you don't
27 |     have any such key your app automatically has an XSS hole.
28 | 
29 |     A JavaScript-pushing server can be made safe by ensuring your clients only
30 |     run code sent by your application, and this can be done by turning Faye into
31 |     a "push-only server":/security/push.html.
32 | 


--------------------------------------------------------------------------------
/site/src/pages/security/publication.haml:
--------------------------------------------------------------------------------
 1 | .content
 2 |   = partial 'security_navigation'
 3 | 
 4 |   :textile
 5 |     h4. Restricting publication access
 6 | 
 7 |     Applications typically only allow authenticated users to modify things: you
 8 |     must prove you 'own' a resource or that someone has given you permission
 9 |     before you go and change someone's database. In Faye, publishing is the
10 |     write operation: publishing a message to the server causes it to be sent to
11 |     all subscribed clients, which will act based on the data in the message. By
12 |     publishing a message, you are sending instructions to other clients, and the
13 |     clients must be able to trust that the data they receive is genuine.
14 | 
15 |     If you do not protect publication, your site probably has a "Cross-Site
16 |     Request Forgery":/security/csrf.html (CSRF) vulnerability, and possibly a
17 |     "Cross-Site Scripting":/security/javascript.html (XSS) one too.
18 | 
19 |     Protecting publication on the server side is simpler than protecting
20 |     subscription, because publication messages (those with channels other than
21 |     @/meta/*@) cannot be addressed to wildcards. So, to protect a channel's
22 |     publications, you _only_ need to check that literal channel name.
23 | 
24 |     The channel the message is being published to will be in the
25 |     @message.channel@ field. An important fact to remember here is that messages
26 |     are forwarded verbatim to other clients, so if they contain authentication
27 |     data you should delete this from the message during the @authorized()@
28 |     function so it is not leaked to third parties.
29 | 
30 |     <pre>var channel = '/foo/bar/qux';
31 | 
32 |     var authorized = function(message) {
33 |       // returns true or false
34 |     };
35 | 
36 |     server.addExtension({
37 |       incoming: function(message, callback) {
38 |         if (message.channel === channel) {
39 |           if (!authorized(message))
40 |       	    message.error = '403::Authentication required';
41 |         }
42 |         callback(message);
43 |       }
44 |     });</pre>
45 | 
46 |     See "Authentication":/security/authentication.html for a discussing of the
47 |     @authorized()@ function.
48 | 


--------------------------------------------------------------------------------
/site/src/pages/security/push.haml:
--------------------------------------------------------------------------------
 1 | .content
 2 |   = partial 'security_navigation'
 3 | 
 4 |   :textile
 5 |     h4. Push-only servers
 6 | 
 7 |     Sometimes you only want to use Faye to push events from your server-side
 8 |     application to your clients, and you don't want clients to be able to
 9 |     publish at all. This can easily be done by requiring a password for
10 |     publishing. On any non-@/meta/@ message, check for the password. If it's not
11 |     present, add an error to the message. Finally, delete the password from the
12 |     message to prevent leaking it to clients.
13 | 
14 |     <pre>var secret = 'some long and unguessable application-specific string';
15 | 
16 |     server.addExtension({
17 |       incoming: function(message, callback) {
18 |         if (!message.channel.match(/^\/meta\//)) {
19 |           var password = message.ext && message.ext.password;
20 |           if (password !== secret)
21 |             message.error = '403::Password required';
22 |         }
23 |         callback(message);
24 |       },
25 | 
26 |       outgoing: function(message, callback) {
27 |         if (message.ext) delete message.ext.password;
28 |         callback(message);
29 |       }
30 |     });</pre>
31 | 
32 |     Then you can add a client-side extension to your server-side client to add
33 |     the password:
34 | 
35 |     <pre>var secret = 'some long and unguessable application-specific string';
36 | 
37 |     client.addExtension({
38 |       outgoing: function(message, callback) {
39 |         message.ext = message.ext || {};
40 |         message.ext.password = secret;
41 |         callback(message);
42 |       }
43 |     });</pre>
44 | 
45 |     If you're using a plain HTTP client to publish messages, include the
46 |     password in the JSON body:
47 | 
48 |     <pre>$ curl -X POST www.example.com/faye \
49 |         -H 'Content-Type: application/json' \
50 |         -d '{"channel": "/foo", "data": "hi", "ext": {"password": "..."}}'</pre>
51 | 
52 |     Remember to keep the password secret, and do not let it leak out of your
53 |     servers into the outside world.
54 | 


--------------------------------------------------------------------------------
/site/src/pages/security/subscription.haml:
--------------------------------------------------------------------------------
 1 | .content
 2 |   = partial 'security_navigation'
 3 | 
 4 |   :textile
 5 |     h4. Restricting subscription access
 6 | 
 7 |     Most web applications have a concept of access control: some content is only
 8 |     accessible to certain people, and you must be logged in to prove your
 9 |     identity. In Faye, you might want subscriptions to certain channels to
10 |     require authentication, if you are publishing private data on such channels.
11 |     This is easily done with a server-side extension that filters incoming
12 |     @/meta/subscribe@ messages.
13 | 
14 |     An important point here is that subscriptions can contain wildcards, and you
15 |     must protect these. A message published to @/foo/bar/qux@ will be routed to
16 |     any client subscribed to @/foo/bar/qux@, @/foo/bar/*@, @/foo/bar/**@,
17 |     @/foo/**@ or @/**@.
18 | 
19 |     Adding an @error@ field to any incoming message will stop the server from
20 |     processing it. The channel the client is attempting to subscribe to will be
21 |     in the @message.subscription@ field.
22 | 
23 |     <pre>var subscriptions = [
24 |       '/foo/bar/qux',
25 |       '/foo/bar/*',
26 |       '/foo/bar/**',
27 |       '/foo/**',
28 |       '/**'
29 |     ];
30 | 
31 |     var authorized = function(message) {
32 |       // returns true or false
33 |     };
34 | 
35 |     server.addExtension({
36 |       incoming: function(message, callback) {
37 |         if (message.channel === '/meta/subscribe') {
38 |           if (subscriptions.indexOf(message.subscription) >= 0) {
39 |             if (!authorized(message))
40 |               message.error = '403::Authentication required';
41 |           }
42 |         }
43 |         callback(message);
44 |       }
45 |     });</pre>
46 | 
47 |     How the @authorized()@ function is implemented depends on your clients'
48 |     capabilities and is covered in more detail under
49 |     "Authentication":/security/authentication.html. Note that because extensions
50 |     are asynchronous (you hand the message back to the server using a callback),
51 |     your authentication logic can contain async operations, which is useful if
52 |     you need to do some I/O.
53 | 
54 |     A simple extension like this, with properly implemented authentication, will
55 |     prevent unauthorized access to published data on the selected channel.
56 | 


--------------------------------------------------------------------------------
/site/src/pages/security/summary.haml:
--------------------------------------------------------------------------------
 1 | .content
 2 |   = partial 'security_navigation'
 3 | 
 4 |   :textile
 5 |     h4. Other techniques
 6 | 
 7 |     The above is a fairly comprehensive picture of restricting access to your
 8 |     Faye server. The important thing to remember is that when exchanging
 9 |     messages, you just need a way to prove that the data is genuine. This relies
10 |     heavily on cryptograhpic techniques and you should always use standard
11 |     functions for this rather than inventing your own.
12 | 
13 |     However, sometimes, it's just a case of using data that is very hard to
14 |     guess. For example, say you want to send messages to one particular user and
15 |     nobody else. Instead of naming a channel after a username and requiring an
16 |     access token to subcribe to it, you could just make the channel name
17 |     _contain_ the access token. For example, the client could call an endpoint
18 |     on your server to get a channel name for the logged-in user, then subscribe
19 |     to that channel in Faye. When publishing, you would just regenerate the
20 |     channel name from the username you want to publish to.
21 | 
22 |     These channel names may be a cryptograhpically signed copy of the user's
23 |     name or ID, or they could simply be very large random numbers (larger than
24 |     160 bits is advisable) that you store in a database next to each user ID. As
25 |     long as they cannot be guessed by a third party, you're alright. Just
26 |     remember that 'cannot be guessed' is surprisingly hard to implement
27 |     correctly, and you should consult someone with a grounding in crypto if
28 |     you're not sure what you're doing is safe.
29 | 
30 |     h4. Summary
31 | 
32 |     This guide, while not exhaustive should give you enough grounding on the
33 |     topic to safely implement a real-time application using Faye.  If you have
34 |     further questions you should "ask on the mailing
35 |     list":http://groups.google.com/group/faye-users - many people there have run
36 |     into the same problems as you and will likely have already thought of a
37 |     solution.  If you have a genuinely unusual case then you will most likely
38 |     benefit from their sage advice.
39 | 
40 |     Thank you for taking the time to familiarise yourself with this advice and
41 |     for using Faye.  Your feedback on this document is eagerly solicited; issues
42 |     and pull requests can be submitted on the "Faye
43 |     project":https://github.com/faye/faye on GitHub.
44 | 


--------------------------------------------------------------------------------
/site/src/partials/browser_navigation.haml:
--------------------------------------------------------------------------------
 1 | :textile
 2 |   h3. Browser client
 3 | 
 4 |   <div class="sections">
 5 | 
 6 |   * "Setting up":/browser.html
 7 |   * "Subscribing to channels":/browser/subscribing.html
 8 |   * "Sending messages":/browser/publishing.html
 9 |   * "Controlling dispatch":/browser/dispatch.html
10 |   * "Network errors":/browser/transport.html
11 |   * "Extensions":/browser/extensions.html
12 | 
13 |   </div>
14 | 


--------------------------------------------------------------------------------
/site/src/partials/node_navigation.haml:
--------------------------------------------------------------------------------
 1 | :textile
 2 |   h3. Node.js server
 3 | 
 4 |   <div class="sections">
 5 | 
 6 |   * "Setting up":/node.html
 7 |   * "Extensions":/node/extensions.html
 8 |   * "Monitoring":/node/monitoring.html
 9 |   * "Server-side clients":/node/clients.html
10 |   * "Engines":/node/engines.html
11 |   * "WebSockets":/node/websockets.html
12 | 
13 |   </div>
14 | 


--------------------------------------------------------------------------------
/site/src/partials/ruby_navigation.haml:
--------------------------------------------------------------------------------
 1 | :textile
 2 |   h3. Ruby server
 3 | 
 4 |   <div class="sections">
 5 | 
 6 |   * "Setting up":/ruby.html
 7 |   * "Extensions":/ruby/extensions.html
 8 |   * "Monitoring":/ruby/monitoring.html
 9 |   * "Server-side clients":/ruby/clients.html
10 |   * "Engines":/ruby/engines.html
11 |   * "WebSockets":/ruby/websockets.html
12 | 
13 |   </div>
14 | 


--------------------------------------------------------------------------------
/site/src/partials/security_navigation.haml:
--------------------------------------------------------------------------------
 1 | :textile
 2 |   h3. Security advice
 3 | 
 4 |   <div class="sections">
 5 | 
 6 |   * "Overview":/security.html
 7 |   * "Restricting subscriptions":/security/subscription.html
 8 |   * "Restricting publication":/security/publication.html
 9 |   * "Publishing JavaScript":/security/javascript.html
10 |   * "Push-only servers":/security/push.html
11 |   * "Authentication":/security/authentication.html
12 |   * "Cookies, Origin, and Referer":/security/headers.html
13 |   * "CSRF protection":/security/csrf.html
14 |   * "Other techniques":/security/summary.html
15 | 
16 |   </div>
17 | 


--------------------------------------------------------------------------------
/site/src/stylesheets/screen.sass:
--------------------------------------------------------------------------------
  1 | body
  2 |   background:         #3f3f3f
  3 |   color:              #fff
  4 |   font:               14px/1.5 Open Sans, FreeSans, Helvetica, Arial, sans-serif
  5 |   text-align:         center
  6 |   margin:             0 0 0 0
  7 |   padding:            0 0 0 0
  8 | 
  9 | .container
 10 |   width:              960px
 11 |   margin:             0 auto
 12 |   text-align:         left
 13 |   position:           relative
 14 | 
 15 |   a
 16 |     color:            #6fbc62
 17 |     font-weight:      bold
 18 |     text-decoration:  none
 19 | 
 20 |   a:hover
 21 |     text-decoration:  underline
 22 | 
 23 |   .clear
 24 |     clear:            both
 25 |     display:          block
 26 |     height:           0
 27 |     overflow:         hidden
 28 | 
 29 | .header
 30 |   border-bottom:      4px solid #ccc
 31 | 
 32 |   h1
 33 |     margin:           0 0 0 0
 34 |     padding:          26px 0 0 0
 35 |     a
 36 |       display:        block
 37 |       width:          286px
 38 |       height:         0
 39 |       overflow:       hidden
 40 |       padding:        84px 0 0 0
 41 |       background:     #3f3f3f url(/images/faye-logo.gif) 0 0 no-repeat
 42 |       border-bottom:  8px solid #3f3f3f
 43 | 
 44 |   h2
 45 |     font-size:        16px
 46 |     font-weight:      normal
 47 |     margin:           0 0 0 0
 48 |     padding:          0 8px 40px
 49 | 
 50 |   .docs, .download, .community
 51 |     position:         absolute
 52 |     top:              0
 53 |     border-left:      1px solid #666
 54 |     padding:          40px 0 0 20px
 55 |     height:           112px
 56 | 
 57 |     a
 58 |       color:          #999
 59 |       font-weight:    normal
 60 |     a:hover
 61 |       color:          #fff
 62 | 
 63 |     h3
 64 |       margin:         0 0 0 0
 65 |       padding:        0 0 0 0
 66 |       font-weight:    bold
 67 |       font-size:      100%
 68 | 
 69 |     ul, li
 70 |       list-style:     none
 71 |       margin:         0 0 0 0
 72 |       padding:        0 0 0 0
 73 | 
 74 |   .docs
 75 |     left:             480px
 76 |   .community
 77 |     left:             640px
 78 |   .download
 79 |     left:             800px
 80 | 
 81 | .footer
 82 |   border-top:         4px solid #ccc
 83 |   padding-bottom:     2em
 84 |   p
 85 |     font-size:        80%
 86 |     margin:           1em 0 1em 320px
 87 | 
 88 | .main
 89 |   background:         #fff
 90 |   color:              #3f3f3f
 91 |   padding:            2em 0 4em
 92 | 
 93 | .front-matter
 94 |   .intro
 95 |     font-size:        140%
 96 |     h3
 97 |       margin-top:     0
 98 |     p
 99 |       border-top:     none
100 |       margin-top:     0.5em
101 |       padding-top:    0
102 | 
103 |     a img
104 |       margin-bottom:  18px
105 |       margin-right:   32px
106 |   h3
107 |     font-weight:      normal
108 |     font-style:       italic
109 |     font-size:        150%
110 |     float:            left
111 |     margin:           1em 0 0 80px
112 |     position:         relative
113 | 
114 |     .number
115 |       color:          #6fbc62
116 |       font-weight:    bold
117 | 
118 |   p, pre
119 |     border-top:       1px solid #eee
120 |     margin:           1.5em 0
121 |     padding:          1.5em 0 0 320px
122 | 
123 |   pre
124 |     font-family:      Inconsolata, Monaco, Lucida Console, Courier New, monospace
125 | 
126 | .content
127 |   h3
128 |     float:            left
129 |     margin:           -0.5em 0 0 0
130 |     font-size:        200%
131 |     font-weight:      bold
132 | 
133 |   .sections
134 |     clear:            left
135 |     float:            left
136 |     margin:           32px 0
137 |     padding:          0 0 0 2em
138 | 
139 |     ul, li
140 |       margin:         0
141 |       padding:        0
142 | 
143 |   h4
144 |     font-size:        120%
145 |     font-weight:      bold
146 | 
147 |   h4, p, pre, ul, .image
148 |     margin:           1.5em 0 1.5em 320px
149 | 
150 |   .image img
151 |     display:          block
152 |     margin:           1em auto
153 | 
154 |   pre
155 |     border-left:      1em solid #f0f0f0
156 |     font-family:      Inconsolata, Monaco, Lucida Console, Courier New, monospace
157 |     padding-left:     2em
158 | 
159 |   code
160 |     background:       #eee
161 | 


--------------------------------------------------------------------------------
/spec/browser.js:
--------------------------------------------------------------------------------
 1 | require("./javascript/util/copy_object_spec")
 2 | require("./javascript/channel_spec")
 3 | require("./javascript/client_spec")
 4 | require("./javascript/dispatcher_spec")
 5 | require("./javascript/grammar_spec")
 6 | require("./javascript/publisher_spec")
 7 | require("./javascript/transport_spec")
 8 | require("./javascript/uri_spec")
 9 | 
10 | require("jstest").Test.autorun()
11 | 


--------------------------------------------------------------------------------
/spec/index.html:
--------------------------------------------------------------------------------
 1 | <!doctype html>
 2 | <html>
 3 |   <head>
 4 |     <meta charset="utf-8">
 5 |     <title>Faye test suite</title>
 6 |   </head>
 7 |   <body>
 8 |     <script src="./browser_bundle.js"></script>
 9 |   </body>
10 | </html>
11 | 


--------------------------------------------------------------------------------
/spec/javascript/channel_spec.js:
--------------------------------------------------------------------------------
 1 | var jstest = require("jstest").Test
 2 | 
 3 | var Channel = require("../../src/protocol/channel")
 4 | 
 5 | jstest.describe("Channel", function() { with(this) {
 6 |   describe("expand", function() { with(this) {
 7 |     it("returns all patterns that match a channel", function() { with(this) {
 8 | 
 9 |       assertEqual( ["/**", "/foo", "/*"],
10 |                    Channel.expand("/foo") )
11 | 
12 |       assertEqual( ["/**", "/foo/bar", "/foo/*", "/foo/**"],
13 |                    Channel.expand("/foo/bar") )
14 | 
15 |       assertEqual( ["/**", "/foo/bar/qux", "/foo/bar/*", "/foo/**", "/foo/bar/**"],
16 |                    Channel.expand("/foo/bar/qux") )
17 |     }})
18 |   }})
19 | 
20 |   describe("Set", function() { with(this) {
21 |     describe("subscribe", function() { with(this) {
22 |       it("subscribes and unsubscribes without callback", function() { with(this) {
23 |         var channels = new Channel.Set()
24 |         channels.subscribe(["/foo/**"], null)
25 |         assertEqual( ["/foo/**"], channels.getKeys() )
26 |         assert( channels.unsubscribe("/foo/**", null) )
27 |       }})
28 |     }})
29 |   }})
30 | }})
31 | 


--------------------------------------------------------------------------------
/spec/javascript/engine/memory_spec.js:
--------------------------------------------------------------------------------
 1 | var jstest = require("jstest").Test
 2 | 
 3 | var Memory = require("../../../src/engines/memory")
 4 | 
 5 | require("../engine_spec")
 6 | 
 7 | jstest.describe("Memory engine", function() { with(this) {
 8 |   before(function() {
 9 |     this.engineOpts = { type: Memory }
10 |   })
11 | 
12 |   itShouldBehaveLike("faye engine")
13 | }})
14 | 


--------------------------------------------------------------------------------
/spec/javascript/grammar_spec.js:
--------------------------------------------------------------------------------
 1 | var jstest = require("jstest").Test
 2 | 
 3 | var Grammar = require("../../src/protocol/grammar")
 4 | 
 5 | jstest.describe("Grammar", function() { with(this) {
 6 |   describe("CHANNEL_NAME", function() { with(this) {
 7 |     it("matches valid channel names", function() { with(this) {
 8 |       assertMatch( Grammar.CHANNEL_NAME, "/fo_o/$@()bar" )
 9 |     }})
10 | 
11 |     it("does not match channel patterns", function() { with(this) {
12 |       assertNoMatch( Grammar.CHANNEL_NAME, "/foo/**" )
13 |     }})
14 | 
15 |     it("does not match invalid channel names", function() { with(this) {
16 |       assertNoMatch( Grammar.CHANNEL_NAME, "foo/$@()bar" )
17 |       assertNoMatch( Grammar.CHANNEL_NAME, "/foo/$@()bar/" )
18 |       assertNoMatch( Grammar.CHANNEL_NAME, "/fo o/$@()bar" )
19 |     }})
20 |   }})
21 | 
22 |   describe("CHANNEL_PATTERN", function() { with(this) {
23 |     it("does not match channel names", function() { with(this) {
24 |       assertNoMatch( Grammar.CHANNEL_PATTERN, "/fo_o/$@()bar" )
25 |     }})
26 | 
27 |     it("matches valid channel patterns", function() { with(this) {
28 |       assertMatch( Grammar.CHANNEL_PATTERN, "/foo/**" )
29 |       assertMatch( Grammar.CHANNEL_PATTERN, "/foo/*" )
30 |     }})
31 | 
32 |     it("does not match invalid channel patterns", function() { with(this) {
33 |       assertNoMatch( Grammar.CHANNEL_PATTERN, "/foo/**/*" )
34 |     }})
35 |   }})
36 | 
37 |   describe("ERROR", function() { with(this) {
38 |     it("matches an error with an argument", function() { with(this) {
39 |       assertMatch( Grammar.ERROR, "402:xj3sjdsjdsjad:Unknown Client ID" )
40 |     }})
41 | 
42 |     it("matches an error with many arguments", function() { with(this) {
43 |       assertMatch( Grammar.ERROR, "403:xj3sjdsjdsjad,/foo/bar:Subscription denied" )
44 |     }})
45 | 
46 |     it("matches an error with no arguments", function() { with(this) {
47 |       assertMatch( Grammar.ERROR, "402::Unknown Client ID" )
48 |     }})
49 | 
50 |     it("does not match an error with no code", function() { with(this) {
51 |       assertNoMatch( Grammar.ERROR, ":xj3sjdsjdsjad:Unknown Client ID" )
52 |     }})
53 | 
54 |     it("does not match an error with an invalid code", function() { with(this) {
55 |       assertNoMatch( Grammar.ERROR, "40:xj3sjdsjdsjad:Unknown Client ID" )
56 |     }})
57 |   }})
58 | 
59 |   describe("VERSION", function() { with(this) {
60 |     it("matches a version number", function() { with(this) {
61 |       assertMatch( Grammar.VERSION, "9" )
62 |       assertMatch( Grammar.VERSION, "9.0.a-delta1" )
63 |     }})
64 | 
65 |     it("does not match invalid version numbers", function() { with(this) {
66 |       assertNoMatch( Grammar.VERSION, "9.0.a-delta1." )
67 |       assertNoMatch( Grammar.VERSION, "" )
68 |     }})
69 |   }})
70 | }})
71 | 


--------------------------------------------------------------------------------
/spec/javascript/publisher_spec.js:
--------------------------------------------------------------------------------
 1 | var jstest = require("jstest").Test
 2 | 
 3 | var Publisher = require("../../src/mixins/publisher"),
 4 |     assign    = require("../../src/util/assign")
 5 | 
 6 | jstest.describe("Publisher", function() { with(this) {
 7 |   before(function() { with(this) {
 8 |     this.publisher = assign({}, Publisher)
 9 |   }})
10 | 
11 |   describe("with subscribers that remove themselves", function() { with(this) {
12 |     before(function() { with(this) {
13 |       this.calledA = false
14 |       this.calledB = false
15 | 
16 |       this.handler = function() {
17 |         calledA = true
18 |         publisher.unbind("event", handler)
19 |       }
20 | 
21 |       publisher.bind("event", handler)
22 |       publisher.bind("event", function() { calledB = true })
23 |     }})
24 | 
25 |     it("successfully calls all the callbacks", function() { with(this) {
26 |       publisher.trigger("event")
27 |       assert( calledA )
28 |       assert( calledB )
29 |     }})
30 |   }})
31 | }})
32 | 


--------------------------------------------------------------------------------
/spec/javascript/server/extensions_spec.js:
--------------------------------------------------------------------------------
  1 | var jstest = require("jstest").Test
  2 | 
  3 | var Engine = require("../../../src/engines/proxy"),
  4 |     Server = require("../../../src/protocol/server")
  5 | 
  6 | jstest.describe("Server extensions", function() { with(this) {
  7 |     before(function() { with(this) {
  8 |     this.engine = {}
  9 |     stub(Engine, "get").returns(engine)
 10 |     this.server = new Server()
 11 |   }})
 12 | 
 13 |   describe("with an incoming extension installed", function() { with(this) {
 14 |     before(function() { with(this) {
 15 |       var extension = {
 16 |         incoming: function(message, callback) {
 17 |           message.ext = { auth: "password" }
 18 |           callback(message)
 19 |         }
 20 |       }
 21 |       server.addExtension(extension)
 22 |       this.message = { channel: "/foo", data: "hello" }
 23 |     }})
 24 | 
 25 |     it("passes incoming messages through the extension", function() { with(this) {
 26 |       expect(engine, "publish").given({ channel: "/foo", data: "hello", ext: { auth: "password" }})
 27 |       server.process(message, false, function() {})
 28 |     }})
 29 | 
 30 |     it("does not pass outgoing messages through the extension", function() { with(this) {
 31 |       stub(server, "handshake").yields([message])
 32 |       stub(engine, "publish")
 33 |       var response = null
 34 |       server.process({ channel: "/meta/handshake" }, false, function(r) { response = r })
 35 |       assertEqual( [{ channel: "/foo", data: "hello" }], response )
 36 |     }})
 37 |   }})
 38 | 
 39 |   describe("with subscription auth installed", function() { with(this) {
 40 |     before(function() { with(this) {
 41 |       var extension = {
 42 |         incoming: function(message, callback) {
 43 |           if (message.channel === "/meta/subscribe" && !message.auth) {
 44 |             message.error = "Invalid auth"
 45 |           }
 46 |           callback(message)
 47 |         }
 48 |       }
 49 |       server.addExtension(extension)
 50 |     }})
 51 | 
 52 |     it("does not subscribe using the intended channel", function() { with(this) {
 53 |       var message = {
 54 |         channel: "/meta/subscribe",
 55 |         clientId: "fakeclientid",
 56 |         subscription: "/foo"
 57 |       }
 58 |       stub(engine, "clientExists").yields([true])
 59 |       expect(engine, "subscribe").exactly(0)
 60 |       server.process(message, false, function() {})
 61 |     }})
 62 | 
 63 |     it("does not subscribe using an extended channel", function() { with(this) {
 64 |       var message = {
 65 |         channel: "/meta/subscribe/x",
 66 |         clientId: "fakeclientid",
 67 |         subscription: "/foo"
 68 |       }
 69 |       stub(engine, "clientExists").yields([true])
 70 |       expect(engine, "subscribe").exactly(0)
 71 |       server.process(message, false, function() {})
 72 |     }})
 73 |   }})
 74 | 
 75 |   describe("with an outgoing extension installed", function() { with(this) {
 76 |     before(function() { with(this) {
 77 |       var extension = {
 78 |         outgoing: function(message, callback) {
 79 |           message.ext = { auth: "password" }
 80 |           callback(message)
 81 |         }
 82 |       }
 83 |       server.addExtension(extension)
 84 |       this.message = { channel: "/foo", data: "hello" }
 85 |     }})
 86 | 
 87 |     it("does not pass incoming messages through the extension", function() { with(this) {
 88 |       expect(engine, "publish").given({ channel: "/foo", data: "hello" })
 89 |       server.process(message, false, function() {})
 90 |     }})
 91 | 
 92 |     it("passes outgoing messages through the extension", function() { with(this) {
 93 |       stub(server, "handshake").yields([message])
 94 |       stub(engine, "publish")
 95 |       var response = null
 96 |       server.process({ channel: "/meta/handshake" }, false, function(r) { response = r })
 97 |       assertEqual( [{ channel: "/foo", data: "hello", ext: { auth: "password" }}], response )
 98 |     }})
 99 |   }})
100 | }})
101 | 


--------------------------------------------------------------------------------
/spec/javascript/server/publish_spec.js:
--------------------------------------------------------------------------------
  1 | var jstest = require("jstest").Test
  2 | 
  3 | var Engine = require("../../../src/engines/proxy"),
  4 |     Server = require("../../../src/protocol/server")
  5 | 
  6 | jstest.describe("Server publish", function() { with(this) {
  7 |   before(function() { with(this) {
  8 |     this.engine = {}
  9 |     stub(Engine, "get").returns(engine)
 10 |     this.server = new Server()
 11 | 
 12 |     this.message = { channel: "/some/channel", data: "publish" }
 13 |   }})
 14 | 
 15 |   describe("publishing a message", function() { with(this) {
 16 |     it("tells the engine to publish the message", function() { with(this) {
 17 |       expect(engine, "publish").given(message)
 18 |       server.process(message, false, function() {})
 19 |     }})
 20 | 
 21 |     it("returns a successful response", function() { with(this) {
 22 |       stub(engine, "publish")
 23 |       server.process(message, false, function(response) {
 24 |         assertEqual([
 25 |           { channel:    "/some/channel",
 26 |             successful: true
 27 |           }
 28 |         ], response)
 29 |       })
 30 |     }})
 31 | 
 32 |     describe("with an invalid channel", function() { with(this) {
 33 |       before(function() { with(this) {
 34 |         message.channel = "channel"
 35 |       }})
 36 | 
 37 |       it("does not tell the engine to publish the message", function() { with(this) {
 38 |         expect(engine, "publish").exactly(0)
 39 |         server.process(message, false, function() {})
 40 |       }})
 41 | 
 42 |       it("returns an unsuccessful response", function() { with(this) {
 43 |         stub(engine, "publish")
 44 |         server.process(message, false, function(response) {
 45 |           assertEqual([
 46 |             { channel:    "channel",
 47 |               successful: false,
 48 |               error:      "405:channel:Invalid channel"
 49 |             }
 50 |           ], response)
 51 |         })
 52 |       }})
 53 |     }})
 54 | 
 55 |     describe("with no data", function() { with(this) {
 56 |       before(function() { with(this) {
 57 |         delete message.data
 58 |       }})
 59 | 
 60 |       it("does not tell the engine to publish the message", function() { with(this) {
 61 |         expect(engine, "publish").exactly(0)
 62 |         server.process(message, false, function() {})
 63 |       }})
 64 | 
 65 |       it("returns an unsuccessful response", function() { with(this) {
 66 |         stub(engine, "publish")
 67 |         server.process(message, false, function(response) {
 68 |           assertEqual([
 69 |             { channel:    "/some/channel",
 70 |               successful: false,
 71 |               error:      "402:data:Missing required parameter"
 72 |             }
 73 |           ], response)
 74 |         })
 75 |       }})
 76 |     }})
 77 | 
 78 |     describe("with an error", function() { with(this) {
 79 |       before(function() { with(this) {
 80 |         message.error = "invalid"
 81 |       }})
 82 | 
 83 |       it("does not tell the engine to publish the message", function() { with(this) {
 84 |         expect(engine, "publish").exactly(0)
 85 |         server.process(message, false, function() {})
 86 |       }})
 87 | 
 88 |       it("returns an unsuccessful response", function() { with(this) {
 89 |         stub(engine, "publish")
 90 |         server.process(message, false, function(response) {
 91 |           assertEqual([
 92 |             { channel:    "/some/channel",
 93 |               successful: false,
 94 |               error:      "invalid"
 95 |             }
 96 |           ], response)
 97 |         })
 98 |       }})
 99 |     }})
100 | 
101 |     describe("to an invalid channel", function() { with(this) {
102 |       before(function() { with(this) {
103 |         message.channel = "/invalid/*"
104 |       }})
105 | 
106 |       it("does not tell the engine to publish the message", function() { with(this) {
107 |         expect(engine, "publish").exactly(0)
108 |         server.process(message, false, function() {})
109 |       }})
110 |     }})
111 |   }})
112 | }})
113 | 


--------------------------------------------------------------------------------
/spec/javascript/uri_spec.js:
--------------------------------------------------------------------------------
 1 | var jstest = require("jstest").Test
 2 | 
 3 | var URI = require("../../src/util/uri")
 4 | 
 5 | jstest.describe("URI", function() { with(this) {
 6 |   describe("parse", function() { with(this) {
 7 |     it("parses all the bits of a URI", function() { with(this) {
 8 |       assertEqual( {
 9 |           href:     "http://example.com:80/foo.html?foo=bar&hello=%2Fworld#cloud",
10 |           protocol: "http:",
11 |           host:     "example.com:80",
12 |           hostname: "example.com",
13 |           port:     "80",
14 |           path:     "/foo.html?foo=bar&hello=%2Fworld",
15 |           pathname: "/foo.html",
16 |           search:   "?foo=bar&hello=%2Fworld",
17 |           query:    { foo: "bar", hello: "/world" },
18 |           hash:     "#cloud"
19 |         }, URI.parse("http://example.com:80/foo.html?foo=bar&hello=%2Fworld#cloud") )
20 |     }})
21 | 
22 |     it("parses a URI with no hash", function() { with(this) {
23 |       assertEqual( {
24 |           href:     "http://example.com:80/foo.html?foo=bar&hello=%2Fworld",
25 |           protocol: "http:",
26 |           host:     "example.com:80",
27 |           hostname: "example.com",
28 |           port:     "80",
29 |           path:     "/foo.html?foo=bar&hello=%2Fworld",
30 |           pathname: "/foo.html",
31 |           search:   "?foo=bar&hello=%2Fworld",
32 |           query:    { foo: "bar", hello: "/world" },
33 |           hash:     ""
34 |         }, URI.parse("http://example.com:80/foo.html?foo=bar&hello=%2Fworld") )
35 |     }})
36 | 
37 |     it("parses a URI with no query", function() { with(this) {
38 |       assertEqual( {
39 |           href:     "http://example.com:80/foo.html#cloud",
40 |           protocol: "http:",
41 |           host:     "example.com:80",
42 |           hostname: "example.com",
43 |           port:     "80",
44 |           path:     "/foo.html",
45 |           pathname: "/foo.html",
46 |           search:   "",
47 |           query:    {},
48 |           hash:     "#cloud"
49 |         }, URI.parse("http://example.com:80/foo.html#cloud") )
50 |     }})
51 | 
52 |     it("parses a URI with an encoded path", function() { with(this) {
53 |       assertEqual( {
54 |           href:     "http://example.com:80/fo%20o.html?foo=bar&hello=%2Fworld#cloud",
55 |           protocol: "http:",
56 |           host:     "example.com:80",
57 |           hostname: "example.com",
58 |           port:     "80",
59 |           path:     "/fo%20o.html?foo=bar&hello=%2Fworld",
60 |           pathname: "/fo%20o.html",
61 |           search:   "?foo=bar&hello=%2Fworld",
62 |           query:    { foo: "bar", hello: "/world" },
63 |           hash:     "#cloud"
64 |         }, URI.parse("http://example.com:80/fo%20o.html?foo=bar&hello=%2Fworld#cloud") )
65 |     }})
66 | 
67 |     it("parses a URI with no path", function() { with(this) {
68 |       assertEqual( {
69 |           href:     "http://example.com:80/?foo=bar&hello=%2Fworld#cloud",
70 |           protocol: "http:",
71 |           host:     "example.com:80",
72 |           hostname: "example.com",
73 |           port:     "80",
74 |           path:     "/?foo=bar&hello=%2Fworld",
75 |           pathname: "/",
76 |           search:   "?foo=bar&hello=%2Fworld",
77 |           query:    { foo: "bar", hello: "/world" },
78 |           hash:     "#cloud"
79 |         }, URI.parse("http://example.com:80?foo=bar&hello=%2Fworld#cloud") )
80 |     }})
81 | 
82 |     it("parses a URI with no port", function() { with(this) {
83 |       assertEqual( {
84 |           href:     "http://example.com/foo.html?foo=bar&hello=%2Fworld#cloud",
85 |           protocol: "http:",
86 |           host:     "example.com",
87 |           hostname: "example.com",
88 |           port:     "",
89 |           path:     "/foo.html?foo=bar&hello=%2Fworld",
90 |           pathname: "/foo.html",
91 |           search:   "?foo=bar&hello=%2Fworld",
92 |           query:    { foo: "bar", hello: "/world" },
93 |           hash:     "#cloud"
94 |         }, URI.parse("http://example.com/foo.html?foo=bar&hello=%2Fworld#cloud") )
95 |     }})
96 |   }})
97 | }})
98 | 


--------------------------------------------------------------------------------
/spec/javascript/util/copy_object_spec.js:
--------------------------------------------------------------------------------
 1 | var jstest = require("jstest").Test
 2 | 
 3 | var copyObject = require("../../../src/util/copy_object")
 4 | 
 5 | jstest.describe("copyObject", function() { with(this) {
 6 |   before(function() { with(this) {
 7 |     this.object = { foo: "bar", qux: 42, hey: null, obj: { bar: 67 }}
 8 |   }})
 9 | 
10 |   it("returns an equal object", function() { with(this) {
11 |     assertEqual( { foo: "bar", qux: 42, hey: null, obj: { bar: 67 }},
12 |                  copyObject(object) )
13 |   }})
14 | 
15 |   it("does not return the same object", function() { with(this) {
16 |     assertNotSame( object, copyObject(object) )
17 |   }})
18 | 
19 |   it("performs a deep clone", function() { with(this) {
20 |     assertNotSame( object.obj, copyObject(object).obj )
21 |   }})
22 | }})
23 | 


--------------------------------------------------------------------------------
/spec/javascript/util/random_spec.js:
--------------------------------------------------------------------------------
 1 | var jstest = require("jstest").Test,
 2 |     Range  = require("jstest").Range
 3 | 
 4 | var random = require("../../../src/util/random")
 5 | 
 6 | jstest.describe("random", function() { with(this) {
 7 |   if (typeof document !== "undefined") return
 8 | 
 9 |   it("returns a 160-bit random number in base 36", function() { with(this) {
10 |     assertMatch( /^[a-z0-9]+$/, random() )
11 |   }})
12 | 
13 |   it("always produces the same length of string", function() { with(this) {
14 |     var ids = new Range(1,100).map(function() { return random().length })
15 |     var expected = new Range(1,100).map(function() { return 31 })
16 |     assertEqual( expected, ids )
17 |   }})
18 | }})
19 | 


--------------------------------------------------------------------------------
/spec/phantom.js:
--------------------------------------------------------------------------------
1 | phantom.injectJs('node_modules/jstest/jstest.js')
2 | 
3 | var options  = { format: 'dot' },
4 |     reporter = new JS.Test.Reporters.Headless(options)
5 | 
6 | reporter.open('spec/index.html')
7 | 


--------------------------------------------------------------------------------
/spec/ruby/channel_spec.rb:
--------------------------------------------------------------------------------
 1 | require "spec_helper"
 2 | 
 3 | describe Faye::Channel do
 4 |   describe :expand do
 5 |     it "returns all patterns that match a channel" do
 6 |       Faye::Channel.expand("/foo").should == [
 7 |                            "/**", "/foo", "/*"]
 8 | 
 9 |       Faye::Channel.expand("/foo/bar").should == [
10 |                            "/**", "/foo/bar", "/foo/*", "/foo/**"]
11 | 
12 |       Faye::Channel.expand("/foo/bar/qux").should == [
13 |                            "/**", "/foo/bar/qux", "/foo/bar/*", "/foo/**", "/foo/bar/**"]
14 |     end
15 |   end
16 | 
17 |   describe Faye::Channel::Set do
18 |     describe :subscribe do
19 |       it "subscribes and unsubscribes without callback" do
20 |         channels = Faye::Channel::Set.new
21 |         channels.subscribe(["/foo/**"], nil)
22 |         channels.keys.should == ["/foo/**"]
23 |         channels.unsubscribe("/foo/**", nil).should == true
24 |       end
25 |     end
26 |   end
27 | end
28 | 


--------------------------------------------------------------------------------
/spec/ruby/encoding_helper.rb:
--------------------------------------------------------------------------------
1 | module EncodingHelper
2 |   def encode(string)
3 |     return string unless string.respond_to?(:force_encoding)
4 |     string.force_encoding("UTF-8")
5 |   end
6 | end
7 | 


--------------------------------------------------------------------------------
/spec/ruby/engine/memory_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 | 
3 | describe Faye::Engine::Memory do
4 |   let(:engine_opts)  { { :type => Faye::Engine::Memory } }
5 |   it_should_behave_like "faye engine"
6 | end
7 | 


--------------------------------------------------------------------------------
/spec/ruby/faye_spec.rb:
--------------------------------------------------------------------------------
 1 | require "spec_helper"
 2 | 
 3 | describe Faye do
 4 |   describe :random do
 5 |     it "returns a 160-bit random number in base 36" do
 6 |       Faye.random.should =~ /^[a-z0-9]+$/
 7 |     end
 8 | 
 9 |     it "always produces the same length of string" do
10 |       ids = (1..100).map { Faye.random }
11 |       ids.should be_all { |id| id.size == 31 }
12 |     end
13 |   end
14 | 
15 |   describe :copy_obect do
16 |     let(:object) { { "foo" => "bar", "qux" => 42, "hey" => nil, "obj" => { "bar" => 67 } } }
17 | 
18 |     it "returns an equal object" do
19 |       Faye.copy_object(object).should == { "foo" => "bar", "qux" => 42, "hey" => nil, "obj" => { "bar" => 67 }}
20 |     end
21 | 
22 |     it "does not return the same object" do
23 |       Faye.copy_object(object).should_not be_equal(object)
24 |     end
25 | 
26 |     it "performs a deep clone" do
27 |       Faye.copy_object(object)["obj"].should_not be_equal(object["obj"])
28 |     end
29 |   end
30 | end
31 | 


--------------------------------------------------------------------------------
/spec/ruby/grammar_spec.rb:
--------------------------------------------------------------------------------
 1 | require "spec_helper"
 2 | 
 3 | describe Faye::Grammar do
 4 |   describe :CHANNEL_NAME do
 5 |     it "matches valid channel names" do
 6 |       Faye::Grammar::CHANNEL_NAME.should =~ "/fo_o/$@()bar"
 7 |     end
 8 | 
 9 |     it "does not match channel patterns" do
10 |       Faye::Grammar::CHANNEL_NAME.should_not =~ "/foo/**"
11 |     end
12 | 
13 |     it "does not match invalid channel names" do
14 |       Faye::Grammar::CHANNEL_NAME.should_not =~ "foo/$@()bar"
15 |       Faye::Grammar::CHANNEL_NAME.should_not =~ "/foo/$@()bar/"
16 |       Faye::Grammar::CHANNEL_NAME.should_not =~ "/fo o/$@()bar"
17 |     end
18 |   end
19 | 
20 |   describe :CHANNEL_PATTERN do
21 |     it "does not match channel names" do
22 |       Faye::Grammar::CHANNEL_PATTERN.should_not =~ "/fo_o/$@()bar"
23 |     end
24 | 
25 |     it "matches valid channel patterns" do
26 |       Faye::Grammar::CHANNEL_PATTERN.should =~ "/foo/**"
27 |       Faye::Grammar::CHANNEL_PATTERN.should =~ "/foo/*"
28 |     end
29 | 
30 |     it "does not match invalid channel patterns" do
31 |       Faye::Grammar::CHANNEL_PATTERN.should_not =~ "/foo/**/*"
32 |     end
33 |   end
34 | 
35 |   describe :ERROR do
36 |     it "matches an error with an argument" do
37 |       Faye::Grammar::ERROR.should =~ "402:xj3sjdsjdsjad:Unknown Client ID"
38 |     end
39 | 
40 |     it "matches an error with many arguments" do
41 |       Faye::Grammar::ERROR.should =~ "403:xj3sjdsjdsjad,/foo/bar:Subscription denied"
42 |     end
43 | 
44 |     it "matches an error with no arguments" do
45 |       Faye::Grammar::ERROR.should =~ "402::Unknown Client ID"
46 |     end
47 | 
48 |     it "does not match an error with no code" do
49 |       Faye::Grammar::ERROR.should_not =~ ":xj3sjdsjdsjad:Unknown Client ID"
50 |     end
51 | 
52 |     it "does not match an error with an invalid code" do
53 |       Faye::Grammar::ERROR.should_not =~ "40:xj3sjdsjdsjad:Unknown Client ID"
54 |     end
55 |   end
56 | 
57 |   describe :VERSION do
58 |     it "matches a version number" do
59 |       Faye::Grammar::VERSION.should =~ "9"
60 |       Faye::Grammar::VERSION.should =~ "9.0.a-delta1"
61 |     end
62 | 
63 |     it "does not match invalid version numbers" do
64 |       Faye::Grammar::VERSION.should_not =~ "9.0.a-delta1."
65 |       Faye::Grammar::VERSION.should_not =~ ""
66 |     end
67 |   end
68 | end
69 | 


--------------------------------------------------------------------------------
/spec/ruby/publisher_spec.rb:
--------------------------------------------------------------------------------
 1 | require "spec_helper"
 2 | 
 3 | describe Faye::Publisher do
 4 |   let(:publisher) { Class.new { include Faye::Publisher }.new }
 5 | 
 6 |   describe "with subscribers that remove themselves" do
 7 |     before do
 8 |       @called_a = false
 9 |       @called_b = false
10 | 
11 |       handler = lambda do
12 |         @called_a = true
13 |         publisher.unbind(:event, &handler)
14 |       end
15 | 
16 |       publisher.bind(:event, &handler)
17 |       publisher.bind(:event) { @called_b = true }
18 |     end
19 | 
20 |     it "successfully calls all the callbacks" do
21 |       publisher.trigger(:event)
22 |       @called_a.should == true
23 |       @called_b.should == true
24 |     end
25 |   end
26 | end
27 | 


--------------------------------------------------------------------------------
/spec/ruby/server/disconnect_spec.rb:
--------------------------------------------------------------------------------
  1 | require "spec_helper"
  2 | 
  3 | describe "server disconnect" do
  4 |   let(:engine) { double "engine" }
  5 |   let(:server) { Faye::Server.new }
  6 | 
  7 |   before do
  8 |     Faye::Engine.stub(:get).and_return engine
  9 |   end
 10 | 
 11 |   describe :disconnect do
 12 |     let(:client_id) { "fakeclientid" }
 13 |     let(:message) { { "channel" => "/meta/disconnect",
 14 |                       "clientId" => "fakeclientid"
 15 |                   } }
 16 | 
 17 |     describe "with valid parameters" do
 18 |       before do
 19 |         engine.should_receive(:client_exists).with(client_id).and_yield true
 20 |       end
 21 | 
 22 |       it "destroys the client" do
 23 |         engine.should_receive(:destroy_client).with(client_id)
 24 |         server.disconnect(message) {}
 25 |       end
 26 | 
 27 |       it "returns a successful response" do
 28 |         engine.stub(:destroy_client)
 29 |         server.disconnect(message) do |response|
 30 |           response.should == {
 31 |             "channel"    => "/meta/disconnect",
 32 |             "successful" => true,
 33 |             "clientId"   => client_id
 34 |           }
 35 |         end
 36 |       end
 37 | 
 38 |       describe "with a message id" do
 39 |         before { message["id"] = "foo" }
 40 | 
 41 |         it "returns the same id" do
 42 |           engine.stub(:destroy_client)
 43 |           server.disconnect(message) do |response|
 44 |             response.should == {
 45 |               "channel"    => "/meta/disconnect",
 46 |               "successful" => true,
 47 |               "clientId"   => client_id,
 48 |               "id"         => "foo"
 49 |             }
 50 |           end
 51 |         end
 52 |       end
 53 |     end
 54 | 
 55 |     describe "with an unknown client" do
 56 |       before do
 57 |         engine.should_receive(:client_exists).with(client_id).and_yield false
 58 |       end
 59 | 
 60 |       it "does not destroy the client" do
 61 |         engine.should_not_receive(:destroy_client)
 62 |         server.disconnect(message) {}
 63 |       end
 64 | 
 65 |       it "returns an unsuccessful response" do
 66 |         server.disconnect(message) do |response|
 67 |           response.should == {
 68 |             "channel"    => "/meta/disconnect",
 69 |             "successful" => false,
 70 |             "error"      => "401:fakeclientid:Unknown client"
 71 |           }
 72 |         end
 73 |       end
 74 |     end
 75 | 
 76 |     describe "missing clientId" do
 77 |       before do
 78 |         message.delete("clientId")
 79 |         engine.should_receive(:client_exists).with(nil).and_yield false
 80 |       end
 81 | 
 82 |       it "does not destroy the client" do
 83 |         engine.should_not_receive(:destroy_client)
 84 |         server.disconnect(message) {}
 85 |       end
 86 | 
 87 |       it "returns an unsuccessful response" do
 88 |         server.disconnect(message) do |response|
 89 |           response.should == {
 90 |             "channel"    => "/meta/disconnect",
 91 |             "successful" => false,
 92 |             "error"      => "402:clientId:Missing required parameter"
 93 |           }
 94 |         end
 95 |       end
 96 |     end
 97 | 
 98 |     describe "with an error" do
 99 |       before do
100 |         message["error"] = "invalid"
101 |         engine.should_receive(:client_exists).with(client_id).and_yield true
102 |       end
103 | 
104 |       it "does not destroy the client" do
105 |         engine.should_not_receive(:destroy_client)
106 |         server.disconnect(message) {}
107 |       end
108 | 
109 |       it "returns an unsuccessful response" do
110 |         server.disconnect(message) do |response|
111 |           response.should == {
112 |             "channel"    => "/meta/disconnect",
113 |             "successful" => false,
114 |             "error"      => "invalid"
115 |           }
116 |         end
117 |       end
118 |     end
119 |   end
120 | end
121 | 


--------------------------------------------------------------------------------
/spec/ruby/server/extensions_spec.rb:
--------------------------------------------------------------------------------
  1 | require "spec_helper"
  2 | 
  3 | describe "server extensions" do
  4 |   let(:engine) do
  5 |     engine = double "engine"
  6 |     engine.stub(:interval).and_return(0)
  7 |     engine.stub(:timeout).and_return(60)
  8 |     engine
  9 |   end
 10 | 
 11 |   let(:server)  { Faye::Server.new }
 12 |   let(:message) { { "channel" => "/foo", "data" => "hello" } }
 13 | 
 14 |   before do
 15 |     Faye::Engine.stub(:get).and_return engine
 16 |   end
 17 | 
 18 |   describe "with an incoming extension installed" do
 19 |     before do
 20 |       extension = Class.new do
 21 |         def incoming(message, callback)
 22 |           message["ext"] = { "auth" => "password" }
 23 |           callback.call(message)
 24 |         end
 25 |       end
 26 |       server.add_extension(extension.new)
 27 |     end
 28 | 
 29 |     it "passes incoming messages through the extension" do
 30 |       engine.should_receive(:publish).with({ "channel" => "/foo", "data" => "hello", "ext" => { "auth" => "password" }})
 31 |       server.process(message, false) {}
 32 |     end
 33 | 
 34 |     it "does not pass outgoing messages through the extension" do
 35 |       server.stub(:handshake).and_yield(message)
 36 |       engine.stub(:publish)
 37 |       response = nil
 38 |       server.process({ "channel" => "/meta/handshake" }, false) { |r| response = r }
 39 |       response.should == [{ "channel" => "/foo", "data" => "hello" }]
 40 |     end
 41 |   end
 42 | 
 43 |   describe "with subscription auth installed" do
 44 |     before do
 45 |       extension = Class.new do
 46 |         def incoming(message, callback)
 47 |           if message["channel"] == "/meta/subscribe" and !message["auth"]
 48 |             message["error"] = "Invalid auth"
 49 |           end
 50 |           callback.call(message)
 51 |         end
 52 |       end
 53 |       server.add_extension(extension.new)
 54 |     end
 55 | 
 56 |     it "does not subscribe using the intended channel" do
 57 |       message = {
 58 |         "channel" => "/meta/subscribe",
 59 |         "clientId" => "fakeclientid",
 60 |         "subscription" => "/foo"
 61 |       }
 62 |       engine.stub(:client_exists).and_yield(true)
 63 |       engine.should_not_receive(:subscribe)
 64 |       server.process(message, false) {}
 65 |     end
 66 | 
 67 |     it "does not subscribe using an extended channel" do
 68 |       message = {
 69 |         "channel" => "/meta/subscribe/x",
 70 |         "clientId" => "fakeclientid",
 71 |         "subscription" => "/foo"
 72 |       }
 73 |       engine.stub(:client_exists).and_yield(true)
 74 |       engine.should_not_receive(:subscribe)
 75 |       server.process(message, false) {}
 76 |     end
 77 |   end
 78 | 
 79 |   describe "with an outgoing extension installed" do
 80 |     before do
 81 |       extension = Class.new do
 82 |         def outgoing(message, callback)
 83 |           message["ext"] = { "auth" => "password" }
 84 |           callback.call(message)
 85 |         end
 86 |       end
 87 |       server.add_extension(extension.new)
 88 |     end
 89 | 
 90 |     it "does not pass incoming messages through the extension" do
 91 |       engine.should_receive(:publish).with({ "channel" => "/foo", "data" => "hello" })
 92 |       server.process(message, false) {}
 93 |     end
 94 | 
 95 |     it "passes outgoing messages through the extension" do
 96 |       server.stub(:handshake).and_yield(message)
 97 |       engine.stub(:publish)
 98 |       response = nil
 99 |       server.process({ "channel" => "/meta/handshake" }, false) { |r| response = r }
100 |       response.should == [{ "channel" => "/foo", "data" => "hello", "ext" => { "auth" => "password" }}]
101 |     end
102 |   end
103 | end
104 | 


--------------------------------------------------------------------------------
/spec/ruby/server/integration_spec.rb:
--------------------------------------------------------------------------------
  1 | # encoding=utf-8
  2 | 
  3 | require "spec_helper"
  4 | 
  5 | IntegrationSteps = RSpec::EM.async_steps do
  6 |   class Tagger
  7 |     def incoming(message, callback)
  8 |       message["data"]["tagged"] = true if message["data"]
  9 |       callback.call(message)
 10 |     end
 11 | 
 12 |     def outgoing(message, request, callback)
 13 |       message["data"]["url"] = request.path_info if message["data"]
 14 |       callback.call(message)
 15 |     end
 16 |   end
 17 | 
 18 |   def server(port, &callback)
 19 |     @faye = Faye::RackAdapter.new(:mount => "/bayeux", :timeout => 25)
 20 |     @faye.add_extension(Tagger.new)
 21 | 
 22 |     @server = ServerProxy::App.new(@faye)
 23 |     @port   = port
 24 | 
 25 |     @server.listen(@port)
 26 |     EM.next_tick(&callback)
 27 |   end
 28 | 
 29 |   def stop(&callback)
 30 |     @server.stop
 31 |     EM.next_tick(&callback)
 32 |   end
 33 | 
 34 |   def client(name, channels, &callback)
 35 |     @clients       ||= {}
 36 |     @inboxes       ||= {}
 37 |     @clients[name]   = Faye::Client.new("http://0.0.0.0:#{ @port }/bayeux")
 38 |     @inboxes[name]   = {}
 39 | 
 40 |     n = channels.size
 41 |     return @clients[name].connect(&callback) if n.zero?
 42 | 
 43 |     channels.each do |channel|
 44 |       subscription = @clients[name].subscribe(channel) do |message|
 45 |         @inboxes[name][channel] ||= []
 46 |         @inboxes[name][channel] << message
 47 |       end
 48 |       subscription.callback do
 49 |         n -= 1
 50 |         callback.call if n.zero?
 51 |       end
 52 |     end
 53 |   end
 54 | 
 55 |   def publish(name, channel, message, &callback)
 56 |     @clients[name].publish(channel, message)
 57 |     EM.add_timer(0.1, &callback)
 58 |   end
 59 | 
 60 |   def check_inbox(name, channel, messages, &callback)
 61 |     inbox = @inboxes[name][channel] || []
 62 |     inbox.should == messages
 63 |     callback.call
 64 |   end
 65 | end
 66 | 
 67 | describe "server integration" do
 68 |   next if RUBY_PLATFORM =~ /java/
 69 | 
 70 |   include IntegrationSteps
 71 |   include EncodingHelper
 72 | 
 73 |   before do
 74 |     server 4180
 75 |     client :alice, []
 76 |     client :bob,   ["/foo"]
 77 |   end
 78 | 
 79 |   after { stop }
 80 | 
 81 |   shared_examples_for "message bus" do
 82 |     it "delivers a message between clients" do
 83 |       publish :alice, "/foo", { "hello" => "world", "extra" => nil }
 84 |       check_inbox :bob, "/foo", [{ "hello" => "world", "extra" => nil, "tagged" => true, "url" => "/bayeux" }]
 85 |     end
 86 | 
 87 |     it "does not deliver messages for unsubscribed channels" do
 88 |       publish :alice, "/bar", { "hello" => "world" }
 89 |       check_inbox :bob, "/foo", []
 90 |     end
 91 | 
 92 |     it "delivers multiple messages" do
 93 |       publish :alice, "/foo", { "hello" => "world" }
 94 |       publish :alice, "/foo", { "hello" => "world" }
 95 |       check_inbox :bob, "/foo", [{ "hello" => "world", "tagged" => true, "url" => "/bayeux" }, { "hello" => "world", "tagged" => true, "url" => "/bayeux" }]
 96 |     end
 97 | 
 98 |     it "delivers multibyte strings" do
 99 |       publish :alice, "/foo", { "hello" => encode("Apple = "), "tagged" => true, "url" => "/bayeux" }
100 |       check_inbox :bob, "/foo", [{ "hello" => encode("Apple = "), "tagged" => true, "url" => "/bayeux" }]
101 |     end
102 |   end
103 | 
104 |   shared_examples_for "network transports" do
105 |     describe "with HTTP transport" do
106 |       before do
107 |         Faye::Transport::WebSocket.stub(:usable?).and_yield(false)
108 |       end
109 | 
110 |       it_should_behave_like "message bus"
111 |     end
112 | 
113 |     describe "with WebSocket transport" do
114 |       before do
115 |         Faye::Transport::WebSocket.stub(:usable?).and_yield(true)
116 |       end
117 | 
118 |       it_should_behave_like "message bus"
119 |     end
120 |   end
121 | 
122 |   describe "with HTTP server" do
123 |     let(:server_options) { { :ssl => false } }
124 |     it_should_behave_like "network transports"
125 |   end
126 | end
127 | 


--------------------------------------------------------------------------------
/spec/ruby/server/publish_spec.rb:
--------------------------------------------------------------------------------
  1 | require "spec_helper"
  2 | 
  3 | describe "server publish" do
  4 |   let(:engine)  { double "engine" }
  5 |   let(:server)  { Faye::Server.new }
  6 |   let(:message) { { "channel" => "/some/channel", "data" => "publish" } }
  7 | 
  8 |   before do
  9 |     Faye::Engine.stub(:get).and_return engine
 10 |   end
 11 | 
 12 |   describe "publishing a message" do
 13 |     it "tells the engine to publish the message" do
 14 |       engine.should_receive(:publish).with(message)
 15 |       server.process(message, false) {}
 16 |     end
 17 | 
 18 |     it "returns a successful response" do
 19 |       engine.stub(:publish)
 20 |       server.process(message, false) do |response|
 21 |         response.should == [
 22 |           { "channel"     => "/some/channel",
 23 |             "successful"  => true
 24 |           }
 25 |         ]
 26 |       end
 27 |     end
 28 | 
 29 |     describe "with an invalid channel" do
 30 |       before { message["channel"] = "channel" }
 31 | 
 32 |       it "does not tell the engine to publish the message" do
 33 |         engine.should_not_receive(:publish)
 34 |         server.process(message, false) {}
 35 |       end
 36 | 
 37 |       it "returns an unsuccessful response" do
 38 |         engine.stub(:publish)
 39 |         server.process(message, false) do |response|
 40 |           response.should == [
 41 |             { "channel"     => "channel",
 42 |               "successful"  => false,
 43 |               "error"       => "405:channel:Invalid channel"
 44 |             }
 45 |           ]
 46 |         end
 47 |       end
 48 |     end
 49 | 
 50 |     describe "with no data" do
 51 |       before { message.delete("data") }
 52 | 
 53 |       it "does not tell the engine to publish the message" do
 54 |         engine.should_not_receive(:publish)
 55 |         server.process(message, false) {}
 56 |       end
 57 | 
 58 |       it "returns an unsuccessful response" do
 59 |         engine.stub(:publish)
 60 |         server.process(message, false) do |response|
 61 |           response.should == [
 62 |             { "channel"     => "/some/channel",
 63 |               "successful"  => false,
 64 |               "error"       => "402:data:Missing required parameter"
 65 |             }
 66 |           ]
 67 |         end
 68 |       end
 69 |     end
 70 | 
 71 | 
 72 |     describe "with an error" do
 73 |       before { message["error"] = "invalid" }
 74 | 
 75 |       it "does not tell the engine to publish the message" do
 76 |         engine.should_not_receive(:publish)
 77 |         server.process(message, false) {}
 78 |       end
 79 | 
 80 |       it "returns an unsuccessful response" do
 81 |         engine.stub(:publish)
 82 |         server.process(message, false) do |response|
 83 |           response.should == [
 84 |             { "channel"     => "/some/channel",
 85 |               "successful"  => false,
 86 |               "error"       => "invalid"
 87 |             }
 88 |           ]
 89 |         end
 90 |       end
 91 |     end
 92 | 
 93 |     describe "to an invalid channel" do
 94 |       before { message["channel"] = "/invalid/*" }
 95 | 
 96 |       it "does not tell the engine to publish the message" do
 97 |         engine.should_not_receive(:publish)
 98 |         server.process(message, false) {}
 99 |       end
100 |     end
101 |   end
102 | end
103 | 


--------------------------------------------------------------------------------
/spec/ruby/server_proxy.rb:
--------------------------------------------------------------------------------
 1 | class ServerProxy < Rack::Proxy
 2 |   HOST = 'localhost'
 3 |   PORT = '4180'
 4 | 
 5 |   class App
 6 |     def initialize(app)
 7 |       @app = app
 8 |     end
 9 | 
10 |     def listen(port)
11 |       events = Puma::Events.new($stdout, $stderr)
12 |       binder = Puma::Binder.new(events)
13 |       binder.parse(["tcp://0.0.0.0:#{ PORT }"], self)
14 | 
15 |       @server = Puma::Server.new(self, events)
16 |       @server.binder = binder
17 |       @thread = @server.run
18 |     rescue => e
19 |     end
20 | 
21 |     def stop
22 |       @server.stop
23 |       @thread.join
24 |     end
25 | 
26 |     def call(env)
27 |       @app.call(env)
28 |     end
29 | 
30 |     def log(message)
31 |     end
32 |   end
33 | 
34 |   def initialize(rack_app)
35 |     @app = App.new(rack_app)
36 |     @app.listen(PORT)
37 |   end
38 | 
39 |   def stop
40 |     @app.stop
41 |   end
42 | 
43 |   def rewrite_env(env)
44 |     env['HTTP_HOST'] = HOST
45 |     env['SERVER_PORT'] = PORT
46 |     env[Faye::RackAdapter::HTTP_X_NO_CONTENT_LENGTH] = '1'
47 |     env
48 |   end
49 | end
50 | 


--------------------------------------------------------------------------------
/spec/ruby/server_spec.rb:
--------------------------------------------------------------------------------
  1 | require "spec_helper"
  2 | 
  3 | describe Faye::Server do
  4 |   let(:engine) { double "engine" }
  5 |   let(:server) { Faye::Server.new }
  6 | 
  7 |   before do
  8 |     Faye::Engine.stub(:get).and_return engine
  9 |   end
 10 | 
 11 |   describe :process do
 12 |     let(:handshake)   { { "channel" => "/meta/handshake",   "data" => "handshake"   } }
 13 |     let(:connect)     { { "channel" => "/meta/connect",     "data" => "connect"     } }
 14 |     let(:disconnect)  { { "channel" => "/meta/disconnect",  "data" => "disconnect"  } }
 15 |     let(:subscribe)   { { "channel" => "/meta/subscribe",   "data" => "subscribe"   } }
 16 |     let(:unsubscribe) { { "channel" => "/meta/unsubscribe", "data" => "unsubscribe" } }
 17 |     let(:publish)     { { "channel" => "/some/channel",     "data" => "publish"     } }
 18 | 
 19 |     before do
 20 |       engine.stub(:interval).and_return(0)
 21 |       engine.stub(:timeout).and_return(60)
 22 |     end
 23 | 
 24 |     it "returns an empty response for no messages" do
 25 |       response = nil
 26 |       server.process([], false) { |r| response = r }
 27 |       response.should == []
 28 |     end
 29 | 
 30 |     it "ignores invalid messages" do
 31 |       response = nil
 32 |       server.process([{}, { "channel" => "invalid" }], false) { |r| response = r }
 33 |       response.should == [
 34 |         { "successful"  => false,
 35 |           "error"       => "402:data:Missing required parameter"
 36 |         },
 37 |         { "channel"     => "invalid",
 38 |           "successful"  => false,
 39 |           "error"       => "402:data:Missing required parameter"
 40 |         }
 41 |       ]
 42 |     end
 43 | 
 44 |     it "rejects unknown meta channels" do
 45 |       response = nil
 46 |       server.process([{ "channel" => "/meta/p" }], false) { |r| response = r }
 47 |       response.should == [
 48 |         { "channel"     => "/meta/p",
 49 |           "successful"  => false,
 50 |           "error"       => "403:/meta/p:Forbidden channel"
 51 |         }
 52 |       ]
 53 |     end
 54 | 
 55 |     it "routes single messages to appropriate handlers" do
 56 |       server.should_receive(:handshake).with(handshake, false)
 57 |       server.process(handshake, false) {}
 58 |     end
 59 | 
 60 |     it "routes a list of messages to appropriate handlers" do
 61 |       server.should_receive(:handshake).with(handshake, false)
 62 |       server.should_receive(:connect).with(connect, false)
 63 |       server.should_receive(:disconnect).with(disconnect, false)
 64 |       server.should_receive(:subscribe).with(subscribe, false)
 65 |       server.should_receive(:unsubscribe).with(unsubscribe, false)
 66 | 
 67 |       engine.should_not_receive(:publish).with(handshake)
 68 |       engine.should_not_receive(:publish).with(connect)
 69 |       engine.should_not_receive(:publish).with(disconnect)
 70 |       engine.should_not_receive(:publish).with(subscribe)
 71 |       engine.should_not_receive(:publish).with(unsubscribe)
 72 |       engine.should_receive(:publish).with(publish)
 73 | 
 74 |       server.process([handshake, connect, disconnect, subscribe, unsubscribe, publish], false)
 75 |     end
 76 | 
 77 |     describe "handshaking" do
 78 |       before do
 79 |         response = { "channel" => "/meta/handshake", "successful" => true }
 80 |         server.should_receive(:handshake).with(handshake, false).and_yield(response)
 81 |       end
 82 | 
 83 |       it "returns the handshake response with advice" do
 84 |         server.process(handshake, false) do |response|
 85 |           response.should == [
 86 |             { "channel" => "/meta/handshake",
 87 |               "successful" => true,
 88 |               "advice" => { "reconnect" => "retry", "interval" => 0, "timeout" => 60000 }
 89 |             }
 90 |           ]
 91 |         end
 92 |       end
 93 |     end
 94 | 
 95 |     describe "connecting for messages" do
 96 |       let(:messages) { [{ "channel" => "/a" }, { "channel" => "/b" }] }
 97 | 
 98 |       before do
 99 |         server.should_receive(:connect).with(connect, false).and_yield(messages)
100 |       end
101 | 
102 |       it "returns the new messages" do
103 |         server.process(connect, false) { |r| r.should == messages }
104 |       end
105 |     end
106 |   end
107 | end
108 | 


--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
 1 | require 'rubygems'
 2 | require 'bundler/setup'
 3 | require 'rack/proxy'
 4 | require 'rack/test'
 5 | require 'rspec/em'
 6 | 
 7 | require 'puma'
 8 | require 'puma/binder'
 9 | require 'puma/events'
10 | 
11 | require File.expand_path('../../lib/faye', __FILE__)
12 | 
13 | require 'ruby/encoding_helper'
14 | require 'ruby/server_proxy'
15 | require 'ruby/engine_examples'
16 | 


--------------------------------------------------------------------------------
/src/adapters/content_types.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 |   TYPE_JSON:    { 'Content-Type': 'application/json; charset=utf-8' },
3 |   TYPE_SCRIPT:  { 'Content-Type': 'text/javascript; charset=utf-8' },
4 |   TYPE_TEXT:    { 'Content-Type': 'text/plain; charset=utf-8' }
5 | };
6 | 


--------------------------------------------------------------------------------
/src/adapters/static_server.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | var crypto = require('crypto'),
 4 |     fs     = require('fs'),
 5 |     path   = require('path'),
 6 |     url    = require('url');
 7 | 
 8 | var Class        = require('../util/class'),
 9 |     assign       = require('../util/assign'),
10 |     contenttypes = require('./content_types');
11 | 
12 | var StaticServer = Class({
13 |   initialize: function(directory, pathRegex) {
14 |     this._directory = directory;
15 |     this._pathRegex = pathRegex;
16 |     this._pathMap   = {};
17 |     this._index     = {};
18 |   },
19 | 
20 |   map: function(requestPath, filename) {
21 |     this._pathMap[requestPath] = filename;
22 |   },
23 | 
24 |   test: function(pathname) {
25 |     return this._pathRegex.test(pathname);
26 |   },
27 | 
28 |   call: function(request, response) {
29 |     var pathname = url.parse(request.url, true).pathname,
30 |         filename = path.basename(pathname);
31 | 
32 |     filename = this._pathMap[filename] || filename;
33 |     this._index[filename] = this._index[filename] || {};
34 | 
35 |     var cache    = this._index[filename],
36 |         fullpath = path.join(this._directory, filename);
37 | 
38 |     try {
39 |       cache.content = cache.content || fs.readFileSync(fullpath);
40 |       cache.digest  = cache.digest  || crypto.createHash('sha1').update(cache.content).digest('hex');
41 |       cache.mtime   = cache.mtime   || fs.statSync(fullpath).mtime;
42 |     } catch (error) {
43 |       response.writeHead(404, {});
44 |       return response.end();
45 |     }
46 | 
47 |     var type = /\.js$/.test(pathname) ? 'TYPE_SCRIPT' : 'TYPE_JSON',
48 |         ims  = request.headers['if-modified-since'];
49 | 
50 |     var headers = {
51 |       'ETag':          cache.digest,
52 |       'Last-Modified': cache.mtime.toGMTString()
53 |     };
54 | 
55 |     if (request.headers['if-none-match'] === cache.digest) {
56 |       response.writeHead(304, headers);
57 |       response.end();
58 |     }
59 |     else if (ims && cache.mtime <= new Date(ims)) {
60 |       response.writeHead(304, headers);
61 |       response.end();
62 |     }
63 |     else {
64 |       headers['Content-Length'] = cache.content.length;
65 |       assign(headers, contenttypes[type]);
66 |       response.writeHead(200, headers);
67 |       response.end(cache.content);
68 |     }
69 |   }
70 | });
71 | 
72 | module.exports = StaticServer;
73 | 


--------------------------------------------------------------------------------
/src/engines/connection.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | var Class      = require('../util/class'),
 4 |     assign     = require('../util/assign'),
 5 |     Deferrable = require('../mixins/deferrable'),
 6 |     Timeouts   = require('../mixins/timeouts');
 7 | 
 8 | var Connection = Class({
 9 |   initialize: function(engine, id, options) {
10 |     this._engine  = engine;
11 |     this._id      = id;
12 |     this._options = options;
13 |     this._inbox   = [];
14 |   },
15 | 
16 |   deliver: function(message) {
17 |     delete message.clientId;
18 |     if (this.socket) return this.socket.send(message);
19 |     this._inbox.push(message);
20 |     this._beginDeliveryTimeout();
21 |   },
22 | 
23 |   connect: function(options, callback, context) {
24 |     options = options || {};
25 |     var timeout = (options.timeout !== undefined) ? options.timeout / 1000 : this._engine.timeout;
26 | 
27 |     this.setDeferredStatus('unknown');
28 |     this.callback(callback, context);
29 | 
30 |     this._beginDeliveryTimeout();
31 |     this._beginConnectionTimeout(timeout);
32 |   },
33 | 
34 |   flush: function() {
35 |     this.removeTimeout('connection');
36 |     this.removeTimeout('delivery');
37 | 
38 |     this.setDeferredStatus('succeeded', this._inbox);
39 |     this._inbox = [];
40 | 
41 |     if (!this.socket) this._engine.closeConnection(this._id);
42 |   },
43 | 
44 |   _beginDeliveryTimeout: function() {
45 |     if (this._inbox.length === 0) return;
46 |     this.addTimeout('delivery', this._engine.MAX_DELAY, this.flush, this);
47 |   },
48 | 
49 |   _beginConnectionTimeout: function(timeout) {
50 |     this.addTimeout('connection', timeout, this.flush, this);
51 |   }
52 | });
53 | 
54 | assign(Connection.prototype, Deferrable);
55 | assign(Connection.prototype, Timeouts);
56 | 
57 | module.exports = Connection;
58 | 


--------------------------------------------------------------------------------
/src/faye_browser.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | var constants = require('./util/constants'),
 4 |     Logging   = require('./mixins/logging');
 5 | 
 6 | var Faye = {
 7 |   VERSION:    constants.VERSION,
 8 | 
 9 |   Client:     require('./protocol/client'),
10 |   Scheduler:  require('./protocol/scheduler')
11 | };
12 | 
13 | Logging.wrapper = Faye;
14 | 
15 | module.exports = Faye;
16 | 


--------------------------------------------------------------------------------
/src/faye_node.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | var constants = require('./util/constants'),
 4 |     Logging   = require('./mixins/logging');
 5 | 
 6 | var Faye = {
 7 |   VERSION:      constants.VERSION,
 8 | 
 9 |   Client:       require('./protocol/client'),
10 |   Scheduler:    require('./protocol/scheduler'),
11 |   NodeAdapter:  require('./adapters/node_adapter')
12 | };
13 | 
14 | Logging.wrapper = Faye;
15 | 
16 | module.exports = Faye;
17 | 


--------------------------------------------------------------------------------
/src/mixins/deferrable.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | var Promise   = require('../util/promise');
 4 | 
 5 | module.exports = {
 6 |   then: function(callback, errback) {
 7 |     var self = this;
 8 |     if (!this._promise)
 9 |       this._promise = new Promise(function(resolve, reject) {
10 |         self._resolve = resolve;
11 |         self._reject  = reject;
12 |       });
13 | 
14 |     if (arguments.length === 0)
15 |       return this._promise;
16 |     else
17 |       return this._promise.then(callback, errback);
18 |   },
19 | 
20 |   callback: function(callback, context) {
21 |     return this.then(function(value) { callback.call(context, value) });
22 |   },
23 | 
24 |   errback: function(callback, context) {
25 |     return this.then(null, function(reason) { callback.call(context, reason) });
26 |   },
27 | 
28 |   timeout: function(seconds, message) {
29 |     this.then();
30 |     var self = this;
31 |     this._timer = global.setTimeout(function() {
32 |       self._reject(message);
33 |     }, seconds * 1000);
34 |   },
35 | 
36 |   setDeferredStatus: function(status, value) {
37 |     if (this._timer) global.clearTimeout(this._timer);
38 | 
39 |     this.then();
40 | 
41 |     if (status === 'succeeded')
42 |       this._resolve(value);
43 |     else if (status === 'failed')
44 |       this._reject(value);
45 |     else
46 |       delete this._promise;
47 |   }
48 | };
49 | 


--------------------------------------------------------------------------------
/src/mixins/logging.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | var toJSON = require('../util/to_json');
 4 | 
 5 | var Logging = {
 6 |   LOG_LEVELS: {
 7 |     fatal:  4,
 8 |     error:  3,
 9 |     warn:   2,
10 |     info:   1,
11 |     debug:  0
12 |   },
13 | 
14 |   writeLog: function(messageArgs, level) {
15 |     var logger = Logging.logger || (Logging.wrapper || Logging).logger;
16 |     if (!logger) return;
17 | 
18 |     var args   = Array.prototype.slice.apply(messageArgs),
19 |         banner = '[Faye',
20 |         klass  = this.className,
21 | 
22 |         message = args.shift().replace(/\?/g, function() {
23 |           try {
24 |             return toJSON(args.shift());
25 |           } catch (error) {
26 |             return '[Object]';
27 |           }
28 |         });
29 | 
30 |     if (klass) banner += '.' + klass;
31 |     banner += '] ';
32 | 
33 |     if (typeof logger[level] === 'function')
34 |       logger[level](banner + message);
35 |     else if (typeof logger === 'function')
36 |       logger(banner + message);
37 |   }
38 | };
39 | 
40 | for (var key in Logging.LOG_LEVELS)
41 |   (function(level) {
42 |     Logging[level] = function() {
43 |       this.writeLog(arguments, level);
44 |     };
45 |   })(key);
46 | 
47 | module.exports = Logging;
48 | 


--------------------------------------------------------------------------------
/src/mixins/publisher.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | var assign       = require('../util/assign'),
 4 |     EventEmitter = require('../util/event_emitter');
 5 | 
 6 | var Publisher = {
 7 |   countListeners: function(eventType) {
 8 |     return this.listeners(eventType).length;
 9 |   },
10 | 
11 |   bind: function(eventType, listener, context) {
12 |     var slice   = Array.prototype.slice,
13 |         handler = function() { listener.apply(context, slice.call(arguments)) };
14 | 
15 |     this._listeners = this._listeners || [];
16 |     this._listeners.push([eventType, listener, context, handler]);
17 |     return this.on(eventType, handler);
18 |   },
19 | 
20 |   unbind: function(eventType, listener, context) {
21 |     this._listeners = this._listeners || [];
22 |     var n = this._listeners.length, tuple;
23 | 
24 |     while (n--) {
25 |       tuple = this._listeners[n];
26 |       if (tuple[0] !== eventType) continue;
27 |       if (listener && (tuple[1] !== listener || tuple[2] !== context)) continue;
28 |       this._listeners.splice(n, 1);
29 |       this.removeListener(eventType, tuple[3]);
30 |     }
31 |   }
32 | };
33 | 
34 | assign(Publisher, EventEmitter.prototype);
35 | Publisher.trigger = Publisher.emit;
36 | 
37 | module.exports = Publisher;
38 | 


--------------------------------------------------------------------------------
/src/mixins/timeouts.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | module.exports = {
 4 |   addTimeout: function(name, delay, callback, context) {
 5 |     this._timeouts = this._timeouts || {};
 6 |     if (this._timeouts.hasOwnProperty(name)) return;
 7 |     var self = this;
 8 |     this._timeouts[name] = global.setTimeout(function() {
 9 |       delete self._timeouts[name];
10 |       callback.call(context);
11 |     }, 1000 * delay);
12 |   },
13 | 
14 |   removeTimeout: function(name) {
15 |     this._timeouts = this._timeouts || {};
16 |     var timeout = this._timeouts[name];
17 |     if (!timeout) return;
18 |     global.clearTimeout(timeout);
19 |     delete this._timeouts[name];
20 |   },
21 | 
22 |   removeAllTimeouts: function() {
23 |     this._timeouts = this._timeouts || {};
24 |     for (var name in this._timeouts) this.removeTimeout(name);
25 |   }
26 | };
27 | 


--------------------------------------------------------------------------------
/src/protocol/channel.js:
--------------------------------------------------------------------------------
  1 | 'use strict';
  2 | 
  3 | var Class     = require('../util/class'),
  4 |     assign    = require('../util/assign'),
  5 |     Publisher = require('../mixins/publisher'),
  6 |     Grammar   = require('./grammar');
  7 | 
  8 | var Channel = Class({
  9 |   initialize: function(name) {
 10 |     this.id = this.name = name;
 11 |   },
 12 | 
 13 |   push: function(message) {
 14 |     this.trigger('message', message);
 15 |   },
 16 | 
 17 |   isUnused: function() {
 18 |     return this.countListeners('message') === 0;
 19 |   }
 20 | });
 21 | 
 22 | assign(Channel.prototype, Publisher);
 23 | 
 24 | assign(Channel, {
 25 |   HANDSHAKE:    '/meta/handshake',
 26 |   CONNECT:      '/meta/connect',
 27 |   SUBSCRIBE:    '/meta/subscribe',
 28 |   UNSUBSCRIBE:  '/meta/unsubscribe',
 29 |   DISCONNECT:   '/meta/disconnect',
 30 | 
 31 |   META:         'meta',
 32 |   SERVICE:      'service',
 33 | 
 34 |   expand: function(name) {
 35 |     var segments = this.parse(name),
 36 |         channels = ['/**', name];
 37 | 
 38 |     var copy = segments.slice();
 39 |     copy[copy.length - 1] = '*';
 40 |     channels.push(this.unparse(copy));
 41 | 
 42 |     for (var i = 1, n = segments.length; i < n; i++) {
 43 |       copy = segments.slice(0, i);
 44 |       copy.push('**');
 45 |       channels.push(this.unparse(copy));
 46 |     }
 47 | 
 48 |     return channels;
 49 |   },
 50 | 
 51 |   isValid: function(name) {
 52 |     return Grammar.CHANNEL_NAME.test(name) ||
 53 |            Grammar.CHANNEL_PATTERN.test(name);
 54 |   },
 55 | 
 56 |   parse: function(name) {
 57 |     if (!this.isValid(name)) return null;
 58 |     return name.split('/').slice(1);
 59 |   },
 60 | 
 61 |   unparse: function(segments) {
 62 |     return '/' + segments.join('/');
 63 |   },
 64 | 
 65 |   isMeta: function(name) {
 66 |     var segments = this.parse(name);
 67 |     return segments ? (segments[0] === this.META) : null;
 68 |   },
 69 | 
 70 |   isService: function(name) {
 71 |     var segments = this.parse(name);
 72 |     return segments ? (segments[0] === this.SERVICE) : null;
 73 |   },
 74 | 
 75 |   isSubscribable: function(name) {
 76 |     if (!this.isValid(name)) return null;
 77 |     return !this.isMeta(name) && !this.isService(name);
 78 |   },
 79 | 
 80 |   Set: Class({
 81 |     initialize: function() {
 82 |       this._channels = {};
 83 |     },
 84 | 
 85 |     getKeys: function() {
 86 |       var keys = [];
 87 |       for (var key in this._channels) keys.push(key);
 88 |       return keys;
 89 |     },
 90 | 
 91 |     remove: function(name) {
 92 |       delete this._channels[name];
 93 |     },
 94 | 
 95 |     hasSubscription: function(name) {
 96 |       return this._channels.hasOwnProperty(name);
 97 |     },
 98 | 
 99 |     subscribe: function(names, subscription) {
100 |       var name;
101 |       for (var i = 0, n = names.length; i < n; i++) {
102 |         name = names[i];
103 |         var channel = this._channels[name] = this._channels[name] || new Channel(name);
104 |         channel.bind('message', subscription);
105 |       }
106 |     },
107 | 
108 |     unsubscribe: function(name, subscription) {
109 |       var channel = this._channels[name];
110 |       if (!channel) return false;
111 |       channel.unbind('message', subscription);
112 | 
113 |       if (channel.isUnused()) {
114 |         this.remove(name);
115 |         return true;
116 |       } else {
117 |         return false;
118 |       }
119 |     },
120 | 
121 |     distributeMessage: function(message) {
122 |       var channels = Channel.expand(message.channel);
123 | 
124 |       for (var i = 0, n = channels.length; i < n; i++) {
125 |         var channel = this._channels[channels[i]];
126 |         if (channel) channel.trigger('message', message);
127 |       }
128 |     }
129 |   })
130 | });
131 | 
132 | module.exports = Channel;
133 | 


--------------------------------------------------------------------------------
/src/protocol/error.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | var Class   = require('../util/class'),
 4 |     Grammar = require('./grammar');
 5 | 
 6 | var Error = Class({
 7 |   initialize: function(code, params, message) {
 8 |     this.code    = code;
 9 |     this.params  = Array.prototype.slice.call(params);
10 |     this.message = message;
11 |   },
12 | 
13 |   toString: function() {
14 |     return this.code + ':' +
15 |            this.params.join(',') + ':' +
16 |            this.message;
17 |   }
18 | });
19 | 
20 | Error.parse = function(message) {
21 |   message = message || '';
22 |   if (!Grammar.ERROR.test(message)) return new Error(null, [], message);
23 | 
24 |   var parts   = message.split(':'),
25 |       code    = parseInt(parts[0]),
26 |       params  = parts[1].split(','),
27 |       message = parts[2];
28 | 
29 |   return new Error(code, params, message);
30 | };
31 | 
32 | // http://code.google.com/p/cometd/wiki/BayeuxCodes
33 | var errors = {
34 |   versionMismatch:  [300, 'Version mismatch'],
35 |   conntypeMismatch: [301, 'Connection types not supported'],
36 |   extMismatch:      [302, 'Extension mismatch'],
37 |   badRequest:       [400, 'Bad request'],
38 |   clientUnknown:    [401, 'Unknown client'],
39 |   parameterMissing: [402, 'Missing required parameter'],
40 |   channelForbidden: [403, 'Forbidden channel'],
41 |   channelUnknown:   [404, 'Unknown channel'],
42 |   channelInvalid:   [405, 'Invalid channel'],
43 |   extUnknown:       [406, 'Unknown extension'],
44 |   publishFailed:    [407, 'Failed to publish'],
45 |   serverError:      [500, 'Internal server error']
46 | };
47 | 
48 | for (var name in errors)
49 |   (function(name) {
50 |     Error[name] = function() {
51 |       return new Error(errors[name][0], arguments, errors[name][1]).toString();
52 |     };
53 |   })(name);
54 | 
55 | module.exports = Error;
56 | 


--------------------------------------------------------------------------------
/src/protocol/extensible.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | var assign  = require('../util/assign'),
 4 |     Logging = require('../mixins/logging');
 5 | 
 6 | var Extensible = {
 7 |   addExtension: function(extension) {
 8 |     this._extensions = this._extensions || [];
 9 |     this._extensions.push(extension);
10 |     if (extension.added) extension.added(this);
11 |   },
12 | 
13 |   removeExtension: function(extension) {
14 |     if (!this._extensions) return;
15 |     var i = this._extensions.length;
16 |     while (i--) {
17 |       if (this._extensions[i] !== extension) continue;
18 |       this._extensions.splice(i,1);
19 |       if (extension.removed) extension.removed(this);
20 |     }
21 |   },
22 | 
23 |   pipeThroughExtensions: function(stage, message, request, callback, context) {
24 |     this.debug('Passing through ? extensions: ?', stage, message);
25 | 
26 |     if (!this._extensions) return callback.call(context, message);
27 |     var extensions = this._extensions.slice();
28 | 
29 |     var pipe = function(message) {
30 |       if (!message) return callback.call(context, message);
31 | 
32 |       var extension = extensions.shift();
33 |       if (!extension) return callback.call(context, message);
34 | 
35 |       var fn = extension[stage];
36 |       if (!fn) return pipe(message);
37 | 
38 |       if (fn.length >= 3) extension[stage](message, request, pipe);
39 |       else                extension[stage](message, pipe);
40 |     };
41 |     pipe(message);
42 |   }
43 | };
44 | 
45 | assign(Extensible, Logging);
46 | 
47 | module.exports = Extensible;
48 | 


--------------------------------------------------------------------------------
/src/protocol/grammar.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | 
3 | module.exports = {
4 |   CHANNEL_NAME:     /^\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+(\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+)*$/,
5 |   CHANNEL_PATTERN:  /^(\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+)*\/\*{1,2}$/,
6 |   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])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*)$/,
7 |   VERSION:          /^([0-9])+(\.(([a-z]|[A-Z])|[0-9])(((([a-z]|[A-Z])|[0-9])|\-|\_))*)*$/
8 | };
9 | 


--------------------------------------------------------------------------------
/src/protocol/publication.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | 
3 | var Class      = require('../util/class'),
4 |     Deferrable = require('../mixins/deferrable');
5 | 
6 | module.exports = Class(Deferrable);
7 | 


--------------------------------------------------------------------------------
/src/protocol/scheduler.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | var assign = require('../util/assign');
 4 | 
 5 | var Scheduler = function(message, options) {
 6 |   this.message  = message;
 7 |   this.options  = options;
 8 |   this.attempts = 0;
 9 | };
10 | 
11 | assign(Scheduler.prototype, {
12 |   getTimeout: function() {
13 |     return this.options.timeout;
14 |   },
15 | 
16 |   getInterval: function() {
17 |     return this.options.interval;
18 |   },
19 | 
20 |   isDeliverable: function() {
21 |     var attempts = this.options.attempts,
22 |         made     = this.attempts,
23 |         deadline = this.options.deadline,
24 |         now      = new Date().getTime();
25 | 
26 |     if (attempts !== undefined && made >= attempts)
27 |       return false;
28 | 
29 |     if (deadline !== undefined && now > deadline)
30 |       return false;
31 | 
32 |     return true;
33 |   },
34 | 
35 |   send: function() {
36 |     this.attempts += 1;
37 |   },
38 | 
39 |   succeed: function() {},
40 | 
41 |   fail: function() {},
42 | 
43 |   abort: function() {}
44 | });
45 | 
46 | module.exports = Scheduler;
47 | 


--------------------------------------------------------------------------------
/src/protocol/socket.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | var Class  = require('../util/class'),
 4 |     toJSON = require('../util/to_json');
 5 | 
 6 | module.exports = Class({
 7 |   initialize: function(server, socket, request) {
 8 |     this._server  = server;
 9 |     this._socket  = socket;
10 |     this._request = request;
11 |   },
12 | 
13 |   send: function(message) {
14 |     this._server.pipeThroughExtensions('outgoing', message, this._request, function(pipedMessage) {
15 |       if (this._socket)
16 |         this._socket.send(toJSON([pipedMessage]));
17 |     }, this);
18 |   },
19 | 
20 |   close: function() {
21 |     if (this._socket) this._socket.close();
22 |     delete this._socket;
23 |   }
24 | });
25 | 


--------------------------------------------------------------------------------
/src/protocol/subscription.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | var Class      = require('../util/class'),
 4 |     assign     = require('../util/assign'),
 5 |     Deferrable = require('../mixins/deferrable');
 6 | 
 7 | var Subscription = Class({
 8 |   initialize: function(client, channels, callback, context) {
 9 |     this._client    = client;
10 |     this._channels  = channels;
11 |     this._callback  = callback;
12 |     this._context   = context;
13 |     this._cancelled = false;
14 |   },
15 | 
16 |   withChannel: function(callback, context) {
17 |     this._withChannel = [callback, context];
18 |     return this;
19 |   },
20 | 
21 |   apply: function(context, args) {
22 |     var message = args[0];
23 | 
24 |     if (this._callback)
25 |       this._callback.call(this._context, message.data);
26 | 
27 |     if (this._withChannel)
28 |       this._withChannel[0].call(this._withChannel[1], message.channel, message.data);
29 |   },
30 | 
31 |   cancel: function() {
32 |     if (this._cancelled) return;
33 |     this._client.unsubscribe(this._channels, this);
34 |     this._cancelled = true;
35 |   },
36 | 
37 |   unsubscribe: function() {
38 |     this.cancel();
39 |   }
40 | });
41 | 
42 | assign(Subscription.prototype, Deferrable);
43 | 
44 | module.exports = Subscription;
45 | 


--------------------------------------------------------------------------------
/src/transport/browser_transports.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | var Transport = require('./transport');
 4 | 
 5 | Transport.register('websocket', require('./web_socket'));
 6 | Transport.register('eventsource', require('./event_source'));
 7 | Transport.register('long-polling', require('./xhr'));
 8 | Transport.register('cross-origin-long-polling', require('./cors'));
 9 | Transport.register('callback-polling', require('./jsonp'));
10 | 
11 | module.exports = Transport;
12 | 


--------------------------------------------------------------------------------
/src/transport/cors.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | var Class     = require('../util/class'),
 4 |     Set       = require('../util/set'),
 5 |     URI       = require('../util/uri'),
 6 |     assign    = require('../util/assign'),
 7 |     toJSON    = require('../util/to_json'),
 8 |     Transport = require('./transport');
 9 | 
10 | var CORS = assign(Class(Transport, {
11 |   encode: function(messages) {
12 |     return 'message=' + encodeURIComponent(toJSON(messages));
13 |   },
14 | 
15 |   request: function(messages) {
16 |     var xhrClass = global.XDomainRequest ? XDomainRequest : XMLHttpRequest,
17 |         xhr      = new xhrClass(),
18 |         id       = ++CORS._id,
19 |         headers  = this._dispatcher.headers,
20 |         self     = this,
21 |         key;
22 | 
23 |     xhr.open('POST', this.endpoint.href, true);
24 |     xhr.withCredentials = true;
25 | 
26 |     if (xhr.setRequestHeader) {
27 |       xhr.setRequestHeader('Pragma', 'no-cache');
28 |       for (key in headers) {
29 |         if (!headers.hasOwnProperty(key)) continue;
30 |         xhr.setRequestHeader(key, headers[key]);
31 |       }
32 |     }
33 | 
34 |     var cleanUp = function() {
35 |       if (!xhr) return false;
36 |       CORS._pending.remove(id);
37 |       xhr.onload = xhr.onerror = xhr.ontimeout = xhr.onprogress = null;
38 |       xhr = null;
39 |     };
40 | 
41 |     xhr.onload = function() {
42 |       var replies;
43 |       try { replies = JSON.parse(xhr.responseText) } catch (error) {}
44 | 
45 |       cleanUp();
46 | 
47 |       if (replies)
48 |         self._receive(replies);
49 |       else
50 |         self._handleError(messages);
51 |     };
52 | 
53 |     xhr.onerror = xhr.ontimeout = function() {
54 |       cleanUp();
55 |       self._handleError(messages);
56 |     };
57 | 
58 |     xhr.onprogress = function() {};
59 | 
60 |     if (xhrClass === global.XDomainRequest)
61 |       CORS._pending.add({ id: id, xhr: xhr });
62 | 
63 |     xhr.send(this.encode(messages));
64 |     return xhr;
65 |   }
66 | }), {
67 |   _id:      0,
68 |   _pending: new Set(),
69 | 
70 |   isUsable: function(dispatcher, endpoint, callback, context) {
71 |     if (URI.isSameOrigin(endpoint))
72 |       return callback.call(context, false);
73 | 
74 |     if (global.XDomainRequest)
75 |       return callback.call(context, endpoint.protocol === location.protocol);
76 | 
77 |     if (global.XMLHttpRequest) {
78 |       var xhr = new XMLHttpRequest();
79 |       return callback.call(context, xhr.withCredentials !== undefined);
80 |     }
81 |     return callback.call(context, false);
82 |   }
83 | });
84 | 
85 | module.exports = CORS;
86 | 


--------------------------------------------------------------------------------
/src/transport/event_source.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | var Class      = require('../util/class'),
 4 |     URI        = require('../util/uri'),
 5 |     copyObject = require('../util/copy_object'),
 6 |     assign     = require('../util/assign'),
 7 |     Deferrable = require('../mixins/deferrable'),
 8 |     Transport  = require('./transport'),
 9 |     XHR        = require('./xhr');
10 | 
11 | var EventSource = assign(Class(Transport, {
12 |   initialize: function(dispatcher, endpoint) {
13 |     Transport.prototype.initialize.call(this, dispatcher, endpoint);
14 |     if (!global.EventSource) return this.setDeferredStatus('failed');
15 | 
16 |     this._xhr = new XHR(dispatcher, endpoint);
17 | 
18 |     endpoint = copyObject(endpoint);
19 |     endpoint.pathname += '/' + dispatcher.clientId;
20 | 
21 |     var socket = new global.EventSource(URI.stringify(endpoint)),
22 |         self   = this;
23 | 
24 |     socket.onopen = function() {
25 |       self._everConnected = true;
26 |       self.setDeferredStatus('succeeded');
27 |     };
28 | 
29 |     socket.onerror = function() {
30 |       if (self._everConnected) {
31 |         self._handleError([]);
32 |       } else {
33 |         self.setDeferredStatus('failed');
34 |         socket.close();
35 |       }
36 |     };
37 | 
38 |     socket.onmessage = function(event) {
39 |       var replies;
40 |       try { replies = JSON.parse(event.data) } catch (error) {}
41 | 
42 |       if (replies)
43 |         self._receive(replies);
44 |       else
45 |         self._handleError([]);
46 |     };
47 | 
48 |     this._socket = socket;
49 |   },
50 | 
51 |   close: function() {
52 |     if (!this._socket) return;
53 |     this._socket.onopen = this._socket.onerror = this._socket.onmessage = null;
54 |     this._socket.close();
55 |     delete this._socket;
56 |   },
57 | 
58 |   isUsable: function(callback, context) {
59 |     this.callback(function() { callback.call(context, true) });
60 |     this.errback(function() { callback.call(context, false) });
61 |   },
62 | 
63 |   encode: function(messages) {
64 |     return this._xhr.encode(messages);
65 |   },
66 | 
67 |   request: function(messages) {
68 |     return this._xhr.request(messages);
69 |   }
70 | 
71 | }), {
72 |   isUsable: function(dispatcher, endpoint, callback, context) {
73 |     var id = dispatcher.clientId;
74 |     if (!id) return callback.call(context, false);
75 | 
76 |     XHR.isUsable(dispatcher, endpoint, function(usable) {
77 |       if (!usable) return callback.call(context, false);
78 |       this.create(dispatcher, endpoint).isUsable(callback, context);
79 |     }, this);
80 |   },
81 | 
82 |   create: function(dispatcher, endpoint) {
83 |     var sockets = dispatcher.transports.eventsource = dispatcher.transports.eventsource || {},
84 |         id      = dispatcher.clientId;
85 | 
86 |     var url = copyObject(endpoint);
87 |     url.pathname += '/' + (id || '');
88 |     url = URI.stringify(url);
89 | 
90 |     sockets[url] = sockets[url] || new this(dispatcher, endpoint);
91 |     return sockets[url];
92 |   }
93 | });
94 | 
95 | assign(EventSource.prototype, Deferrable);
96 | 
97 | module.exports = EventSource;
98 | 


--------------------------------------------------------------------------------
/src/transport/jsonp.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | var Class      = require('../util/class'),
 4 |     URI        = require('../util/uri'),
 5 |     copyObject = require('../util/copy_object'),
 6 |     assign     = require('../util/assign'),
 7 |     toJSON     = require('../util/to_json'),
 8 |     Transport  = require('./transport');
 9 | 
10 | var JSONP = assign(Class(Transport, {
11 |  encode: function(messages) {
12 |     var url = copyObject(this.endpoint);
13 |     url.query.message = toJSON(messages);
14 |     url.query.jsonp   = '__jsonp' + JSONP._cbCount + '__';
15 |     return URI.stringify(url);
16 |   },
17 | 
18 |   request: function(messages) {
19 |     var head         = document.getElementsByTagName('head')[0],
20 |         script       = document.createElement('script'),
21 |         callbackName = JSONP.getCallbackName(),
22 |         endpoint     = copyObject(this.endpoint),
23 |         self         = this;
24 | 
25 |     endpoint.query.message = toJSON(messages);
26 |     endpoint.query.jsonp   = callbackName;
27 | 
28 |     var cleanup = function() {
29 |       if (!global[callbackName]) return false;
30 |       global[callbackName] = undefined;
31 |       try { delete global[callbackName] } catch (error) {}
32 |       script.parentNode.removeChild(script);
33 |     };
34 | 
35 |     global[callbackName] = function(replies) {
36 |       cleanup();
37 |       self._receive(replies);
38 |     };
39 | 
40 |     script.type = 'text/javascript';
41 |     script.src  = URI.stringify(endpoint);
42 |     head.appendChild(script);
43 | 
44 |     script.onerror = function() {
45 |       cleanup();
46 |       self._handleError(messages);
47 |     };
48 | 
49 |     return { abort: cleanup };
50 |   }
51 | }), {
52 |   _cbCount: 0,
53 | 
54 |   getCallbackName: function() {
55 |     this._cbCount += 1;
56 |     return '__jsonp' + this._cbCount + '__';
57 |   },
58 | 
59 |   isUsable: function(dispatcher, endpoint, callback, context) {
60 |     callback.call(context, true);
61 |   }
62 | });
63 | 
64 | module.exports = JSONP;
65 | 


--------------------------------------------------------------------------------
/src/transport/node_local.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | var asap       = require('asap'),
 4 |     Class      = require('../util/class'),
 5 |     URI        = require('../util/uri'),
 6 |     copyObject = require('../util/copy_object'),
 7 |     assign     = require('../util/assign'),
 8 |     Server     = require('../protocol/server'),
 9 |     Transport  = require('./transport');
10 | 
11 | var NodeLocal = assign(Class(Transport, {
12 |   batching: false,
13 | 
14 |   request: function(messages) {
15 |     messages = copyObject(messages);
16 |     var self = this;
17 | 
18 |     asap(function() {
19 |       self.endpoint.process(messages, null, function(replies) {
20 |         self._receive(copyObject(replies));
21 |       });
22 |     });
23 |   }
24 | }), {
25 |   isUsable: function(client, endpoint, callback, context) {
26 |     callback.call(context, endpoint instanceof Server);
27 |   }
28 | });
29 | 
30 | module.exports = NodeLocal;
31 | 


--------------------------------------------------------------------------------
/src/transport/node_transports.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | var Transport = require('./transport');
 4 | 
 5 | Transport.register('in-process', require('./node_local'));
 6 | Transport.register('websocket', require('./web_socket'));
 7 | Transport.register('long-polling', require('./node_http'));
 8 | 
 9 | module.exports = Transport;
10 | 


--------------------------------------------------------------------------------
/src/transport/package.json:
--------------------------------------------------------------------------------
1 | {
2 |   "main":     "node_transports",
3 |   "browser":  "browser_transports"
4 | }
5 | 


--------------------------------------------------------------------------------
/src/transport/xhr.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | var Class     = require('../util/class'),
 4 |     URI       = require('../util/uri'),
 5 |     browser   = require('../util/browser'),
 6 |     assign    = require('../util/assign'),
 7 |     toJSON    = require('../util/to_json'),
 8 |     Transport = require('./transport');
 9 | 
10 | var XHR = assign(Class(Transport, {
11 |   encode: function(messages) {
12 |     return toJSON(messages);
13 |   },
14 | 
15 |   request: function(messages) {
16 |     var href = this.endpoint.href,
17 |         self = this,
18 |         xhr;
19 | 
20 |     // Prefer XMLHttpRequest over ActiveXObject if they both exist
21 |     if (global.XMLHttpRequest) {
22 |       xhr = new XMLHttpRequest();
23 |     } else if (global.ActiveXObject) {
24 |       xhr = new ActiveXObject('Microsoft.XMLHTTP');
25 |     } else {
26 |       return this._handleError(messages);
27 |     }
28 | 
29 |     xhr.open('POST', href, true);
30 |     xhr.setRequestHeader('Content-Type', 'application/json');
31 |     xhr.setRequestHeader('Pragma', 'no-cache');
32 |     xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
33 | 
34 |     var headers = this._dispatcher.headers;
35 |     for (var key in headers) {
36 |       if (!headers.hasOwnProperty(key)) continue;
37 |       xhr.setRequestHeader(key, headers[key]);
38 |     }
39 | 
40 |     var abort = function() { xhr.abort() };
41 |     if (global.onbeforeunload !== undefined)
42 |       browser.Event.on(global, 'beforeunload', abort);
43 | 
44 |     xhr.onreadystatechange = function() {
45 |       if (!xhr || xhr.readyState !== 4) return;
46 | 
47 |       var replies    = null,
48 |           status     = xhr.status,
49 |           text       = xhr.responseText,
50 |           successful = (status >= 200 && status < 300) || status === 304 || status === 1223;
51 | 
52 |       if (global.onbeforeunload !== undefined)
53 |         browser.Event.detach(global, 'beforeunload', abort);
54 | 
55 |       xhr.onreadystatechange = function() {};
56 |       xhr = null;
57 | 
58 |       if (!successful) return self._handleError(messages);
59 | 
60 |       try {
61 |         replies = JSON.parse(text);
62 |       } catch (error) {}
63 | 
64 |       if (replies)
65 |         self._receive(replies);
66 |       else
67 |         self._handleError(messages);
68 |     };
69 | 
70 |     xhr.send(this.encode(messages));
71 |     return xhr;
72 |   }
73 | }), {
74 |   isUsable: function(dispatcher, endpoint, callback, context) {
75 |     var usable = (navigator.product === 'ReactNative')
76 |               || URI.isSameOrigin(endpoint);
77 | 
78 |     callback.call(context, usable);
79 |   }
80 | });
81 | 
82 | module.exports = XHR;
83 | 


--------------------------------------------------------------------------------
/src/util/array.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | module.exports = {
 4 |   commonElement: function(lista, listb) {
 5 |     for (var i = 0, n = lista.length; i < n; i++) {
 6 |       if (this.indexOf(listb, lista[i]) !== -1)
 7 |         return lista[i];
 8 |     }
 9 |     return null;
10 |   },
11 | 
12 |   indexOf: function(list, needle) {
13 |     if (list.indexOf) return list.indexOf(needle);
14 | 
15 |     for (var i = 0, n = list.length; i < n; i++) {
16 |       if (list[i] === needle) return i;
17 |     }
18 |     return -1;
19 |   },
20 | 
21 |   map: function(object, callback, context) {
22 |     if (object.map) return object.map(callback, context);
23 |     var result = [];
24 | 
25 |     if (object instanceof Array) {
26 |       for (var i = 0, n = object.length; i < n; i++) {
27 |         result.push(callback.call(context || null, object[i], i));
28 |       }
29 |     } else {
30 |       for (var key in object) {
31 |         if (!object.hasOwnProperty(key)) continue;
32 |         result.push(callback.call(context || null, key, object[key]));
33 |       }
34 |     }
35 |     return result;
36 |   },
37 | 
38 |   filter: function(array, callback, context) {
39 |     if (array.filter) return array.filter(callback, context);
40 |     var result = [];
41 |     for (var i = 0, n = array.length; i < n; i++) {
42 |       if (callback.call(context || null, array[i], i))
43 |         result.push(array[i]);
44 |     }
45 |     return result;
46 |   },
47 | 
48 |   asyncEach: function(list, iterator, callback, context) {
49 |     var n       = list.length,
50 |         i       = -1,
51 |         calls   = 0,
52 |         looping = false;
53 | 
54 |     var iterate = function() {
55 |       calls -= 1;
56 |       i += 1;
57 |       if (i === n) return callback && callback.call(context);
58 |       iterator(list[i], resume);
59 |     };
60 | 
61 |     var loop = function() {
62 |       if (looping) return;
63 |       looping = true;
64 |       while (calls > 0) iterate();
65 |       looping = false;
66 |     };
67 | 
68 |     var resume = function() {
69 |       calls += 1;
70 |       loop();
71 |     };
72 |     resume();
73 |   }
74 | };
75 | 


--------------------------------------------------------------------------------
/src/util/assign.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | var forEach = Array.prototype.forEach,
 4 |     hasOwn  = Object.prototype.hasOwnProperty;
 5 | 
 6 | module.exports = function(target) {
 7 |   forEach.call(arguments, function(source, i) {
 8 |     if (i === 0) return;
 9 | 
10 |     for (var key in source) {
11 |       if (hasOwn.call(source, key)) target[key] = source[key];
12 |     }
13 |   });
14 | 
15 |   return target;
16 | };
17 | 


--------------------------------------------------------------------------------
/src/util/browser/event.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | var Event = {
 4 |   _registry: [],
 5 | 
 6 |   on: function(element, eventName, callback, context) {
 7 |     var wrapped = function() { callback.call(context) };
 8 | 
 9 |     if (element.addEventListener)
10 |       element.addEventListener(eventName, wrapped, false);
11 |     else
12 |       element.attachEvent('on' + eventName, wrapped);
13 | 
14 |     this._registry.push({
15 |       _element:   element,
16 |       _type:      eventName,
17 |       _callback:  callback,
18 |       _context:     context,
19 |       _handler:   wrapped
20 |     });
21 |   },
22 | 
23 |   detach: function(element, eventName, callback, context) {
24 |     var i = this._registry.length, register;
25 |     while (i--) {
26 |       register = this._registry[i];
27 | 
28 |       if ((element    && element    !== register._element)  ||
29 |           (eventName  && eventName  !== register._type)     ||
30 |           (callback   && callback   !== register._callback) ||
31 |           (context    && context    !== register._context))
32 |         continue;
33 | 
34 |       if (register._element.removeEventListener)
35 |         register._element.removeEventListener(register._type, register._handler, false);
36 |       else
37 |         register._element.detachEvent('on' + register._type, register._handler);
38 | 
39 |       this._registry.splice(i,1);
40 |       register = null;
41 |     }
42 |   }
43 | };
44 | 
45 | module.exports = {
46 |   Event: Event
47 | };
48 | 


--------------------------------------------------------------------------------
/src/util/browser/node_shim.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | 
3 | module.exports = {};
4 | 


--------------------------------------------------------------------------------
/src/util/browser/package.json:
--------------------------------------------------------------------------------
1 | {
2 |   "main":     "node_shim",
3 |   "browser":  "event"
4 | }
5 | 


--------------------------------------------------------------------------------
/src/util/class.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | var assign = require('./assign');
 4 | 
 5 | module.exports = function(parent, methods) {
 6 |   if (typeof parent !== 'function') {
 7 |     methods = parent;
 8 |     parent  = Object;
 9 |   }
10 | 
11 |   var klass = function() {
12 |     if (!this.initialize) return this;
13 |     return this.initialize.apply(this, arguments) || this;
14 |   };
15 | 
16 |   var bridge = function() {};
17 |   bridge.prototype = parent.prototype;
18 | 
19 |   klass.prototype = new bridge();
20 |   assign(klass.prototype, methods);
21 | 
22 |   return klass;
23 | };
24 | 


--------------------------------------------------------------------------------
/src/util/constants.js:
--------------------------------------------------------------------------------
 1 | module.exports = {
 2 |   VERSION:          '1.4.1',
 3 | 
 4 |   BAYEUX_VERSION:   '1.0',
 5 |   ID_LENGTH:        160,
 6 |   JSONP_CALLBACK:   'jsonpcallback',
 7 |   CONNECTION_TYPES: ['long-polling', 'cross-origin-long-polling', 'callback-polling', 'websocket', 'eventsource', 'in-process'],
 8 | 
 9 |   MANDATORY_CONNECTION_TYPES: ['long-polling', 'callback-polling', 'in-process']
10 | };
11 | 


--------------------------------------------------------------------------------
/src/util/cookies/browser_cookies.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | 
3 | module.exports = {};
4 | 


--------------------------------------------------------------------------------
/src/util/cookies/node_cookies.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | 
3 | module.exports = require('tough-cookie');
4 | 


--------------------------------------------------------------------------------
/src/util/cookies/package.json:
--------------------------------------------------------------------------------
1 | {
2 |   "main":     "node_cookies",
3 |   "browser":  "browser_cookies"
4 | }
5 | 


--------------------------------------------------------------------------------
/src/util/copy_object.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | var copyObject = function(object) {
 4 |   var clone, i, key;
 5 |   if (object instanceof Array) {
 6 |     clone = [];
 7 |     i = object.length;
 8 |     while (i--) clone[i] = copyObject(object[i]);
 9 |     return clone;
10 |   } else if (typeof object === 'object') {
11 |     clone = (object === null) ? null : {};
12 |     for (key in object) clone[key] = copyObject(object[key]);
13 |     return clone;
14 |   } else {
15 |     return object;
16 |   }
17 | };
18 | 
19 | module.exports = copyObject;
20 | 


--------------------------------------------------------------------------------
/src/util/id_from_messages.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | var array = require('./array');
 4 | 
 5 | module.exports = function(messages) {
 6 |   var connect = array.filter([].concat(messages), function(message) {
 7 |     return message.channel === '/meta/connect';
 8 |   });
 9 |   return connect[0] && connect[0].clientId;
10 | };
11 | 


--------------------------------------------------------------------------------
/src/util/namespace.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | var Class  = require('./class'),
 4 |     random = require('./random');
 5 | 
 6 | module.exports = Class({
 7 |   initialize: function() {
 8 |     this._used = {};
 9 |   },
10 | 
11 |   exists: function(id) {
12 |     return this._used.hasOwnProperty(id);
13 |   },
14 | 
15 |   generate: function() {
16 |     var name = random();
17 |     while (this._used.hasOwnProperty(name))
18 |       name = random();
19 |     return this._used[name] = name;
20 |   },
21 | 
22 |   release: function(id) {
23 |     delete this._used[id];
24 |   }
25 | });
26 | 


--------------------------------------------------------------------------------
/src/util/random.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | var csprng    = require('csprng'),
 4 |     constants = require('./constants');
 5 | 
 6 | module.exports = function(bitlength) {
 7 |   bitlength = bitlength || constants.ID_LENGTH;
 8 |   var maxLength = Math.ceil(bitlength * Math.log(2) / Math.log(36));
 9 |   var string = csprng(bitlength, 36);
10 |   while (string.length < maxLength) string = '0' + string;
11 |   return string;
12 | };
13 | 


--------------------------------------------------------------------------------
/src/util/set.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | var Class = require('./class');
 4 | 
 5 | module.exports = Class({
 6 |   initialize: function() {
 7 |     this._index = {};
 8 |   },
 9 | 
10 |   add: function(item) {
11 |     var key = (item.id !== undefined) ? item.id : item;
12 |     if (this._index.hasOwnProperty(key)) return false;
13 |     this._index[key] = item;
14 |     return true;
15 |   },
16 | 
17 |   forEach: function(block, context) {
18 |     for (var key in this._index) {
19 |       if (this._index.hasOwnProperty(key))
20 |         block.call(context, this._index[key]);
21 |     }
22 |   },
23 | 
24 |   isEmpty: function() {
25 |     for (var key in this._index) {
26 |       if (this._index.hasOwnProperty(key)) return false;
27 |     }
28 |     return true;
29 |   },
30 | 
31 |   member: function(item) {
32 |     for (var key in this._index) {
33 |       if (this._index[key] === item) return true;
34 |     }
35 |     return false;
36 |   },
37 | 
38 |   remove: function(item) {
39 |     var key = (item.id !== undefined) ? item.id : item;
40 |     var removed = this._index[key];
41 |     delete this._index[key];
42 |     return removed;
43 |   },
44 | 
45 |   toArray: function() {
46 |     var array = [];
47 |     this.forEach(function(item) { array.push(item) });
48 |     return array;
49 |   }
50 | });
51 | 


--------------------------------------------------------------------------------
/src/util/to_json.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | // http://assanka.net/content/tech/2009/09/02/json2-js-vs-prototype/
 4 | 
 5 | module.exports = function(object) {
 6 |   return JSON.stringify(object, function(key, value) {
 7 |     return (this[key] instanceof Array) ? this[key] : value;
 8 |   });
 9 | };
10 | 


--------------------------------------------------------------------------------
/src/util/uri.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | module.exports = {
 4 |   isURI: function(uri) {
 5 |     return uri && uri.protocol && uri.host && uri.path;
 6 |   },
 7 | 
 8 |   isSameOrigin: function(uri) {
 9 |     return uri.protocol === location.protocol &&
10 |            uri.hostname === location.hostname &&
11 |            uri.port     === location.port;
12 |   },
13 | 
14 |   parse: function(url) {
15 |     if (typeof url !== 'string') return url;
16 |     var uri = {}, parts, query, pairs, i, n, data;
17 | 
18 |     var consume = function(name, pattern) {
19 |       url = url.replace(pattern, function(match) {
20 |         uri[name] = match;
21 |         return '';
22 |       });
23 |       uri[name] = uri[name] || '';
24 |     };
25 | 
26 |     consume('protocol', /^[a-z]+\:/i);
27 |     consume('host',     /^\/\/[^\/\?#]+/);
28 | 
29 |     if (!/^\//.test(url) && !uri.host)
30 |       url = location.pathname.replace(/[^\/]*$/, '') + url;
31 | 
32 |     consume('pathname', /^[^\?#]*/);
33 |     consume('search',   /^\?[^#]*/);
34 |     consume('hash',     /^#.*/);
35 | 
36 |     uri.protocol = uri.protocol || location.protocol;
37 | 
38 |     if (uri.host) {
39 |       uri.host = uri.host.substr(2);
40 | 
41 |       if (/@/.test(uri.host)) {
42 |         uri.auth = uri.host.split('@')[0];
43 |         uri.host = uri.host.split('@')[1];
44 |       }
45 |       parts        = uri.host.match(/^\[([^\]]+)\]|^[^:]+/);
46 |       uri.hostname = parts[1] || parts[0];
47 |       uri.port     = (uri.host.match(/:(\d+)$/) || [])[1] || '';
48 |     } else {
49 |       uri.host     = location.host;
50 |       uri.hostname = location.hostname;
51 |       uri.port     = location.port;
52 |     }
53 | 
54 |     uri.pathname = uri.pathname || '/';
55 |     uri.path = uri.pathname + uri.search;
56 | 
57 |     query = uri.search.replace(/^\?/, '');
58 |     pairs = query ? query.split('&') : [];
59 |     data  = {};
60 | 
61 |     for (i = 0, n = pairs.length; i < n; i++) {
62 |       parts = pairs[i].split('=');
63 |       data[decodeURIComponent(parts[0] || '')] = decodeURIComponent(parts[1] || '');
64 |     }
65 | 
66 |     uri.query = data;
67 | 
68 |     uri.href = this.stringify(uri);
69 |     return uri;
70 |   },
71 | 
72 |   stringify: function(uri) {
73 |     var auth   = uri.auth ? uri.auth + '@' : '',
74 |         string = uri.protocol + '//' + auth + uri.host;
75 | 
76 |     string += uri.pathname + this.queryString(uri.query) + (uri.hash || '');
77 | 
78 |     return string;
79 |   },
80 | 
81 |   queryString: function(query) {
82 |     var pairs = [];
83 |     for (var key in query) {
84 |       if (!query.hasOwnProperty(key)) continue;
85 |       pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(query[key]));
86 |     }
87 |     if (pairs.length === 0) return '';
88 |     return '?' + pairs.join('&');
89 |   }
90 | };
91 | 


--------------------------------------------------------------------------------
/src/util/validate_options.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | var array = require('./array');
 4 | 
 5 | module.exports = function(options, validKeys) {
 6 |   for (var key in options) {
 7 |     if (array.indexOf(validKeys, key) < 0)
 8 |       throw new Error('Unrecognized option: ' + key);
 9 |   }
10 | };
11 | 


--------------------------------------------------------------------------------
/src/util/websocket/browser_websocket.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | var WS = global.MozWebSocket || global.WebSocket;
 4 | 
 5 | module.exports = {
 6 |   create: function(url, protocols, options) {
 7 |     if (typeof WS !== 'function') return null;
 8 |     return new WS(url);
 9 |   }
10 | };
11 | 


--------------------------------------------------------------------------------
/src/util/websocket/node_websocket.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | var WS = require('faye-websocket').Client;
 4 | 
 5 | module.exports = {
 6 |   create: function(url, protocols, options) {
 7 |     return new WS(url, protocols, options);
 8 |   }
 9 | };
10 | 


--------------------------------------------------------------------------------
/src/util/websocket/package.json:
--------------------------------------------------------------------------------
1 | {
2 |   "main":     "node_websocket",
3 |   "browser":  "browser_websocket"
4 | }
5 | 


--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
 1 | let mode = process.env.NODE_ENV || 'development',
 2 |     name;
 3 | 
 4 | if (mode === 'production') {
 5 |   name = 'faye-browser-min';
 6 | } else {
 7 |   name = 'faye-browser';
 8 | }
 9 | 
10 | module.exports = {
11 |   mode,
12 |   devtool: 'source-map',
13 | 
14 |   entry: {
15 |     ['build/client/' + name]: '.',
16 |     'spec/browser_bundle': './spec/browser'
17 |   },
18 | 
19 |   output: {
20 |     path: __dirname,
21 |     filename: '[name].js',
22 |     library: 'Faye'
23 |   },
24 | 
25 |   module: {
26 |     rules: [
27 |       {
28 |         test: /\/spec\/.*\.js$/,
29 |         loader: 'imports-loader?define=>false'
30 |       }
31 |     ],
32 | 
33 |     noParse: /jstest/
34 |   },
35 | 
36 |   node: {
37 |     process: false
38 |   }
39 | };
40 | 


--------------------------------------------------------------------------------