pax_global_header00006660000000000000000000000064137110437720014517gustar00rootroot0000000000000052 comment=60141e8d3942a88ba3de6a9b3aef9503bc1bb1e6 faye-1.4.0/000077500000000000000000000000001371104377200124455ustar00rootroot00000000000000faye-1.4.0/.gitignore000066400000000000000000000001371371104377200144360ustar00rootroot00000000000000*.gem build Gemfile.lock lib/client node_modules package-lock.json spec/*_bundle.js spec/*.map faye-1.4.0/CHANGELOG.md000066400000000000000000000472151371104377200142670ustar00rootroot00000000000000### 1.4.0 / 2020-07-31 - Implement TLS certificate verification and enable it by default in the Ruby client class `Faye::Client` - Add a `:tls` option to the Ruby client with sub-field `:verify_peer` for configuring TLS verification - Officially support the `tls` option in the Node.js client, whose contents are forward to the `https` and `tls` modules as appropriate ### 1.3.0 / 2020-06-08 - Support `user:pass@` authorization in URIs and send `Authorization` headers from the Node HTTP transport - Support IPv6 hostnames in URIs - Allow credentials (cookies and `Authorization` headers) in cross-origin requests, by: - setting `Access-Control-Allow-Origin` to the value of the `Origin` header (not `*`) - enabling `Access-Control-Allow-Credentials` - Enable credentials when sending cross-origin requests - Don't disconnect WebSocket on page unload if `autodisconnect` is turned off - Catch errors when creating a WebSocket, which happens when Content Security Policy blocks it, allowing other transports to be tried - Fix a bug in the client where it handles messages from other clients as though they're the server's response to its own messages, based on the `id` field; now we only treat messages as server responses if they contain `successful: true` - Stop sending an empty message list `[]` from the WebSocket client as a keep-alive mechanism since CometD does not accept this message - Fix deprecation warnings for using the `new Buffer()` constructor - Switch to the Apache 2.0 license ### 1.2.5 / 2020-04-28 - Fix `/meta/*` channel recognition bug in the server that enables authentication bypass - https://blog.jcoglan.com/2020/04/28/authentication-bypass-in-faye/ ### 1.2.4 / 2017-01-28 - Fix `RackAdapter#get_client` that was failing due to a URI error - Define `Promise#catch` in a safe way for old browsers - Log errors in the Node HTTP transport ### 1.2.3 / 2016-10-11 - Return an error if the `data` field is missing on published messages - Fix errors that occur in the new `websocket` util when the browser does not support WebSocket ### 1.2.2 / 2016-07-18 - Mitigate the HTTPoxy vulnerability: https://httpoxy.org/ ### 1.2.1 / 2016-06-29 - Fix a missing variable error in `NodeAdapter` ### 1.2.0 / 2016-06-26 - Add `client.subscribe().withChannel()` to yield the message channel for wildcard subscriptions - Restructure the JavaScript codebase around Node modules (require/exports) rather than globals - Update the Promise shim to reflect the standard API, including `catch()` and `all()` - Support connecting to servers that use SNI in the Ruby client - Make the JavaScript client work inside React Native and Web Workers - Remove JSON2; you should import a JSON shim yourself if necessary - Handle errors that occur when a message is partially delivered via EventSource - Reject requests with invalid (non-array or -object) top-level JSON values - Make local client requests asynchronous to avoid re-entrant request handling errors - Remove `Connection: Close` from HTTP responses to allow use of keep-alive - Use `XMLHttpRequest` in preference to the ActiveX API in IE10 - Fix bug where flushing large message batches puts promises in an invalid state ### 1.1.3 / 2020-04-28 - Fix `/meta/*` channel recognition bug in the server that enables authentication bypass - https://blog.jcoglan.com/2020/04/28/authentication-bypass-in-faye/ ### 1.1.2 / 2015-07-19 - Allow the `Authorization` header to be used on CORS requests - Disallow unused methods like PUT and DELETE on CORS requests - Stop IE prematurely garbage-collecting `XDomainRequest` objects - Make sure messages can be sent if they overflow the request size limit and the outbox is empty - Don't send messages over WebSockets unless they are in the 'open' ready-state - Fix a bug preventing use of the in-process transport in Ruby ### 1.1.1 / 2015-02-25 - Make sure the client ID associated with a WebSocket is not dropped, so the socket can be closed properly - Handle cases where a JSON-P endpoint returns no response argument - Stop trying to retry messages after the client has been disconnected - Remove duplication of the client ID in EventSource URLs ### 1.1.0 / 2014-12-22 - Allow the server and client to use WebSocket extensions, for example permessage-deflate - Support the `HTTP_PROXY` and `HTTPS_PROXY` environment variables to send all client connections through an HTTP proxy - Introduce the `Scheduler` API to allow the user to control message retries - Add the `attempts` and `deadline` options to `Client#publish()` - Let `RackAdapter` take a block that yields the instance, so extensions can be added to middleware - Allow monitoring listeners to see the `clientId` on publishd messages but still avoid sending it to subscribers - Return a promise from `Client#disconnect()` - Fix client-side retry bugs causing the client to flood the server with duplicate messages - Send all transport types in the `supportedConnectionTypes` handshake parameter - Don't close WebSockets when the client recovers from an error and sends a new `clientId` - Replace `cookiejar` with `tough-cookie` to avoid global variable leaks ### 1.0.4 / 2020-04-28 - Fix `/meta/*` channel recognition bug in the server that enables authentication bypass - https://blog.jcoglan.com/2020/04/28/authentication-bypass-in-faye/ ### 1.0.3 / 2014-07-08 - Make some changes to JSON-P responses to mitigate the Rosetta Flash attack - http://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/ ### 1.0.2 -- removed due to error while publishing ### 1.0.1 / 2013-12-10 - Add `Adapter#close()` method for gracefully shutting down the server - Fix error recover bug in WebSocket that made transport cycle through `up`/`down` state - Update Promise implementation to pass `promises-aplus-tests 2.0` - Correct some incorrect variable names in the Ruby transports - Make logging methods public to fix a problem on Ruby 2.1 ### 1.0.0 / 2013-10-01 - Client changes: - Allow clients to be instantiated with URI objects rather than strings - Add a `ca` option to the Node `Client` class for passing in trusted server certificates - Objects supporting the `callback()` method in JavaScript are now Promises - Fix protocol-relative URI parsing in the client - Remove the `getClientId()` and `getState()` methods from the `Client` class - Transport changes: - Add request-size limiting to all batching transports - Make the WebSocket transport more robust against quiet network periods and clients going to sleep - Support cookies across all transports when using the client on Node.js or Ruby - Support custom headers in the `cross-origin-long-polling` and server-side `websocket` transports - Adapter changes: - Support the `rack.hijack` streaming API - Migrate to MultiJson for JSON handling on Ruby, allowing use of JRuby - Escape U+2028 and U+2029 in JSON-P output - Fix a bug stopping requests being routed when the mount point is `/` - Fix various bugs that cause errors to be thrown if we try to send a message over a closed socket - Remove the `listen()` method from `Adapter` in favour of using server-specific APIs - Server changes: - Use cryptographically secure random number generators to create client IDs - Allow extensions to access request properties by using 3-ary methods - Objects supporting the `bind()` method now implement the full `EventEmitter` API - Stop the server from forwarding the `clientId` property of published messages - Miscellaneous: - Support Browserify by returning the client module - `Faye.logger` can now be a logger object rather than a function ### 0.8.11 / 2014-07-08 - Make some changes to JSON-P responses to mitigate the Rosetta Flash attack - http://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/ ### 0.8.10 -- removed due to error while publishing ### 0.8.9 / 2013-02-26 - Specify ciphers for SSL on Node to mitigate the BEAST attack - Mitigate increased risk of socket hang-up errors in Node v0.8.20 - Fix race condition when processing outgoing extensions in the Node server - Fix problem loading the client script when using `{mount: '/'}` - Clean up connection objects when a WebSocket is re-used with a new clientId - All JavaScript code now runs in strict mode - Select transport on handshake, instead of on client creation to allow time for `disable()` calls - Do not speculatively open WebSocket/EventSource connections if they are disabled - Gracefully handle WebSocket messages with no data on the client side - Close and reconnect WebSocket when onerror is fired, not just when onclose is fired - Fix problem with caching of EventSource connections with stale clientIds - Don't parse query strings when checking if a URL is same-origin or not ### 0.8.8 / 2013-01-10 - Patch security hole allowing remote execution of arbitrary Server methods ### 0.8.7 -- removed due to error while publishing ### 0.8.6 / 2012-10-07 - Make sure messages pushed to the client over a socket pass through outgoing extensions ### 0.8.5 / 2012-09-30 - Fix a bug in `URI.parse()` that caused Faye endpoints to inherit search and hash from `window.location` ### 0.8.4 / 2012-09-29 - Optimise upgrade process so that WebSocket is tested earlier and the connection is cached - Check that EventSource actually works to work around broken Opera implementation - Emit `connection:open` and `connection:close` events from the Engine proxy - Increase size of client IDs from 128 to 160 bits - Fix bug with relative URL resolution in IE - Limit the JSON-P transport's message buffer so it doesn't create over-long URLs - Send `Pragma: no-cache` with XHR requests to guard against iOS 6 POST caching - Add `charset=utf-8` to response Content-Type headers ### 0.8.3 / 2012-07-15 - `Client#subscribe` returns an array of Subscriptions if given an array of channels - Allow different endpoints to be specified per-transport - Only use IE's `XDomainRequest` for same-protocol requests - Replace URL parser with one that treats relative URLs the same as the browser - Improve logging of malformed requests and detect problems earlier - Make sure socket connections are closed when a client session is timed out - Stop WebSocket reconnecting after `window.onbeforeunload` ### 0.8.2 / 2012-04-12 - Fix replacement of `null` with `{}` in `copyObject()` - Make EventSource transport trigger `transport:up/down` events - Supply source map for minified JavaScript client, and include source in gem - Return `Content-Length: 0` for 304 responses - Handle pre-flight CORS requests from old versions of Safari ### 0.8.1 / 2012-03-15 - Make `Publisher#trigger` safe for event listeners that modify the listener list - Make `Server#subscribe` return a response if the incoming message has an error - Fix edge case in code that identifies the `clientId` of socket connections - Return `Content-Length` headers for HTTP responses - Don't send empty lists of messages from the WebSocket transport - Stop client sending multiple `/meta/subscribe` messages for subscriptions made before handshaking - Stop client treating incoming published messages as responses to `/meta/*` messages ### 0.8.0 / 2012-02-26 - Extract the Redis engine into a separate library, `faye-redis` - Stabilize and document the Engine API so others can write backends - Extract WebSocket and EventSource tools into a separate library, `faye-websocket` - Improve use of WebSocket so messages are immediately pushed rather than polling - Introduce new EventSource-based transport, for proxies that block WebSocket - Support the Rainbows and Goliath web servers for Ruby, same as `faye-websocket` - Improve detection of network errors and switch to fixed-interval for reconnecting - Add `setHeader()` method to Client (e.g. for connecting to Salesforce API) - Add `timeout()` method to `Faye.Deferrable` to match `EventMachine::Deferrable` - Fix some bugs in client-side message handlers created with `subscribe()` - Improve speed and memory consumption of `copyObject()` - Switch from JSON to Yajl for JSON parsing in Ruby ### 0.7.2 / 2013-01-10 - Patch security hole allowing remote execution of arbitrary Server methods ### 0.7.1 / 2011-12-22 - Extension `added()` and `removed()` methods now receive the extended object - Detection of WebSockets in RackAdapter is more strict ### 0.7.0 / 2011-11-22 - Provide an event API for monitoring engine events on the server side - Implement server-side WebSocket connections for improved latency - Fix WebSocket protocol bugs and expose APIs for developers to use - Make server-side HTTP transports support SSL and cookies - Allow clients to disable selected transports and autodisconnection - Add callback/errback API to `Client#publish()` interface - Add `socket` setting for the Redis engine for connecting through a Unix socket ### 0.6.8 / 2013-01-10 - Patch security hole allowing remote execution of arbitrary Server methods ### 0.6.7 / 2011-10-20 - Cache client script in memory and add `ETag` and `Last-Modified` headers - Fix bug in Node Redis engine where `undefined` was used if no namespace given - Flush Redis message queues using a transaction to avoid re-delivery of messages - Fix race condition and timing errors present in Redis locking code - Use `Cache-Control: no-cache, no-store` on JSON-P responses - Improvements to the CORS and JSON-P transports - Prevent retry handlers in transports from being invoked multiple times - Use the current page protocol by default when parsing relative URIs ### 0.6.6 / 2011-09-12 - Add `:key` and `:cert` options to the `Adapter#listen` methods for setting up SSL - Fix error detection of CORS transport in IE9 running IE8 compatibility mode - Fix dependency versions so that Rubygems lets Faye install ### 0.6.5 / 2011-08-29 - Fix UTF-8 encoding bugs in draft-75/76 and protocol-8 WebSocket parsers - Switch to streaming parser for WebSocket protocol-8 - Remove an `SREM` operation that shouldn't have been in the Redis engine - Move `thin_extensions.rb` so it's not on the Rubygems load path ### 0.6.4 / 2011-08-18 - Support WebSocket protocol used by Chrome 14 and Firefox 6 - Fix handling of multibyte characters in WebSocket messages on Node - Improve message routing in Node memory engine to avoid false duplicates ### 0.6.3 / 2011-07-10 - Use sequential message IDs to reduce memory usage on the client side - Only send advice with handshake and connect responses - Stop trying to publish `/meta/*` messages - no-one is listening and it breaks `/**` - Fix bug causing invalid listeners to appear after a client reconnection - Stop loading `rubygems` within our library code - Make sure we only queue a message for each client once in the Redis engine - Use lists instead of sets for message queues in Redis - Improve clean-up of expired clients in Redis engine ### 0.6.2 / 2011-06-19 - Add authentication, database selection and namespacing to Redis engine - Clean up all client data when removing clients from Redis - Fix `cross-origin-long-polling` for `OPTIONS`-aware browsers - Update secure WebSocket detection for recent Node versions - Reinstate `faye.client` field in Rack environment ### 0.6.1 / 2011-06-06 - Fix `cross-origin-long-polling` support in `RackAdapter` - Plug some potential memory leaks in `Memory` engine ### 0.6.0 / 2011-05-21 - Extract core logic into the `Engine` class to support swappable backends - Introduce a Redis-backed engine to support clustered web front-ends - Use CORS for `cross-domain long-polling` - Make server more resilient against bad requests, including empty message lists - Perform subscription validation on the server and use errbacks to signal errors - Prohibit publishing to wildcard channels - Unsubscribing from a channel is now O(1) instead of O(N) - Much more thorough and consistent unit test coverage of both versions - Automatic integration tests using Terminus and TestSwarm ### 0.5.5 / 2011-01-16 - Open a real socket to check for WebSocket usability, not just object detection - Catch server-side errors when handshaking with WebSockets ### 0.5.4 / 2010-12-19 - Add a `#callback` method to `Subscriptions` to detect when they become active - Add `:extensions` option to `RackAdapter` to make it easier to extend middleware - Detect secure WebSocket requests through the `HTTP_X_FORWARDED_PROTO` header - Handle socket errors when sending WebSocket messages from `NodeAdapter` - Use exponential backoff to reconnect client-side WebSockets to reduce CPU load ### 0.5.3 / 2010-10-21 - Improve detection of `wss:` requirement for secure WebSocket connections - Correctly use default ports (80,443) for server-side HTTP connections - Support legacy `application/x-www-form-urlencoded` POST requests - Delete unused Channel objects that have all their subscribers removed - Fix resend/reconnect logic in WebSocket transport - Keep client script in memory rather than reading it from disk every time - Prevent error-adding extensions from breaking the core protocol ### 0.5.2 / 2010-08-12 - Support draft-76 of the WebSocket protocol (FF4, Chrome 6) - Reduce `Connection::MAX_DELAY` to improve latency ### 0.5.1 / 2010-07-21 - Fix a publishing problem in Ruby `LocalTransport` ### 0.5.0 / 2010-07-17 - Handle multiple event listeners bound to a channel - Add extension system for adding domain-specific logic to the protocol - Improve handling of client reconnections if the server goes down - Change default polling interval to 0 (immediate reconnect) - Add support for WebSockets (draft75 only) as a network transport - Remove support for Ruby servers other than Thin - Make client and server compatible with CometD (1.x and 2.0) components - Improve clean-up of unused server-side connections - Change Node API for adding Faye service to an HTTP server ### 0.3.4 / 2010-06-20 - Stop local clients going into an infinite loop if a subscription block causes a reconnect ### 0.3.3 / 2010-06-07 - Bring Node APIs up to date with 0.1.97 - Catch `ECONNREFUSED` errors in Node clients to withstand server outages - Refactor the `Server` internals ### 0.3.2 / 2010-04-04 - Fix problems with JSON serialization when Prototype, MooTools present - Make the client reconnect if it doesn't hear from the server after a timeout - Stop JavaScript server returning `NaN` for `advice.interval` - Make Ruby server return an integer for `advice.interval` - Ensure EventMachine is running before handling messages - Handle `data` and `end` events properly in Node HTTP API - Switch to `application/json` for content types and stop using querystring format in POST bodies - Respond to any URL path under the mount point, not just the exact match ### 0.3.1 / 2010-03-09 - Pass client down through Rack stack as `env['faye.client']` - Refactor some JavaScript internals to mirror Ruby codebase ### 0.3.0 / 2010-03-01 - Add server-side clients for Node.js and Ruby environments - Clients support both HTTP and in-process transports - Fix ID generation in JavaScript version to 128-bit IDs - Fix bug in interpretation of `**` channel wildcard - Users don't have to call `#connect()` on clients any more - Fix timeout race conditions that were killing active connections - Support new Node APIs from 0.1.29. ### 0.2.2 / 2010-02-10 - Kick out requests with malformed JSON as 400s ### 0.2.1 / 2010-02-04 - Fix server-side flushing of callback-polling connections - Backend can be used cross-domain if running on Node or Thin ### 0.2.0 / 2010-02-02 - Port server to JavaScript with an adapter for Node.js - Support Thin's async responses in the Ruby version for complete non-blocking - Fix some minor client-side bugs in transport choice ### 0.1.1 / 2009-07-26 - Fix a broken client build ### 0.1.0 / 2009-06-15 - Ruby Bayeux server and Rack adapter - Internally evented using EventMachine, web frontend blocks - JavaScript client with `long-polling` and `callback-polling` faye-1.4.0/CODE_OF_CONDUCT.md000066400000000000000000000002421371104377200152420ustar00rootroot00000000000000# Code of Conduct All projects under the [Faye](https://github.com/faye) umbrella are covered by the [Code of Conduct](https://github.com/faye/code-of-conduct). faye-1.4.0/CONTRIBUTING.md000066400000000000000000000017621371104377200147040ustar00rootroot00000000000000# Contributing to Faye Faye is implemented in both JavaScript and Ruby. You should be able to hack on each implementation independently, although since both implementations include a build of the JS client, you will need Node if you want to build the Ruby gem. To get the code: git clone git://github.com/faye/faye.git cd faye ## Working on the JavaScript codebase To install the dependencies (you will need to do this if you need to build the Ruby gem as well): npm install To run the tests on Node: npm test To run the tests in the browser, you should run make test which starts a process to continuously rebuild the source code and tests as you edit them. Open `spec/index.html` to run the tests. To build the package that we release to npm, run: make ## Working on the Ruby codebase To install the dependencies: bundle install To run the tests: bundle exec rspec To build the gem (you will need to install the Node dependencies for this): make gem faye-1.4.0/Gemfile000066400000000000000000000000461371104377200137400ustar00rootroot00000000000000source 'https://rubygems.org' gemspec faye-1.4.0/LICENSE.md000066400000000000000000000010561371104377200140530ustar00rootroot00000000000000Copyright 2009-2020 James Coglan Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. faye-1.4.0/Makefile000066400000000000000000000015501371104377200141060ustar00rootroot00000000000000PATH := node_modules/.bin:$(PATH) SHELL := /bin/bash source_files := $(shell find src -name '*.js') spec_files := $(shell find spec -name '*_spec.js') webpack_config := webpack.config.js name := faye-browser bundles := $(name).js $(name)-min.js client_dir := build/client client_bundles := $(bundles:%=$(client_dir)/%) top_files := CHANGELOG.md LICENSE.md README.md package.json src top_level := $(top_files:%=build/%) .PHONY: all gem clean all: $(client_bundles) $(top_level) gem: all gem build faye.gemspec clean: rm -rf build *.gem spec/*_bundle.js{,.map} $(client_dir)/$(name).js: $(webpack_config) $(source_files) webpack $(client_dir)/$(name)-min.js: $(webpack_config) $(source_files) NODE_ENV=production webpack build/src: $(source_files) build rsync -a src/ $@/ build/%: % build cp $< $@ build: mkdir -p $@ faye-1.4.0/README.md000066400000000000000000000006601371104377200137260ustar00rootroot00000000000000# Faye Faye is a set of tools for simple publish-subscribe messaging between web clients. It ships with easy-to-use message routing servers for Node.js and Rack applications, and clients that can be used on the server and in the browser. - Documentation: http://faye.jcoglan.com - Mailing list: http://groups.google.com/group/faye-users - Bug tracker: http://github.com/faye/faye/issues - Source code: http://github.com/faye/faye faye-1.4.0/Rakefile000066400000000000000000000011261371104377200141120ustar00rootroot00000000000000require 'rubygems' require './lib/faye' task :example, :port, :ssl do |t, args| exec "ruby examples/ruby/server.rb #{args[:port]} #{args[:ssl] && 'ssl'}" end task :handshake, :port, :n, :c do |t, args| require 'cgi' require 'json' message = {:channel => '/meta/handshake', :version => '1.0', :supportedConnectionTypes => ['long-polling']} message = CGI.escape(JSON.dump message) url = "http://127.0.0.1:#{args[:port]}/bayeux?jsonp=callback&message=#{message}" puts "Request URL:\n#{url}\n\n" exec "ab -n #{args[:n]} -c #{args[:c]} '#{url}'" end faye-1.4.0/examples/000077500000000000000000000000001371104377200142635ustar00rootroot00000000000000faye-1.4.0/examples/automate.rb000066400000000000000000000023361371104377200164330ustar00rootroot00000000000000# Load and configure Capybara require 'capybara/dsl' require 'terminus' Capybara.current_driver = :terminus Capybara.app_host = 'http://localhost:9292' extend Capybara::DSL # Acquire some browsers and log into each with a username NAMES = %w[alice bob carol dan erica frank gemma harold ingrid james] BROWSERS = {} Terminus.ensure_browsers 5 Terminus.browsers.each_with_index do |browser, i| name = NAMES[i] puts "#{ name } is using #{ browser }" BROWSERS[name] = browser Terminus.browser = browser visit '/' fill_in 'username', :with => name click_button 'Go' end # Send a message from each browser to every other browser, and check that it # arrived. If it doesn't arrive, send all the browsers back to the dock and # raise an exception BROWSERS.each do |name, sender| BROWSERS.each do |at, target| next if at == name Terminus.browser = sender fill_in 'message', :with => "@#{ at } Hello, world!" click_button 'Send' Terminus.browser = target unless page.has_content?("#{ name }: @#{ at } Hello, world!") Terminus.return_to_dock raise "Message did not make it from #{ sender } to #{ target }" end end end # Re-dock all the browsers when we're finished Terminus.return_to_dock faye-1.4.0/examples/haproxy.conf000066400000000000000000000005131371104377200166230ustar00rootroot00000000000000listen stats 127.0.0.1:7000 option httpchk mode http stats uri / listen soapbox 127.0.0.1:3000 option httpchk GET / server soapbox1 127.0.0.1:7070 weight 1 maxconn 1000 check port 7070 server soapbox2 127.0.0.1:8080 weight 1 maxconn 1000 check port 8080 server soapbox3 127.0.0.1:9090 weight 1 maxconn 1000 check port 9090 faye-1.4.0/examples/node/000077500000000000000000000000001371104377200152105ustar00rootroot00000000000000faye-1.4.0/examples/node/benchmark.js000066400000000000000000000017221371104377200175020ustar00rootroot00000000000000var faye = require('../../build'), port = process.argv[2] || 8000, path = process.argv[3] || 'bayeux', scheme = process.argv[4] === 'tls' ? 'https' : 'http'; var A = new faye.Client(scheme + '://localhost:' + port + '/' + path), B = new faye.Client(scheme + '://localhost:' + port + '/' + path); A.connect(function() { B.connect(function() { var time = new Date().getTime(), MAX = 1000; var stop = function() { console.log(new Date().getTime() - time); process.exit(); }; var handle = function(client, channel) { return function(n) { if (n === MAX) return stop(); client.publish(channel, n + 1); }; }; var subA = A.subscribe('/chat/a', handle(A, '/chat/b')), subB = B.subscribe('/chat/b', handle(B, '/chat/a')); subA.callback(function() { subB.callback(function() { console.log('START'); A.publish('/chat/b', 0); }); }); }); }); faye-1.4.0/examples/node/client.js000066400000000000000000000031761371104377200170330ustar00rootroot00000000000000// This script demonstrates a logger for the chat app. First, start the chat // server in one terminal then run this in another: // // $ node examples/node/server.js // $ node examples/node/client.js // // The client connects to the chat server and logs all messages sent by all // connected users. var fs = require('fs'), deflate = require('permessage-deflate'), faye = require('../../build'), port = process.argv[2] || 8000, path = process.argv[3] || 'bayeux', scheme = process.argv[4] === 'tls' ? 'https' : 'http', endpoint = scheme + '://localhost:' + port + '/' + path, cert = fs.readFileSync(__dirname + '/../server.crt'), proxy = { headers: { 'User-Agent': 'Faye' }, tls: { ca: cert }}; console.log('Connecting to ' + endpoint); var client = new faye.Client(endpoint, { proxy: proxy, tls: { ca: cert }}); client.addWebsocketExtension(deflate); var subscription = client.subscribe('/chat/*', function(message) { var user = message.user; var publication = client.publish('/members/' + user, { user: 'node-logger', message: ' Got your message, ' + user + '!' }); publication.callback(function() { console.log('[PUBLISH SUCCEEDED]'); }); publication.errback(function(error) { console.log('[PUBLISH FAILED]', error); }); }); subscription.callback(function() { console.log('[SUBSCRIBE SUCCEEDED]'); }); subscription.errback(function(error) { console.log('[SUBSCRIBE FAILED]', error); }); client.bind('transport:down', function() { console.log('[CONNECTION DOWN]'); }); client.bind('transport:up', function() { console.log('[CONNECTION UP]'); }); faye-1.4.0/examples/node/pingpong.js000066400000000000000000000007771371104377200174020ustar00rootroot00000000000000var faye = require('../../build'); ENDPOINT = 'http://localhost:8000/bayeux'; console.log('Connecting to ' + ENDPOINT); var ping = new faye.Client(ENDPOINT); ping.subscribe('/ping', function() { console.log('PING'); setTimeout(function() { ping.publish('/pong', {}) }, 1000); }); var pong = new faye.Client(ENDPOINT); pong.subscribe('/pong', function() { console.log('PONG'); setTimeout(function() { pong.publish('/ping', {}) }, 1000); }); setTimeout(function() { ping.publish('/pong', {}) }, 500); faye-1.4.0/examples/node/server.js000066400000000000000000000033031371104377200170530ustar00rootroot00000000000000var fs = require('fs'), path = require('path'), http = require('http'), https = require('https'), mime = require('mime'), deflate = require('permessage-deflate'), faye = require('../../build'); var SHARED_DIR = __dirname + '/..', PUBLIC_DIR = SHARED_DIR + '/public', bayeux = new faye.NodeAdapter({ mount: '/bayeux', timeout: 20 }), port = process.argv[2] || '8000', secure = process.argv[3] === 'tls', key = fs.readFileSync(SHARED_DIR + '/server.key'), cert = fs.readFileSync(SHARED_DIR + '/server.crt'); bayeux.addWebsocketExtension(deflate); var handleRequest = function(request, response) { var path = (request.url === '/') ? '/index.html' : request.url; fs.readFile(PUBLIC_DIR + path, function(err, content) { var status = err ? 404 : 200; try { response.writeHead(status, { 'Content-Type': mime.lookup(path) }); response.end(content || 'Not found'); } catch (e) {} }); }; var server = secure ? https.createServer({ cert: cert, key: key }, handleRequest) : http.createServer(handleRequest); bayeux.attach(server); server.listen(Number(port)); bayeux.getClient().subscribe('/chat/*', function(message) { console.log('[' + message.user + ']: ' + message.message); }); bayeux.on('subscribe', function(clientId, channel) { console.log('[ SUBSCRIBE] ' + clientId + ' -> ' + channel); }); bayeux.on('unsubscribe', function(clientId, channel) { console.log('[UNSUBSCRIBE] ' + clientId + ' -> ' + channel); }); bayeux.on('disconnect', function(clientId) { console.log('[ DISCONNECT] ' + clientId); }); console.log('Listening on ' + port + (secure? ' (https)' : '')); faye-1.4.0/examples/node/ticker.js000066400000000000000000000003621371104377200170300ustar00rootroot00000000000000var faye = require('../../build'); var endpoint = process.argv[2] || 'http://localhost:8000/bayeux', client = new faye.Client(endpoint), n = 0; setInterval(function() { client.publish('/chat/tick', { n: ++n }); }, 1000); faye-1.4.0/examples/public/000077500000000000000000000000001371104377200155415ustar00rootroot00000000000000faye-1.4.0/examples/public/index.html000066400000000000000000000025301371104377200175360ustar00rootroot00000000000000 Faye demo: chat client

Soapbox | a Twitter-style chat app


faye-1.4.0/examples/public/jquery.js000066400000000000000000001576471371104377200174420ustar00rootroot00000000000000/* * jQuery JavaScript Library v1.3.2 * http://jquery.com/ * * Copyright (c) 2009 John Resig * Dual licensed under the MIT and GPL licenses. * http://docs.jquery.com/License * * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009) * Revision: 6246 */ (function(){var l=this,g,y=l.jQuery,p=l.$,o=l.jQuery=l.$=function(E,F){return new o.fn.init(E,F)},D=/^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,f=/^.[^:#\[\.,]*$/;o.fn=o.prototype={init:function(E,H){E=E||document;if(E.nodeType){this[0]=E;this.length=1;this.context=E;return this}if(typeof E==="string"){var G=D.exec(E);if(G&&(G[1]||!H)){if(G[1]){E=o.clean([G[1]],H)}else{var I=document.getElementById(G[3]);if(I&&I.id!=G[3]){return o().find(E)}var F=o(I||[]);F.context=document;F.selector=E;return F}}else{return o(H).find(E)}}else{if(o.isFunction(E)){return o(document).ready(E)}}if(E.selector&&E.context){this.selector=E.selector;this.context=E.context}return this.setArray(o.isArray(E)?E:o.makeArray(E))},selector:"",jquery:"1.3.2",size:function(){return this.length},get:function(E){return E===g?Array.prototype.slice.call(this):this[E]},pushStack:function(F,H,E){var G=o(F);G.prevObject=this;G.context=this.context;if(H==="find"){G.selector=this.selector+(this.selector?" ":"")+E}else{if(H){G.selector=this.selector+"."+H+"("+E+")"}}return G},setArray:function(E){this.length=0;Array.prototype.push.apply(this,E);return this},each:function(F,E){return o.each(this,F,E)},index:function(E){return o.inArray(E&&E.jquery?E[0]:E,this)},attr:function(F,H,G){var E=F;if(typeof F==="string"){if(H===g){return this[0]&&o[G||"attr"](this[0],F)}else{E={};E[F]=H}}return this.each(function(I){for(F in E){o.attr(G?this.style:this,F,o.prop(this,E[F],G,I,F))}})},css:function(E,F){if((E=="width"||E=="height")&&parseFloat(F)<0){F=g}return this.attr(E,F,"curCSS")},text:function(F){if(typeof F!=="object"&&F!=null){return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(F))}var E="";o.each(F||this,function(){o.each(this.childNodes,function(){if(this.nodeType!=8){E+=this.nodeType!=1?this.nodeValue:o.fn.text([this])}})});return E},wrapAll:function(E){if(this[0]){var F=o(E,this[0].ownerDocument).clone();if(this[0].parentNode){F.insertBefore(this[0])}F.map(function(){var G=this;while(G.firstChild){G=G.firstChild}return G}).append(this)}return this},wrapInner:function(E){return this.each(function(){o(this).contents().wrapAll(E)})},wrap:function(E){return this.each(function(){o(this).wrapAll(E)})},append:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.appendChild(E)}})},prepend:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.insertBefore(E,this.firstChild)}})},before:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this)})},after:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this.nextSibling)})},end:function(){return this.prevObject||o([])},push:[].push,sort:[].sort,splice:[].splice,find:function(E){if(this.length===1){var F=this.pushStack([],"find",E);F.length=0;o.find(E,this[0],F);return F}else{return this.pushStack(o.unique(o.map(this,function(G){return o.find(E,G)})),"find",E)}},clone:function(G){var E=this.map(function(){if(!o.support.noCloneEvent&&!o.isXMLDoc(this)){var I=this.outerHTML;if(!I){var J=this.ownerDocument.createElement("div");J.appendChild(this.cloneNode(true));I=J.innerHTML}return o.clean([I.replace(/ jQuery\d+="(?:\d+|null)"/g,"").replace(/^\s*/,"")])[0]}else{return this.cloneNode(true)}});if(G===true){var H=this.find("*").andSelf(),F=0;E.find("*").andSelf().each(function(){if(this.nodeName!==H[F].nodeName){return}var I=o.data(H[F],"events");for(var K in I){for(var J in I[K]){o.event.add(this,K,I[K][J],I[K][J].data)}}F++})}return E},filter:function(E){return this.pushStack(o.isFunction(E)&&o.grep(this,function(G,F){return E.call(G,F)})||o.multiFilter(E,o.grep(this,function(F){return F.nodeType===1})),"filter",E)},closest:function(E){var G=o.expr.match.POS.test(E)?o(E):null,F=0;return this.map(function(){var H=this;while(H&&H.ownerDocument){if(G?G.index(H)>-1:o(H).is(E)){o.data(H,"closest",F);return H}H=H.parentNode;F++}})},not:function(E){if(typeof E==="string"){if(f.test(E)){return this.pushStack(o.multiFilter(E,this,true),"not",E)}else{E=o.multiFilter(E,this)}}var F=E.length&&E[E.length-1]!==g&&!E.nodeType;return this.filter(function(){return F?o.inArray(this,E)<0:this!=E})},add:function(E){return this.pushStack(o.unique(o.merge(this.get(),typeof E==="string"?o(E):o.makeArray(E))))},is:function(E){return !!E&&o.multiFilter(E,this).length>0},hasClass:function(E){return !!E&&this.is("."+E)},val:function(K){if(K===g){var E=this[0];if(E){if(o.nodeName(E,"option")){return(E.attributes.value||{}).specified?E.value:E.text}if(o.nodeName(E,"select")){var I=E.selectedIndex,L=[],M=E.options,H=E.type=="select-one";if(I<0){return null}for(var F=H?I:0,J=H?I+1:M.length;F=0||o.inArray(this.name,K)>=0)}else{if(o.nodeName(this,"select")){var N=o.makeArray(K);o("option",this).each(function(){this.selected=(o.inArray(this.value,N)>=0||o.inArray(this.text,N)>=0)});if(!N.length){this.selectedIndex=-1}}else{this.value=K}}})},html:function(E){return E===g?(this[0]?this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g,""):null):this.empty().append(E)},replaceWith:function(E){return this.after(E).remove()},eq:function(E){return this.slice(E,+E+1)},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments),"slice",Array.prototype.slice.call(arguments).join(","))},map:function(E){return this.pushStack(o.map(this,function(G,F){return E.call(G,F,G)}))},andSelf:function(){return this.add(this.prevObject)},domManip:function(J,M,L){if(this[0]){var I=(this[0].ownerDocument||this[0]).createDocumentFragment(),F=o.clean(J,(this[0].ownerDocument||this[0]),I),H=I.firstChild;if(H){for(var G=0,E=this.length;G1||G>0?I.cloneNode(true):I)}}if(F){o.each(F,z)}}return this;function K(N,O){return M&&o.nodeName(N,"table")&&o.nodeName(O,"tr")?(N.getElementsByTagName("tbody")[0]||N.appendChild(N.ownerDocument.createElement("tbody"))):N}}};o.fn.init.prototype=o.fn;function z(E,F){if(F.src){o.ajax({url:F.src,async:false,dataType:"script"})}else{o.globalEval(F.text||F.textContent||F.innerHTML||"")}if(F.parentNode){F.parentNode.removeChild(F)}}function e(){return +new Date}o.extend=o.fn.extend=function(){var J=arguments[0]||{},H=1,I=arguments.length,E=false,G;if(typeof J==="boolean"){E=J;J=arguments[1]||{};H=2}if(typeof J!=="object"&&!o.isFunction(J)){J={}}if(I==H){J=this;--H}for(;H-1}},swap:function(H,G,I){var E={};for(var F in G){E[F]=H.style[F];H.style[F]=G[F]}I.call(H);for(var F in G){H.style[F]=E[F]}},css:function(H,F,J,E){if(F=="width"||F=="height"){var L,G={position:"absolute",visibility:"hidden",display:"block"},K=F=="width"?["Left","Right"]:["Top","Bottom"];function I(){L=F=="width"?H.offsetWidth:H.offsetHeight;if(E==="border"){return}o.each(K,function(){if(!E){L-=parseFloat(o.curCSS(H,"padding"+this,true))||0}if(E==="margin"){L+=parseFloat(o.curCSS(H,"margin"+this,true))||0}else{L-=parseFloat(o.curCSS(H,"border"+this+"Width",true))||0}})}if(H.offsetWidth!==0){I()}else{o.swap(H,G,I)}return Math.max(0,Math.round(L))}return o.curCSS(H,F,J)},curCSS:function(I,F,G){var L,E=I.style;if(F=="opacity"&&!o.support.opacity){L=o.attr(E,"opacity");return L==""?"1":L}if(F.match(/float/i)){F=w}if(!G&&E&&E[F]){L=E[F]}else{if(q.getComputedStyle){if(F.match(/float/i)){F="float"}F=F.replace(/([A-Z])/g,"-$1").toLowerCase();var M=q.getComputedStyle(I,null);if(M){L=M.getPropertyValue(F)}if(F=="opacity"&&L==""){L="1"}}else{if(I.currentStyle){var J=F.replace(/\-(\w)/g,function(N,O){return O.toUpperCase()});L=I.currentStyle[F]||I.currentStyle[J];if(!/^\d+(px)?$/i.test(L)&&/^\d/.test(L)){var H=E.left,K=I.runtimeStyle.left;I.runtimeStyle.left=I.currentStyle.left;E.left=L||0;L=E.pixelLeft+"px";E.left=H;I.runtimeStyle.left=K}}}}return L},clean:function(F,K,I){K=K||document;if(typeof K.createElement==="undefined"){K=K.ownerDocument||K[0]&&K[0].ownerDocument||document}if(!I&&F.length===1&&typeof F[0]==="string"){var H=/^<(\w+)\s*\/?>$/.exec(F[0]);if(H){return[K.createElement(H[1])]}}var G=[],E=[],L=K.createElement("div");o.each(F,function(P,S){if(typeof S==="number"){S+=""}if(!S){return}if(typeof S==="string"){S=S.replace(/(<(\w+)[^>]*?)\/>/g,function(U,V,T){return T.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?U:V+">"});var O=S.replace(/^\s+/,"").substring(0,10).toLowerCase();var Q=!O.indexOf("",""]||!O.indexOf("",""]||O.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"","
"]||!O.indexOf("",""]||(!O.indexOf("",""]||!O.indexOf("",""]||!o.support.htmlSerialize&&[1,"div
","
"]||[0,"",""];L.innerHTML=Q[1]+S+Q[2];while(Q[0]--){L=L.lastChild}if(!o.support.tbody){var R=/"&&!R?L.childNodes:[];for(var M=N.length-1;M>=0;--M){if(o.nodeName(N[M],"tbody")&&!N[M].childNodes.length){N[M].parentNode.removeChild(N[M])}}}if(!o.support.leadingWhitespace&&/^\s/.test(S)){L.insertBefore(K.createTextNode(S.match(/^\s*/)[0]),L.firstChild)}S=o.makeArray(L.childNodes)}if(S.nodeType){G.push(S)}else{G=o.merge(G,S)}});if(I){for(var J=0;G[J];J++){if(o.nodeName(G[J],"script")&&(!G[J].type||G[J].type.toLowerCase()==="text/javascript")){E.push(G[J].parentNode?G[J].parentNode.removeChild(G[J]):G[J])}else{if(G[J].nodeType===1){G.splice.apply(G,[J+1,0].concat(o.makeArray(G[J].getElementsByTagName("script"))))}I.appendChild(G[J])}}return E}return G},attr:function(J,G,K){if(!J||J.nodeType==3||J.nodeType==8){return g}var H=!o.isXMLDoc(J),L=K!==g;G=H&&o.props[G]||G;if(J.tagName){var F=/href|src|style/.test(G);if(G=="selected"&&J.parentNode){J.parentNode.selectedIndex}if(G in J&&H&&!F){if(L){if(G=="type"&&o.nodeName(J,"input")&&J.parentNode){throw"type property can't be changed"}J[G]=K}if(o.nodeName(J,"form")&&J.getAttributeNode(G)){return J.getAttributeNode(G).nodeValue}if(G=="tabIndex"){var I=J.getAttributeNode("tabIndex");return I&&I.specified?I.value:J.nodeName.match(/(button|input|object|select|textarea)/i)?0:J.nodeName.match(/^(a|area)$/i)&&J.href?0:g}return J[G]}if(!o.support.style&&H&&G=="style"){return o.attr(J.style,"cssText",K)}if(L){J.setAttribute(G,""+K)}var E=!o.support.hrefNormalized&&H&&F?J.getAttribute(G,2):J.getAttribute(G);return E===null?g:E}if(!o.support.opacity&&G=="opacity"){if(L){J.zoom=1;J.filter=(J.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(K)+""=="NaN"?"":"alpha(opacity="+K*100+")")}return J.filter&&J.filter.indexOf("opacity=")>=0?(parseFloat(J.filter.match(/opacity=([^)]*)/)[1])/100)+"":""}G=G.replace(/-([a-z])/ig,function(M,N){return N.toUpperCase()});if(L){J[G]=K}return J[G]},trim:function(E){return(E||"").replace(/^\s+|\s+$/g,"")},makeArray:function(G){var E=[];if(G!=null){var F=G.length;if(F==null||typeof G==="string"||o.isFunction(G)||G.setInterval){E[0]=G}else{while(F){E[--F]=G[F]}}}return E},inArray:function(G,H){for(var E=0,F=H.length;E0?this.clone(true):this).get();o.fn[F].apply(o(L[K]),I);J=J.concat(I)}return this.pushStack(J,E,G)}});o.each({removeAttr:function(E){o.attr(this,E,"");if(this.nodeType==1){this.removeAttribute(E)}},addClass:function(E){o.className.add(this,E)},removeClass:function(E){o.className.remove(this,E)},toggleClass:function(F,E){if(typeof E!=="boolean"){E=!o.className.has(this,F)}o.className[E?"add":"remove"](this,F)},remove:function(E){if(!E||o.filter(E,[this]).length){o("*",this).add([this]).each(function(){o.event.remove(this);o.removeData(this)});if(this.parentNode){this.parentNode.removeChild(this)}}},empty:function(){o(this).children().remove();while(this.firstChild){this.removeChild(this.firstChild)}}},function(E,F){o.fn[E]=function(){return this.each(F,arguments)}});function j(E,F){return E[0]&&parseInt(o.curCSS(E[0],F,true),10)||0}var h="jQuery"+e(),v=0,A={};o.extend({cache:{},data:function(F,E,G){F=F==l?A:F;var H=F[h];if(!H){H=F[h]=++v}if(E&&!o.cache[H]){o.cache[H]={}}if(G!==g){o.cache[H][E]=G}return E?o.cache[H][E]:H},removeData:function(F,E){F=F==l?A:F;var H=F[h];if(E){if(o.cache[H]){delete o.cache[H][E];E="";for(E in o.cache[H]){break}if(!E){o.removeData(F)}}}else{try{delete F[h]}catch(G){if(F.removeAttribute){F.removeAttribute(h)}}delete o.cache[H]}},queue:function(F,E,H){if(F){E=(E||"fx")+"queue";var G=o.data(F,E);if(!G||o.isArray(H)){G=o.data(F,E,o.makeArray(H))}else{if(H){G.push(H)}}}return G},dequeue:function(H,G){var E=o.queue(H,G),F=E.shift();if(!G||G==="fx"){F=E[0]}if(F!==g){F.call(H)}}});o.fn.extend({data:function(E,G){var H=E.split(".");H[1]=H[1]?"."+H[1]:"";if(G===g){var F=this.triggerHandler("getData"+H[1]+"!",[H[0]]);if(F===g&&this.length){F=o.data(this[0],E)}return F===g&&H[1]?this.data(H[0]):F}else{return this.trigger("setData"+H[1]+"!",[H[0],G]).each(function(){o.data(this,E,G)})}},removeData:function(E){return this.each(function(){o.removeData(this,E)})},queue:function(E,F){if(typeof E!=="string"){F=E;E="fx"}if(F===g){return o.queue(this[0],E)}return this.each(function(){var G=o.queue(this,E,F);if(E=="fx"&&G.length==1){G[0].call(this)}})},dequeue:function(E){return this.each(function(){o.dequeue(this,E)})}}); /* * Sizzle CSS Selector Engine - v0.9.3 * Copyright 2009, The Dojo Foundation * Released under the MIT, BSD, and GPL Licenses. * More information: http://sizzlejs.com/ */ (function(){var R=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,L=0,H=Object.prototype.toString;var F=function(Y,U,ab,ac){ab=ab||[];U=U||document;if(U.nodeType!==1&&U.nodeType!==9){return[]}if(!Y||typeof Y!=="string"){return ab}var Z=[],W,af,ai,T,ad,V,X=true;R.lastIndex=0;while((W=R.exec(Y))!==null){Z.push(W[1]);if(W[2]){V=RegExp.rightContext;break}}if(Z.length>1&&M.exec(Y)){if(Z.length===2&&I.relative[Z[0]]){af=J(Z[0]+Z[1],U)}else{af=I.relative[Z[0]]?[U]:F(Z.shift(),U);while(Z.length){Y=Z.shift();if(I.relative[Y]){Y+=Z.shift()}af=J(Y,af)}}}else{var ae=ac?{expr:Z.pop(),set:E(ac)}:F.find(Z.pop(),Z.length===1&&U.parentNode?U.parentNode:U,Q(U));af=F.filter(ae.expr,ae.set);if(Z.length>0){ai=E(af)}else{X=false}while(Z.length){var ah=Z.pop(),ag=ah;if(!I.relative[ah]){ah=""}else{ag=Z.pop()}if(ag==null){ag=U}I.relative[ah](ai,ag,Q(U))}}if(!ai){ai=af}if(!ai){throw"Syntax error, unrecognized expression: "+(ah||Y)}if(H.call(ai)==="[object Array]"){if(!X){ab.push.apply(ab,ai)}else{if(U.nodeType===1){for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&(ai[aa]===true||ai[aa].nodeType===1&&K(U,ai[aa]))){ab.push(af[aa])}}}else{for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&ai[aa].nodeType===1){ab.push(af[aa])}}}}}else{E(ai,ab)}if(V){F(V,U,ab,ac);if(G){hasDuplicate=false;ab.sort(G);if(hasDuplicate){for(var aa=1;aa":function(Z,U,aa){var X=typeof U==="string";if(X&&!/\W/.test(U)){U=aa?U:U.toUpperCase();for(var V=0,T=Z.length;V=0)){if(!V){T.push(Y)}}else{if(V){U[X]=false}}}}return false},ID:function(T){return T[1].replace(/\\/g,"")},TAG:function(U,T){for(var V=0;T[V]===false;V++){}return T[V]&&Q(T[V])?U[1]:U[1].toUpperCase()},CHILD:function(T){if(T[1]=="nth"){var U=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(T[2]=="even"&&"2n"||T[2]=="odd"&&"2n+1"||!/\D/.test(T[2])&&"0n+"+T[2]||T[2]);T[2]=(U[1]+(U[2]||1))-0;T[3]=U[3]-0}T[0]=L++;return T},ATTR:function(X,U,V,T,Y,Z){var W=X[1].replace(/\\/g,"");if(!Z&&I.attrMap[W]){X[1]=I.attrMap[W]}if(X[2]==="~="){X[4]=" "+X[4]+" "}return X},PSEUDO:function(X,U,V,T,Y){if(X[1]==="not"){if(X[3].match(R).length>1||/^\w/.test(X[3])){X[3]=F(X[3],null,null,U)}else{var W=F.filter(X[3],U,V,true^Y);if(!V){T.push.apply(T,W)}return false}}else{if(I.match.POS.test(X[0])||I.match.CHILD.test(X[0])){return true}}return X},POS:function(T){T.unshift(true);return T}},filters:{enabled:function(T){return T.disabled===false&&T.type!=="hidden"},disabled:function(T){return T.disabled===true},checked:function(T){return T.checked===true},selected:function(T){T.parentNode.selectedIndex;return T.selected===true},parent:function(T){return !!T.firstChild},empty:function(T){return !T.firstChild},has:function(V,U,T){return !!F(T[3],V).length},header:function(T){return/h\d/i.test(T.nodeName)},text:function(T){return"text"===T.type},radio:function(T){return"radio"===T.type},checkbox:function(T){return"checkbox"===T.type},file:function(T){return"file"===T.type},password:function(T){return"password"===T.type},submit:function(T){return"submit"===T.type},image:function(T){return"image"===T.type},reset:function(T){return"reset"===T.type},button:function(T){return"button"===T.type||T.nodeName.toUpperCase()==="BUTTON"},input:function(T){return/input|select|textarea|button/i.test(T.nodeName)}},setFilters:{first:function(U,T){return T===0},last:function(V,U,T,W){return U===W.length-1},even:function(U,T){return T%2===0},odd:function(U,T){return T%2===1},lt:function(V,U,T){return UT[3]-0},nth:function(V,U,T){return T[3]-0==U},eq:function(V,U,T){return T[3]-0==U}},filter:{PSEUDO:function(Z,V,W,aa){var U=V[1],X=I.filters[U];if(X){return X(Z,W,V,aa)}else{if(U==="contains"){return(Z.textContent||Z.innerText||"").indexOf(V[3])>=0}else{if(U==="not"){var Y=V[3];for(var W=0,T=Y.length;W=0)}}},ID:function(U,T){return U.nodeType===1&&U.getAttribute("id")===T},TAG:function(U,T){return(T==="*"&&U.nodeType===1)||U.nodeName===T},CLASS:function(U,T){return(" "+(U.className||U.getAttribute("class"))+" ").indexOf(T)>-1},ATTR:function(Y,W){var V=W[1],T=I.attrHandle[V]?I.attrHandle[V](Y):Y[V]!=null?Y[V]:Y.getAttribute(V),Z=T+"",X=W[2],U=W[4];return T==null?X==="!=":X==="="?Z===U:X==="*="?Z.indexOf(U)>=0:X==="~="?(" "+Z+" ").indexOf(U)>=0:!U?Z&&T!==false:X==="!="?Z!=U:X==="^="?Z.indexOf(U)===0:X==="$="?Z.substr(Z.length-U.length)===U:X==="|="?Z===U||Z.substr(0,U.length+1)===U+"-":false},POS:function(X,U,V,Y){var T=U[2],W=I.setFilters[T];if(W){return W(X,V,U,Y)}}}};var M=I.match.POS;for(var O in I.match){I.match[O]=RegExp(I.match[O].source+/(?![^\[]*\])(?![^\(]*\))/.source)}var E=function(U,T){U=Array.prototype.slice.call(U);if(T){T.push.apply(T,U);return T}return U};try{Array.prototype.slice.call(document.documentElement.childNodes)}catch(N){E=function(X,W){var U=W||[];if(H.call(X)==="[object Array]"){Array.prototype.push.apply(U,X)}else{if(typeof X.length==="number"){for(var V=0,T=X.length;V";var T=document.documentElement;T.insertBefore(U,T.firstChild);if(!!document.getElementById(V)){I.find.ID=function(X,Y,Z){if(typeof Y.getElementById!=="undefined"&&!Z){var W=Y.getElementById(X[1]);return W?W.id===X[1]||typeof W.getAttributeNode!=="undefined"&&W.getAttributeNode("id").nodeValue===X[1]?[W]:g:[]}};I.filter.ID=function(Y,W){var X=typeof Y.getAttributeNode!=="undefined"&&Y.getAttributeNode("id");return Y.nodeType===1&&X&&X.nodeValue===W}}T.removeChild(U)})();(function(){var T=document.createElement("div");T.appendChild(document.createComment(""));if(T.getElementsByTagName("*").length>0){I.find.TAG=function(U,Y){var X=Y.getElementsByTagName(U[1]);if(U[1]==="*"){var W=[];for(var V=0;X[V];V++){if(X[V].nodeType===1){W.push(X[V])}}X=W}return X}}T.innerHTML="";if(T.firstChild&&typeof T.firstChild.getAttribute!=="undefined"&&T.firstChild.getAttribute("href")!=="#"){I.attrHandle.href=function(U){return U.getAttribute("href",2)}}})();if(document.querySelectorAll){(function(){var T=F,U=document.createElement("div");U.innerHTML="

";if(U.querySelectorAll&&U.querySelectorAll(".TEST").length===0){return}F=function(Y,X,V,W){X=X||document;if(!W&&X.nodeType===9&&!Q(X)){try{return E(X.querySelectorAll(Y),V)}catch(Z){}}return T(Y,X,V,W)};F.find=T.find;F.filter=T.filter;F.selectors=T.selectors;F.matches=T.matches})()}if(document.getElementsByClassName&&document.documentElement.getElementsByClassName){(function(){var T=document.createElement("div");T.innerHTML="
";if(T.getElementsByClassName("e").length===0){return}T.lastChild.className="e";if(T.getElementsByClassName("e").length===1){return}I.order.splice(1,0,"CLASS");I.find.CLASS=function(U,V,W){if(typeof V.getElementsByClassName!=="undefined"&&!W){return V.getElementsByClassName(U[1])}}})()}function P(U,Z,Y,ad,aa,ac){var ab=U=="previousSibling"&&!ac;for(var W=0,V=ad.length;W0){X=T;break}}}T=T[U]}ad[W]=X}}}var K=document.compareDocumentPosition?function(U,T){return U.compareDocumentPosition(T)&16}:function(U,T){return U!==T&&(U.contains?U.contains(T):true)};var Q=function(T){return T.nodeType===9&&T.documentElement.nodeName!=="HTML"||!!T.ownerDocument&&Q(T.ownerDocument)};var J=function(T,aa){var W=[],X="",Y,V=aa.nodeType?[aa]:aa;while((Y=I.match.PSEUDO.exec(T))){X+=Y[0];T=T.replace(I.match.PSEUDO,"")}T=I.relative[T]?T+"*":T;for(var Z=0,U=V.length;Z0||T.offsetHeight>0};F.selectors.filters.animated=function(T){return o.grep(o.timers,function(U){return T===U.elem}).length};o.multiFilter=function(V,T,U){if(U){V=":not("+V+")"}return F.matches(V,T)};o.dir=function(V,U){var T=[],W=V[U];while(W&&W!=document){if(W.nodeType==1){T.push(W)}W=W[U]}return T};o.nth=function(X,T,V,W){T=T||1;var U=0;for(;X;X=X[V]){if(X.nodeType==1&&++U==T){break}}return X};o.sibling=function(V,U){var T=[];for(;V;V=V.nextSibling){if(V.nodeType==1&&V!=U){T.push(V)}}return T};return;l.Sizzle=F})();o.event={add:function(I,F,H,K){if(I.nodeType==3||I.nodeType==8){return}if(I.setInterval&&I!=l){I=l}if(!H.guid){H.guid=this.guid++}if(K!==g){var G=H;H=this.proxy(G);H.data=K}var E=o.data(I,"events")||o.data(I,"events",{}),J=o.data(I,"handle")||o.data(I,"handle",function(){return typeof o!=="undefined"&&!o.event.triggered?o.event.handle.apply(arguments.callee.elem,arguments):g});J.elem=I;o.each(F.split(/\s+/),function(M,N){var O=N.split(".");N=O.shift();H.type=O.slice().sort().join(".");var L=E[N];if(o.event.specialAll[N]){o.event.specialAll[N].setup.call(I,K,O)}if(!L){L=E[N]={};if(!o.event.special[N]||o.event.special[N].setup.call(I,K,O)===false){if(I.addEventListener){I.addEventListener(N,J,false)}else{if(I.attachEvent){I.attachEvent("on"+N,J)}}}}L[H.guid]=H;o.event.global[N]=true});I=null},guid:1,global:{},remove:function(K,H,J){if(K.nodeType==3||K.nodeType==8){return}var G=o.data(K,"events"),F,E;if(G){if(H===g||(typeof H==="string"&&H.charAt(0)==".")){for(var I in G){this.remove(K,I+(H||""))}}else{if(H.type){J=H.handler;H=H.type}o.each(H.split(/\s+/),function(M,O){var Q=O.split(".");O=Q.shift();var N=RegExp("(^|\\.)"+Q.slice().sort().join(".*\\.")+"(\\.|$)");if(G[O]){if(J){delete G[O][J.guid]}else{for(var P in G[O]){if(N.test(G[O][P].type)){delete G[O][P]}}}if(o.event.specialAll[O]){o.event.specialAll[O].teardown.call(K,Q)}for(F in G[O]){break}if(!F){if(!o.event.special[O]||o.event.special[O].teardown.call(K,Q)===false){if(K.removeEventListener){K.removeEventListener(O,o.data(K,"handle"),false)}else{if(K.detachEvent){K.detachEvent("on"+O,o.data(K,"handle"))}}}F=null;delete G[O]}}})}for(F in G){break}if(!F){var L=o.data(K,"handle");if(L){L.elem=null}o.removeData(K,"events");o.removeData(K,"handle")}}},trigger:function(I,K,H,E){var G=I.type||I;if(!E){I=typeof I==="object"?I[h]?I:o.extend(o.Event(G),I):o.Event(G);if(G.indexOf("!")>=0){I.type=G=G.slice(0,-1);I.exclusive=true}if(!H){I.stopPropagation();if(this.global[G]){o.each(o.cache,function(){if(this.events&&this.events[G]){o.event.trigger(I,K,this.handle.elem)}})}}if(!H||H.nodeType==3||H.nodeType==8){return g}I.result=g;I.target=H;K=o.makeArray(K);K.unshift(I)}I.currentTarget=H;var J=o.data(H,"handle");if(J){J.apply(H,K)}if((!H[G]||(o.nodeName(H,"a")&&G=="click"))&&H["on"+G]&&H["on"+G].apply(H,K)===false){I.result=false}if(!E&&H[G]&&!I.isDefaultPrevented()&&!(o.nodeName(H,"a")&&G=="click")){this.triggered=true;try{H[G]()}catch(L){}}this.triggered=false;if(!I.isPropagationStopped()){var F=H.parentNode||H.ownerDocument;if(F){o.event.trigger(I,K,F,true)}}},handle:function(K){var J,E;K=arguments[0]=o.event.fix(K||l.event);K.currentTarget=this;var L=K.type.split(".");K.type=L.shift();J=!L.length&&!K.exclusive;var I=RegExp("(^|\\.)"+L.slice().sort().join(".*\\.")+"(\\.|$)");E=(o.data(this,"events")||{})[K.type];for(var G in E){var H=E[G];if(J||I.test(H.type)){K.handler=H;K.data=H.data;var F=H.apply(this,arguments);if(F!==g){K.result=F;if(F===false){K.preventDefault();K.stopPropagation()}}if(K.isImmediatePropagationStopped()){break}}}},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(H){if(H[h]){return H}var F=H;H=o.Event(F);for(var G=this.props.length,J;G;){J=this.props[--G];H[J]=F[J]}if(!H.target){H.target=H.srcElement||document}if(H.target.nodeType==3){H.target=H.target.parentNode}if(!H.relatedTarget&&H.fromElement){H.relatedTarget=H.fromElement==H.target?H.toElement:H.fromElement}if(H.pageX==null&&H.clientX!=null){var I=document.documentElement,E=document.body;H.pageX=H.clientX+(I&&I.scrollLeft||E&&E.scrollLeft||0)-(I.clientLeft||0);H.pageY=H.clientY+(I&&I.scrollTop||E&&E.scrollTop||0)-(I.clientTop||0)}if(!H.which&&((H.charCode||H.charCode===0)?H.charCode:H.keyCode)){H.which=H.charCode||H.keyCode}if(!H.metaKey&&H.ctrlKey){H.metaKey=H.ctrlKey}if(!H.which&&H.button){H.which=(H.button&1?1:(H.button&2?3:(H.button&4?2:0)))}return H},proxy:function(F,E){E=E||function(){return F.apply(this,arguments)};E.guid=F.guid=F.guid||E.guid||this.guid++;return E},special:{ready:{setup:B,teardown:function(){}}},specialAll:{live:{setup:function(E,F){o.event.add(this,F[0],c)},teardown:function(G){if(G.length){var E=0,F=RegExp("(^|\\.)"+G[0]+"(\\.|$)");o.each((o.data(this,"events").live||{}),function(){if(F.test(this.type)){E++}});if(E<1){o.event.remove(this,G[0],c)}}}}}};o.Event=function(E){if(!this.preventDefault){return new o.Event(E)}if(E&&E.type){this.originalEvent=E;this.type=E.type}else{this.type=E}this.timeStamp=e();this[h]=true};function k(){return false}function u(){return true}o.Event.prototype={preventDefault:function(){this.isDefaultPrevented=u;var E=this.originalEvent;if(!E){return}if(E.preventDefault){E.preventDefault()}E.returnValue=false},stopPropagation:function(){this.isPropagationStopped=u;var E=this.originalEvent;if(!E){return}if(E.stopPropagation){E.stopPropagation()}E.cancelBubble=true},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=u;this.stopPropagation()},isDefaultPrevented:k,isPropagationStopped:k,isImmediatePropagationStopped:k};var a=function(F){var E=F.relatedTarget;while(E&&E!=this){try{E=E.parentNode}catch(G){E=this}}if(E!=this){F.type=F.data;o.event.handle.apply(this,arguments)}};o.each({mouseover:"mouseenter",mouseout:"mouseleave"},function(F,E){o.event.special[E]={setup:function(){o.event.add(this,F,a,E)},teardown:function(){o.event.remove(this,F,a)}}});o.fn.extend({bind:function(F,G,E){return F=="unload"?this.one(F,G,E):this.each(function(){o.event.add(this,F,E||G,E&&G)})},one:function(G,H,F){var E=o.event.proxy(F||H,function(I){o(this).unbind(I,E);return(F||H).apply(this,arguments)});return this.each(function(){o.event.add(this,G,E,F&&H)})},unbind:function(F,E){return this.each(function(){o.event.remove(this,F,E)})},trigger:function(E,F){return this.each(function(){o.event.trigger(E,F,this)})},triggerHandler:function(E,G){if(this[0]){var F=o.Event(E);F.preventDefault();F.stopPropagation();o.event.trigger(F,G,this[0]);return F.result}},toggle:function(G){var E=arguments,F=1;while(F=0){var E=G.slice(I,G.length);G=G.slice(0,I)}var H="GET";if(J){if(o.isFunction(J)){K=J;J=null}else{if(typeof J==="object"){J=o.param(J);H="POST"}}}var F=this;o.ajax({url:G,type:H,dataType:"html",data:J,complete:function(M,L){if(L=="success"||L=="notmodified"){F.html(E?o("
").append(M.responseText.replace(//g,"")).find(E):M.responseText)}if(K){F.each(K,[M.responseText,L,M])}}});return this},serialize:function(){return o.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?o.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password|search/i.test(this.type))}).map(function(E,F){var G=o(this).val();return G==null?null:o.isArray(G)?o.map(G,function(I,H){return{name:F.name,value:I}}):{name:F.name,value:G}}).get()}});o.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(E,F){o.fn[F]=function(G){return this.bind(F,G)}});var r=e();o.extend({get:function(E,G,H,F){if(o.isFunction(G)){H=G;G=null}return o.ajax({type:"GET",url:E,data:G,success:H,dataType:F})},getScript:function(E,F){return o.get(E,null,F,"script")},getJSON:function(E,F,G){return o.get(E,F,G,"json")},post:function(E,G,H,F){if(o.isFunction(G)){H=G;G={}}return o.ajax({type:"POST",url:E,data:G,success:H,dataType:F})},ajaxSetup:function(E){o.extend(o.ajaxSettings,E)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return l.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest()},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(M){M=o.extend(true,M,o.extend(true,{},o.ajaxSettings,M));var W,F=/=\?(&|$)/g,R,V,G=M.type.toUpperCase();if(M.data&&M.processData&&typeof M.data!=="string"){M.data=o.param(M.data)}if(M.dataType=="jsonp"){if(G=="GET"){if(!M.url.match(F)){M.url+=(M.url.match(/\?/)?"&":"?")+(M.jsonp||"callback")+"=?"}}else{if(!M.data||!M.data.match(F)){M.data=(M.data?M.data+"&":"")+(M.jsonp||"callback")+"=?"}}M.dataType="json"}if(M.dataType=="json"&&(M.data&&M.data.match(F)||M.url.match(F))){W="jsonp"+r++;if(M.data){M.data=(M.data+"").replace(F,"="+W+"$1")}M.url=M.url.replace(F,"="+W+"$1");M.dataType="script";l[W]=function(X){V=X;I();L();l[W]=g;try{delete l[W]}catch(Y){}if(H){H.removeChild(T)}}}if(M.dataType=="script"&&M.cache==null){M.cache=false}if(M.cache===false&&G=="GET"){var E=e();var U=M.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+E+"$2");M.url=U+((U==M.url)?(M.url.match(/\?/)?"&":"?")+"_="+E:"")}if(M.data&&G=="GET"){M.url+=(M.url.match(/\?/)?"&":"?")+M.data;M.data=null}if(M.global&&!o.active++){o.event.trigger("ajaxStart")}var Q=/^(\w+:)?\/\/([^\/?#]+)/.exec(M.url);if(M.dataType=="script"&&G=="GET"&&Q&&(Q[1]&&Q[1]!=location.protocol||Q[2]!=location.host)){var H=document.getElementsByTagName("head")[0];var T=document.createElement("script");T.src=M.url;if(M.scriptCharset){T.charset=M.scriptCharset}if(!W){var O=false;T.onload=T.onreadystatechange=function(){if(!O&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){O=true;I();L();T.onload=T.onreadystatechange=null;H.removeChild(T)}}}H.appendChild(T);return g}var K=false;var J=M.xhr();if(M.username){J.open(G,M.url,M.async,M.username,M.password)}else{J.open(G,M.url,M.async)}try{if(M.data){J.setRequestHeader("Content-Type",M.contentType)}if(M.ifModified){J.setRequestHeader("If-Modified-Since",o.lastModified[M.url]||"Thu, 01 Jan 1970 00:00:00 GMT")}J.setRequestHeader("X-Requested-With","XMLHttpRequest");J.setRequestHeader("Accept",M.dataType&&M.accepts[M.dataType]?M.accepts[M.dataType]+", */*":M.accepts._default)}catch(S){}if(M.beforeSend&&M.beforeSend(J,M)===false){if(M.global&&!--o.active){o.event.trigger("ajaxStop")}J.abort();return false}if(M.global){o.event.trigger("ajaxSend",[J,M])}var N=function(X){if(J.readyState==0){if(P){clearInterval(P);P=null;if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}}else{if(!K&&J&&(J.readyState==4||X=="timeout")){K=true;if(P){clearInterval(P);P=null}R=X=="timeout"?"timeout":!o.httpSuccess(J)?"error":M.ifModified&&o.httpNotModified(J,M.url)?"notmodified":"success";if(R=="success"){try{V=o.httpData(J,M.dataType,M)}catch(Z){R="parsererror"}}if(R=="success"){var Y;try{Y=J.getResponseHeader("Last-Modified")}catch(Z){}if(M.ifModified&&Y){o.lastModified[M.url]=Y}if(!W){I()}}else{o.handleError(M,J,R)}L();if(X){J.abort()}if(M.async){J=null}}}};if(M.async){var P=setInterval(N,13);if(M.timeout>0){setTimeout(function(){if(J&&!K){N("timeout")}},M.timeout)}}try{J.send(M.data)}catch(S){o.handleError(M,J,null,S)}if(!M.async){N()}function I(){if(M.success){M.success(V,R)}if(M.global){o.event.trigger("ajaxSuccess",[J,M])}}function L(){if(M.complete){M.complete(J,R)}if(M.global){o.event.trigger("ajaxComplete",[J,M])}if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}return J},handleError:function(F,H,E,G){if(F.error){F.error(H,E,G)}if(F.global){o.event.trigger("ajaxError",[H,F,G])}},active:0,httpSuccess:function(F){try{return !F.status&&location.protocol=="file:"||(F.status>=200&&F.status<300)||F.status==304||F.status==1223}catch(E){}return false},httpNotModified:function(G,E){try{var H=G.getResponseHeader("Last-Modified");return G.status==304||H==o.lastModified[E]}catch(F){}return false},httpData:function(J,H,G){var F=J.getResponseHeader("content-type"),E=H=="xml"||!H&&F&&F.indexOf("xml")>=0,I=E?J.responseXML:J.responseText;if(E&&I.documentElement.tagName=="parsererror"){throw"parsererror"}if(G&&G.dataFilter){I=G.dataFilter(I,H)}if(typeof I==="string"){if(H=="script"){o.globalEval(I)}if(H=="json"){I=l["eval"]("("+I+")")}}return I},param:function(E){var G=[];function H(I,J){G[G.length]=encodeURIComponent(I)+"="+encodeURIComponent(J)}if(o.isArray(E)||E.jquery){o.each(E,function(){H(this.name,this.value)})}else{for(var F in E){if(o.isArray(E[F])){o.each(E[F],function(){H(F,this)})}else{H(F,o.isFunction(E[F])?E[F]():E[F])}}}return G.join("&").replace(/%20/g,"+")}});var m={},n,d=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];function t(F,E){var G={};o.each(d.concat.apply([],d.slice(0,E)),function(){G[this]=F});return G}o.fn.extend({show:function(J,L){if(J){return this.animate(t("show",3),J,L)}else{for(var H=0,F=this.length;H").appendTo("body");K=I.css("display");if(K==="none"){K="block"}I.remove();m[G]=K}o.data(this[H],"olddisplay",K)}}for(var H=0,F=this.length;H=0;H--){if(G[H].elem==this){if(E){G[H](true)}G.splice(H,1)}}});if(!E){this.dequeue()}return this}});o.each({slideDown:t("show",1),slideUp:t("hide",1),slideToggle:t("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(E,F){o.fn[E]=function(G,H){return this.animate(F,G,H)}});o.extend({speed:function(G,H,F){var E=typeof G==="object"?G:{complete:F||!F&&H||o.isFunction(G)&&G,duration:G,easing:F&&H||H&&!o.isFunction(H)&&H};E.duration=o.fx.off?0:typeof E.duration==="number"?E.duration:o.fx.speeds[E.duration]||o.fx.speeds._default;E.old=E.complete;E.complete=function(){if(E.queue!==false){o(this).dequeue()}if(o.isFunction(E.old)){E.old.call(this)}};return E},easing:{linear:function(G,H,E,F){return E+F*G},swing:function(G,H,E,F){return((-Math.cos(G*Math.PI)/2)+0.5)*F+E}},timers:[],fx:function(F,E,G){this.options=E;this.elem=F;this.prop=G;if(!E.orig){E.orig={}}}});o.fx.prototype={update:function(){if(this.options.step){this.options.step.call(this.elem,this.now,this)}(o.fx.step[this.prop]||o.fx.step._default)(this);if((this.prop=="height"||this.prop=="width")&&this.elem.style){this.elem.style.display="block"}},cur:function(F){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null)){return this.elem[this.prop]}var E=parseFloat(o.css(this.elem,this.prop,F));return E&&E>-10000?E:parseFloat(o.curCSS(this.elem,this.prop))||0},custom:function(I,H,G){this.startTime=e();this.start=I;this.end=H;this.unit=G||this.unit||"px";this.now=this.start;this.pos=this.state=0;var E=this;function F(J){return E.step(J)}F.elem=this.elem;if(F()&&o.timers.push(F)&&!n){n=setInterval(function(){var K=o.timers;for(var J=0;J=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var E=true;for(var F in this.options.curAnim){if(this.options.curAnim[F]!==true){E=false}}if(E){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(o.css(this.elem,"display")=="none"){this.elem.style.display="block"}}if(this.options.hide){o(this.elem).hide()}if(this.options.hide||this.options.show){for(var I in this.options.curAnim){o.attr(this.elem.style,I,this.options.orig[I])}}this.options.complete.call(this.elem)}return false}else{var J=G-this.startTime;this.state=J/this.options.duration;this.pos=o.easing[this.options.easing||(o.easing.swing?"swing":"linear")](this.state,J,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update()}return true}};o.extend(o.fx,{speeds:{slow:600,fast:200,_default:400},step:{opacity:function(E){o.attr(E.elem.style,"opacity",E.now)},_default:function(E){if(E.elem.style&&E.elem.style[E.prop]!=null){E.elem.style[E.prop]=E.now+E.unit}else{E.elem[E.prop]=E.now}}}});if(document.documentElement.getBoundingClientRect){o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}var G=this[0].getBoundingClientRect(),J=this[0].ownerDocument,F=J.body,E=J.documentElement,L=E.clientTop||F.clientTop||0,K=E.clientLeft||F.clientLeft||0,I=G.top+(self.pageYOffset||o.boxModel&&E.scrollTop||F.scrollTop)-L,H=G.left+(self.pageXOffset||o.boxModel&&E.scrollLeft||F.scrollLeft)-K;return{top:I,left:H}}}else{o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}o.offset.initialized||o.offset.initialize();var J=this[0],G=J.offsetParent,F=J,O=J.ownerDocument,M,H=O.documentElement,K=O.body,L=O.defaultView,E=L.getComputedStyle(J,null),N=J.offsetTop,I=J.offsetLeft;while((J=J.parentNode)&&J!==K&&J!==H){M=L.getComputedStyle(J,null);N-=J.scrollTop,I-=J.scrollLeft;if(J===G){N+=J.offsetTop,I+=J.offsetLeft;if(o.offset.doesNotAddBorder&&!(o.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(J.tagName))){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}F=G,G=J.offsetParent}if(o.offset.subtractsBorderForOverflowNotVisible&&M.overflow!=="visible"){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}E=M}if(E.position==="relative"||E.position==="static"){N+=K.offsetTop,I+=K.offsetLeft}if(E.position==="fixed"){N+=Math.max(H.scrollTop,K.scrollTop),I+=Math.max(H.scrollLeft,K.scrollLeft)}return{top:N,left:I}}}o.offset={initialize:function(){if(this.initialized){return}var L=document.body,F=document.createElement("div"),H,G,N,I,M,E,J=L.style.marginTop,K='
';M={position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"};for(E in M){F.style[E]=M[E]}F.innerHTML=K;L.insertBefore(F,L.firstChild);H=F.firstChild,G=H.firstChild,I=H.nextSibling.firstChild.firstChild;this.doesNotAddBorder=(G.offsetTop!==5);this.doesAddBorderForTableAndCells=(I.offsetTop===5);H.style.overflow="hidden",H.style.position="relative";this.subtractsBorderForOverflowNotVisible=(G.offsetTop===-5);L.style.marginTop="1px";this.doesNotIncludeMarginInBodyOffset=(L.offsetTop===0);L.style.marginTop=J;L.removeChild(F);this.initialized=true},bodyOffset:function(E){o.offset.initialized||o.offset.initialize();var G=E.offsetTop,F=E.offsetLeft;if(o.offset.doesNotIncludeMarginInBodyOffset){G+=parseInt(o.curCSS(E,"marginTop",true),10)||0,F+=parseInt(o.curCSS(E,"marginLeft",true),10)||0}return{top:G,left:F}}};o.fn.extend({position:function(){var I=0,H=0,F;if(this[0]){var G=this.offsetParent(),J=this.offset(),E=/^body|html$/i.test(G[0].tagName)?{top:0,left:0}:G.offset();J.top-=j(this,"marginTop");J.left-=j(this,"marginLeft");E.top+=j(G,"borderTopWidth");E.left+=j(G,"borderLeftWidth");F={top:J.top-E.top,left:J.left-E.left}}return F},offsetParent:function(){var E=this[0].offsetParent||document.body;while(E&&(!/^body|html$/i.test(E.tagName)&&o.css(E,"position")=="static")){E=E.offsetParent}return o(E)}});o.each(["Left","Top"],function(F,E){var G="scroll"+E;o.fn[G]=function(H){if(!this[0]){return null}return H!==g?this.each(function(){this==l||this==document?l.scrollTo(!F?H:o(l).scrollLeft(),F?H:o(l).scrollTop()):this[G]=H}):this[0]==l||this[0]==document?self[F?"pageYOffset":"pageXOffset"]||o.boxModel&&document.documentElement[G]||document.body[G]:this[0][G]}});o.each(["Height","Width"],function(I,G){var E=I?"Left":"Top",H=I?"Right":"Bottom",F=G.toLowerCase();o.fn["inner"+G]=function(){return this[0]?o.css(this[0],F,false,"padding"):null};o.fn["outer"+G]=function(K){return this[0]?o.css(this[0],F,false,K?"margin":"border"):null};var J=G.toLowerCase();o.fn[J]=function(K){return this[0]==l?document.compatMode=="CSS1Compat"&&document.documentElement["client"+G]||document.body["client"+G]:this[0]==document?Math.max(document.documentElement["client"+G],document.body["scroll"+G],document.documentElement["scroll"+G],document.body["offset"+G],document.documentElement["offset"+G]):K===g?(this.length?o.css(this[0],J):null):this.css(J,typeof K==="string"?K:K+"px")}})})(); faye-1.4.0/examples/public/json2.js000066400000000000000000000437671371104377200171530ustar00rootroot00000000000000/* json2.js 2015-05-03 Public Domain. NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. See http://www.JSON.org/js.html This code should be minified before deployment. See http://javascript.crockford.com/jsmin.html USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO NOT CONTROL. This file creates a global JSON object containing two methods: stringify and parse. This file is provides the ES5 JSON capability to ES3 systems. If a project might run on IE8 or earlier, then this file should be included. This file does nothing on ES5 systems. JSON.stringify(value, replacer, space) value any JavaScript value, usually an object or array. replacer an optional parameter that determines how object values are stringified for objects. It can be a function or an array of strings. space an optional parameter that specifies the indentation of nested structures. If it is omitted, the text will be packed without extra whitespace. If it is a number, it will specify the number of spaces to indent at each level. If it is a string (such as '\t' or ' '), it contains the characters used to indent at each level. This method produces a JSON text from a JavaScript value. When an object value is found, if the object contains a toJSON method, its toJSON method will be called and the result will be stringified. A toJSON method does not serialize: it returns the value represented by the name/value pair that should be serialized, or undefined if nothing should be serialized. The toJSON method will be passed the key associated with the value, and this will be bound to the value For example, this would serialize Dates as ISO strings. Date.prototype.toJSON = function (key) { function f(n) { // Format integers to have at least two digits. return n < 10 ? '0' + n : n; } return this.getUTCFullYear() + '-' + f(this.getUTCMonth() + 1) + '-' + f(this.getUTCDate()) + 'T' + f(this.getUTCHours()) + ':' + f(this.getUTCMinutes()) + ':' + f(this.getUTCSeconds()) + 'Z'; }; You can provide an optional replacer method. It will be passed the key and value of each member, with this bound to the containing object. The value that is returned from your method will be serialized. If your method returns undefined, then the member will be excluded from the serialization. If the replacer parameter is an array of strings, then it will be used to select the members to be serialized. It filters the results such that only members with keys listed in the replacer array are stringified. Values that do not have JSON representations, such as undefined or functions, will not be serialized. Such values in objects will be dropped; in arrays they will be replaced with null. You can use a replacer function to replace those with JSON values. JSON.stringify(undefined) returns undefined. The optional space parameter produces a stringification of the value that is filled with line breaks and indentation to make it easier to read. If the space parameter is a non-empty string, then that string will be used for indentation. If the space parameter is a number, then the indentation will be that many spaces. Example: text = JSON.stringify(['e', {pluribus: 'unum'}]); // text is '["e",{"pluribus":"unum"}]' text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' text = JSON.stringify([new Date()], function (key, value) { return this[key] instanceof Date ? 'Date(' + this[key] + ')' : value; }); // text is '["Date(---current time---)"]' JSON.parse(text, reviver) This method parses a JSON text to produce an object or array. It can throw a SyntaxError exception. The optional reviver parameter is a function that can filter and transform the results. It receives each of the keys and values, and its return value is used instead of the original value. If it returns what it received, then the structure is not modified. If it returns undefined then the member is deleted. Example: // Parse the text. Values that look like ISO date strings will // be converted to Date objects. myData = JSON.parse(text, function (key, value) { var a; if (typeof value === 'string') { a = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); if (a) { return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6])); } } return value; }); myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { var d; if (typeof value === 'string' && value.slice(0, 5) === 'Date(' && value.slice(-1) === ')') { d = new Date(value.slice(5, -1)); if (d) { return d; } } return value; }); This is a reference implementation. You are free to copy, modify, or redistribute. */ /*jslint eval, for, this */ /*property JSON, apply, call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, lastIndex, length, parse, prototype, push, replace, slice, stringify, test, toJSON, toString, valueOf */ // Create a JSON object only if one does not already exist. We create the // methods in a closure to avoid creating global variables. if (typeof JSON !== 'object') { JSON = {}; } (function () { 'use strict'; var rx_one = /^[\],:{}\s]*$/, rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, rx_four = /(?:^|:|,)(?:\s*\[)+/g, rx_escapable = /[\\\"\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, rx_dangerous = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; function f(n) { // Format integers to have at least two digits. return n < 10 ? '0' + n : n; } function this_value() { return this.valueOf(); } if (typeof Date.prototype.toJSON !== 'function') { Date.prototype.toJSON = function () { return isFinite(this.valueOf()) ? this.getUTCFullYear() + '-' + f(this.getUTCMonth() + 1) + '-' + f(this.getUTCDate()) + 'T' + f(this.getUTCHours()) + ':' + f(this.getUTCMinutes()) + ':' + f(this.getUTCSeconds()) + 'Z' : null; }; Boolean.prototype.toJSON = this_value; Number.prototype.toJSON = this_value; String.prototype.toJSON = this_value; } var gap, indent, meta, rep; function quote(string) { // If the string contains no control characters, no quote characters, and no // backslash characters, then we can safely slap some quotes around it. // Otherwise we must also replace the offending characters with safe escape // sequences. rx_escapable.lastIndex = 0; return rx_escapable.test(string) ? '"' + string.replace(rx_escapable, function (a) { var c = meta[a]; return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); }) + '"' : '"' + string + '"'; } function str(key, holder) { // Produce a string from holder[key]. var i, // The loop counter. k, // The member key. v, // The member value. length, mind = gap, partial, value = holder[key]; // If the value has a toJSON method, call it to obtain a replacement value. if (value && typeof value === 'object' && typeof value.toJSON === 'function') { value = value.toJSON(key); } // If we were called with a replacer function, then call the replacer to // obtain a replacement value. if (typeof rep === 'function') { value = rep.call(holder, key, value); } // What happens next depends on the value's type. switch (typeof value) { case 'string': return quote(value); case 'number': // JSON numbers must be finite. Encode non-finite numbers as null. return isFinite(value) ? String(value) : 'null'; case 'boolean': case 'null': // If the value is a boolean or null, convert it to a string. Note: // typeof null does not produce 'null'. The case is included here in // the remote chance that this gets fixed someday. return String(value); // If the type is 'object', we might be dealing with an object or an array or // null. case 'object': // Due to a specification blunder in ECMAScript, typeof null is 'object', // so watch out for that case. if (!value) { return 'null'; } // Make an array to hold the partial results of stringifying this object value. gap += indent; partial = []; // Is the value an array? if (Object.prototype.toString.apply(value) === '[object Array]') { // The value is an array. Stringify every element. Use null as a placeholder // for non-JSON values. length = value.length; for (i = 0; i < length; i += 1) { partial[i] = str(i, value) || 'null'; } // Join all of the elements together, separated with commas, and wrap them in // brackets. v = partial.length === 0 ? '[]' : gap ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : '[' + partial.join(',') + ']'; gap = mind; return v; } // If the replacer is an array, use it to select the members to be stringified. if (rep && typeof rep === 'object') { length = rep.length; for (i = 0; i < length; i += 1) { if (typeof rep[i] === 'string') { k = rep[i]; v = str(k, value); if (v) { partial.push(quote(k) + ( gap ? ': ' : ':' ) + v); } } } } else { // Otherwise, iterate through all of the keys in the object. for (k in value) { if (Object.prototype.hasOwnProperty.call(value, k)) { v = str(k, value); if (v) { partial.push(quote(k) + ( gap ? ': ' : ':' ) + v); } } } } // Join all of the member texts together, separated with commas, // and wrap them in braces. v = partial.length === 0 ? '{}' : gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : '{' + partial.join(',') + '}'; gap = mind; return v; } } // If the JSON object does not yet have a stringify method, give it one. if (typeof JSON.stringify !== 'function') { meta = { // table of character substitutions '\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"': '\\"', '\\': '\\\\' }; JSON.stringify = function (value, replacer, space) { // The stringify method takes a value and an optional replacer, and an optional // space parameter, and returns a JSON text. The replacer can be a function // that can replace values, or an array of strings that will select the keys. // A default replacer method can be provided. Use of the space parameter can // produce text that is more easily readable. var i; gap = ''; indent = ''; // If the space parameter is a number, make an indent string containing that // many spaces. if (typeof space === 'number') { for (i = 0; i < space; i += 1) { indent += ' '; } // If the space parameter is a string, it will be used as the indent string. } else if (typeof space === 'string') { indent = space; } // If there is a replacer, it must be a function or an array. // Otherwise, throw an error. rep = replacer; if (replacer && typeof replacer !== 'function' && (typeof replacer !== 'object' || typeof replacer.length !== 'number')) { throw new Error('JSON.stringify'); } // Make a fake root object containing our value under the key of ''. // Return the result of stringifying the value. return str('', {'': value}); }; } // If the JSON object does not yet have a parse method, give it one. if (typeof JSON.parse !== 'function') { JSON.parse = function (text, reviver) { // The parse method takes a text and an optional reviver function, and returns // a JavaScript value if the text is a valid JSON text. var j; function walk(holder, key) { // The walk method is used to recursively walk the resulting structure so // that modifications can be made. var k, v, value = holder[key]; if (value && typeof value === 'object') { for (k in value) { if (Object.prototype.hasOwnProperty.call(value, k)) { v = walk(value, k); if (v !== undefined) { value[k] = v; } else { delete value[k]; } } } } return reviver.call(holder, key, value); } // Parsing happens in four stages. In the first stage, we replace certain // Unicode characters with escape sequences. JavaScript handles many characters // incorrectly, either silently deleting them, or treating them as line endings. text = String(text); rx_dangerous.lastIndex = 0; if (rx_dangerous.test(text)) { text = text.replace(rx_dangerous, function (a) { return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); }); } // In the second stage, we run the text against regular expressions that look // for non-JSON patterns. We are especially concerned with '()' and 'new' // because they can cause invocation, and '=' because it can cause mutation. // But just to be safe, we want to reject all unexpected forms. // We split the second stage into 4 regexp operations in order to work around // crippling inefficiencies in IE's and Safari's regexp engines. First we // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we // replace all simple value tokens with ']' characters. Third, we delete all // open brackets that follow a colon or comma or that begin the text. Finally, // we look to see that the remaining characters are only whitespace or ']' or // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. if ( rx_one.test( text .replace(rx_two, '@') .replace(rx_three, ']') .replace(rx_four, '') ) ) { // In the third stage we use the eval function to compile the text into a // JavaScript structure. The '{' operator is subject to a syntactic ambiguity // in JavaScript: it can begin a block or an object literal. We wrap the text // in parens to eliminate the ambiguity. j = eval('(' + text + ')'); // In the optional fourth stage, we recursively walk the new structure, passing // each name/value pair to a reviver function for possible transformation. return typeof reviver === 'function' ? walk({'': j}, '') : j; } // If the text is not JSON parseable, then a SyntaxError is thrown. throw new SyntaxError('JSON.parse'); }; } }()); faye-1.4.0/examples/public/mootools.js000066400000000000000000003110151371104377200177530ustar00rootroot00000000000000/* --- script: Core.js description: The core of MooTools, contains all the base functions and the Native and Hash implementations. Required by all the other scripts. license: MIT-style license. copyright: Copyright (c) 2006-2008 [Valerio Proietti](http://mad4milk.net/). authors: The MooTools production team (http://mootools.net/developers/) inspiration: - Class implementation inspired by [Base.js](http://dean.edwards.name/weblog/2006/03/base/) Copyright (c) 2006 Dean Edwards, [GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php) - Some functionality inspired by [Prototype.js](http://prototypejs.org) Copyright (c) 2005-2007 Sam Stephenson, [MIT License](http://opensource.org/licenses/mit-license.php) provides: [Mootools, Native, Hash.base, Array.each, $util] ... */ var MooTools = { 'version': '1.2.4', 'build': '0d9113241a90b9cd5643b926795852a2026710d4' }; var Native = function(options){ options = options || {}; var name = options.name; var legacy = options.legacy; var protect = options.protect; var methods = options.implement; var generics = options.generics; var initialize = options.initialize; var afterImplement = options.afterImplement || function(){}; var object = initialize || legacy; generics = generics !== false; object.constructor = Native; object.$family = {name: 'native'}; if (legacy && initialize) object.prototype = legacy.prototype; object.prototype.constructor = object; if (name){ var family = name.toLowerCase(); object.prototype.$family = {name: family}; Native.typize(object, family); } var add = function(obj, name, method, force){ if (!protect || force || !obj.prototype[name]) obj.prototype[name] = method; if (generics) Native.genericize(obj, name, protect); afterImplement.call(obj, name, method); return obj; }; object.alias = function(a1, a2, a3){ if (typeof a1 == 'string'){ var pa1 = this.prototype[a1]; if ((a1 = pa1)) return add(this, a2, a1, a3); } for (var a in a1) this.alias(a, a1[a], a2); return this; }; object.implement = function(a1, a2, a3){ if (typeof a1 == 'string') return add(this, a1, a2, a3); for (var p in a1) add(this, p, a1[p], a2); return this; }; if (methods) object.implement(methods); return object; }; Native.genericize = function(object, property, check){ if ((!check || !object[property]) && typeof object.prototype[property] == 'function') object[property] = function(){ var args = Array.prototype.slice.call(arguments); return object.prototype[property].apply(args.shift(), args); }; }; Native.implement = function(objects, properties){ for (var i = 0, l = objects.length; i < l; i++) objects[i].implement(properties); }; Native.typize = function(object, family){ if (!object.type) object.type = function(item){ return ($type(item) === family); }; }; (function(){ var natives = {'Array': Array, 'Date': Date, 'Function': Function, 'Number': Number, 'RegExp': RegExp, 'String': String}; for (var n in natives) new Native({name: n, initialize: natives[n], protect: true}); var types = {'boolean': Boolean, 'native': Native, 'object': Object}; for (var t in types) Native.typize(types[t], t); var generics = { 'Array': ["concat", "indexOf", "join", "lastIndexOf", "pop", "push", "reverse", "shift", "slice", "sort", "splice", "toString", "unshift", "valueOf"], 'String': ["charAt", "charCodeAt", "concat", "indexOf", "lastIndexOf", "match", "replace", "search", "slice", "split", "substr", "substring", "toLowerCase", "toUpperCase", "valueOf"] }; for (var g in generics){ for (var i = generics[g].length; i--;) Native.genericize(natives[g], generics[g][i], true); } })(); var Hash = new Native({ name: 'Hash', initialize: function(object){ if ($type(object) == 'hash') object = $unlink(object.getClean()); for (var key in object) this[key] = object[key]; return this; } }); Hash.implement({ forEach: function(fn, bind){ for (var key in this){ if (this.hasOwnProperty(key)) fn.call(bind, this[key], key, this); } }, getClean: function(){ var clean = {}; for (var key in this){ if (this.hasOwnProperty(key)) clean[key] = this[key]; } return clean; }, getLength: function(){ var length = 0; for (var key in this){ if (this.hasOwnProperty(key)) length++; } return length; } }); Hash.alias('forEach', 'each'); Array.implement({ forEach: function(fn, bind){ for (var i = 0, l = this.length; i < l; i++) fn.call(bind, this[i], i, this); } }); Array.alias('forEach', 'each'); function $A(iterable){ if (iterable.item){ var l = iterable.length, array = new Array(l); while (l--) array[l] = iterable[l]; return array; } return Array.prototype.slice.call(iterable); }; function $arguments(i){ return function(){ return arguments[i]; }; }; function $chk(obj){ return !!(obj || obj === 0); }; function $clear(timer){ clearTimeout(timer); clearInterval(timer); return null; }; function $defined(obj){ return (obj != undefined); }; function $each(iterable, fn, bind){ var type = $type(iterable); ((type == 'arguments' || type == 'collection' || type == 'array') ? Array : Hash).each(iterable, fn, bind); }; function $empty(){}; function $extend(original, extended){ for (var key in (extended || {})) original[key] = extended[key]; return original; }; function $H(object){ return new Hash(object); }; function $lambda(value){ return ($type(value) == 'function') ? value : function(){ return value; }; }; function $merge(){ var args = Array.slice(arguments); args.unshift({}); return $mixin.apply(null, args); }; function $mixin(mix){ for (var i = 1, l = arguments.length; i < l; i++){ var object = arguments[i]; if ($type(object) != 'object') continue; for (var key in object){ var op = object[key], mp = mix[key]; mix[key] = (mp && $type(op) == 'object' && $type(mp) == 'object') ? $mixin(mp, op) : $unlink(op); } } return mix; }; function $pick(){ for (var i = 0, l = arguments.length; i < l; i++){ if (arguments[i] != undefined) return arguments[i]; } return null; }; function $random(min, max){ return Math.floor(Math.random() * (max - min + 1) + min); }; function $splat(obj){ var type = $type(obj); return (type) ? ((type != 'array' && type != 'arguments') ? [obj] : obj) : []; }; var $time = Date.now || function(){ return +new Date; }; function $try(){ for (var i = 0, l = arguments.length; i < l; i++){ try { return arguments[i](); } catch(e){} } return null; }; function $type(obj){ if (obj == undefined) return false; if (obj.$family) return (obj.$family.name == 'number' && !isFinite(obj)) ? false : obj.$family.name; if (obj.nodeName){ switch (obj.nodeType){ case 1: return 'element'; case 3: return (/\S/).test(obj.nodeValue) ? 'textnode' : 'whitespace'; } } else if (typeof obj.length == 'number'){ if (obj.callee) return 'arguments'; else if (obj.item) return 'collection'; } return typeof obj; }; function $unlink(object){ var unlinked; switch ($type(object)){ case 'object': unlinked = {}; for (var p in object) unlinked[p] = $unlink(object[p]); break; case 'hash': unlinked = new Hash(object); break; case 'array': unlinked = []; for (var i = 0, l = object.length; i < l; i++) unlinked[i] = $unlink(object[i]); break; default: return object; } return unlinked; }; /* --- script: Browser.js description: The Browser Core. Contains Browser initialization, Window and Document, and the Browser Hash. license: MIT-style license. requires: - /Native - /$util provides: [Browser, Window, Document, $exec] ... */ var Browser = $merge({ Engine: {name: 'unknown', version: 0}, Platform: {name: (window.orientation != undefined) ? 'ipod' : (navigator.platform.match(/mac|win|linux/i) || ['other'])[0].toLowerCase()}, Features: {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)}, Plugins: {}, Engines: { presto: function(){ return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }, trident: function(){ return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4); }, webkit: function(){ return (navigator.taintEnabled) ? false : ((Browser.Features.xpath) ? ((Browser.Features.query) ? 525 : 420) : 419); }, gecko: function(){ return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19 : 18); } } }, Browser || {}); Browser.Platform[Browser.Platform.name] = true; Browser.detect = function(){ for (var engine in this.Engines){ var version = this.Engines[engine](); if (version){ this.Engine = {name: engine, version: version}; this.Engine[engine] = this.Engine[engine + version] = true; break; } } return {name: engine, version: version}; }; Browser.detect(); Browser.Request = function(){ return $try(function(){ return new XMLHttpRequest(); }, function(){ return new ActiveXObject('MSXML2.XMLHTTP'); }, function(){ return new ActiveXObject('Microsoft.XMLHTTP'); }); }; Browser.Features.xhr = !!(Browser.Request()); Browser.Plugins.Flash = (function(){ var version = ($try(function(){ return navigator.plugins['Shockwave Flash'].description; }, function(){ return new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version'); }) || '0 r0').match(/\d+/g); return {version: parseInt(version[0] || 0 + '.' + version[1], 10) || 0, build: parseInt(version[2], 10) || 0}; })(); function $exec(text){ if (!text) return text; if (window.execScript){ window.execScript(text); } else { var script = document.createElement('script'); script.setAttribute('type', 'text/javascript'); script[(Browser.Engine.webkit && Browser.Engine.version < 420) ? 'innerText' : 'text'] = text; document.head.appendChild(script); document.head.removeChild(script); } return text; }; Native.UID = 1; var $uid = (Browser.Engine.trident) ? function(item){ return (item.uid || (item.uid = [Native.UID++]))[0]; } : function(item){ return item.uid || (item.uid = Native.UID++); }; var Window = new Native({ name: 'Window', legacy: (Browser.Engine.trident) ? null: window.Window, initialize: function(win){ $uid(win); if (!win.Element){ win.Element = $empty; if (Browser.Engine.webkit) win.document.createElement("iframe"); //fixes safari 2 win.Element.prototype = (Browser.Engine.webkit) ? window["[[DOMElement.prototype]]"] : {}; } win.document.window = win; return $extend(win, Window.Prototype); }, afterImplement: function(property, value){ window[property] = Window.Prototype[property] = value; } }); Window.Prototype = {$family: {name: 'window'}}; new Window(window); var Document = new Native({ name: 'Document', legacy: (Browser.Engine.trident) ? null: window.Document, initialize: function(doc){ $uid(doc); doc.head = doc.getElementsByTagName('head')[0]; doc.html = doc.getElementsByTagName('html')[0]; if (Browser.Engine.trident && Browser.Engine.version <= 4) $try(function(){ doc.execCommand("BackgroundImageCache", false, true); }); if (Browser.Engine.trident) doc.window.attachEvent('onunload', function(){ doc.window.detachEvent('onunload', arguments.callee); doc.head = doc.html = doc.window = null; }); return $extend(doc, Document.Prototype); }, afterImplement: function(property, value){ document[property] = Document.Prototype[property] = value; } }); Document.Prototype = {$family: {name: 'document'}}; new Document(document); /* --- script: Array.js description: Contains Array Prototypes like each, contains, and erase. license: MIT-style license. requires: - /$util - /Array.each provides: [Array] ... */ Array.implement({ every: function(fn, bind){ for (var i = 0, l = this.length; i < l; i++){ if (!fn.call(bind, this[i], i, this)) return false; } return true; }, filter: function(fn, bind){ var results = []; for (var i = 0, l = this.length; i < l; i++){ if (fn.call(bind, this[i], i, this)) results.push(this[i]); } return results; }, clean: function(){ return this.filter($defined); }, indexOf: function(item, from){ var len = this.length; for (var i = (from < 0) ? Math.max(0, len + from) : from || 0; i < len; i++){ if (this[i] === item) return i; } return -1; }, map: function(fn, bind){ var results = []; for (var i = 0, l = this.length; i < l; i++) results[i] = fn.call(bind, this[i], i, this); return results; }, some: function(fn, bind){ for (var i = 0, l = this.length; i < l; i++){ if (fn.call(bind, this[i], i, this)) return true; } return false; }, associate: function(keys){ var obj = {}, length = Math.min(this.length, keys.length); for (var i = 0; i < length; i++) obj[keys[i]] = this[i]; return obj; }, link: function(object){ var result = {}; for (var i = 0, l = this.length; i < l; i++){ for (var key in object){ if (object[key](this[i])){ result[key] = this[i]; delete object[key]; break; } } } return result; }, contains: function(item, from){ return this.indexOf(item, from) != -1; }, extend: function(array){ for (var i = 0, j = array.length; i < j; i++) this.push(array[i]); return this; }, getLast: function(){ return (this.length) ? this[this.length - 1] : null; }, getRandom: function(){ return (this.length) ? this[$random(0, this.length - 1)] : null; }, include: function(item){ if (!this.contains(item)) this.push(item); return this; }, combine: function(array){ for (var i = 0, l = array.length; i < l; i++) this.include(array[i]); return this; }, erase: function(item){ for (var i = this.length; i--; i){ if (this[i] === item) this.splice(i, 1); } return this; }, empty: function(){ this.length = 0; return this; }, flatten: function(){ var array = []; for (var i = 0, l = this.length; i < l; i++){ var type = $type(this[i]); if (!type) continue; array = array.concat((type == 'array' || type == 'collection' || type == 'arguments') ? Array.flatten(this[i]) : this[i]); } return array; }, hexToRgb: function(array){ if (this.length != 3) return null; var rgb = this.map(function(value){ if (value.length == 1) value += value; return value.toInt(16); }); return (array) ? rgb : 'rgb(' + rgb + ')'; }, rgbToHex: function(array){ if (this.length < 3) return null; if (this.length == 4 && this[3] == 0 && !array) return 'transparent'; var hex = []; for (var i = 0; i < 3; i++){ var bit = (this[i] - 0).toString(16); hex.push((bit.length == 1) ? '0' + bit : bit); } return (array) ? hex : '#' + hex.join(''); } }); /* --- script: Function.js description: Contains Function Prototypes like create, bind, pass, and delay. license: MIT-style license. requires: - /Native - /$util provides: [Function] ... */ Function.implement({ extend: function(properties){ for (var property in properties) this[property] = properties[property]; return this; }, create: function(options){ var self = this; options = options || {}; return function(event){ var args = options.arguments; args = (args != undefined) ? $splat(args) : Array.slice(arguments, (options.event) ? 1 : 0); if (options.event) args = [event || window.event].extend(args); var returns = function(){ return self.apply(options.bind || null, args); }; if (options.delay) return setTimeout(returns, options.delay); if (options.periodical) return setInterval(returns, options.periodical); if (options.attempt) return $try(returns); return returns(); }; }, run: function(args, bind){ return this.apply(bind, $splat(args)); }, pass: function(args, bind){ return this.create({bind: bind, arguments: args}); }, bind: function(bind, args){ return this.create({bind: bind, arguments: args}); }, bindWithEvent: function(bind, args){ return this.create({bind: bind, arguments: args, event: true}); }, attempt: function(args, bind){ return this.create({bind: bind, arguments: args, attempt: true})(); }, delay: function(delay, bind, args){ return this.create({bind: bind, arguments: args, delay: delay})(); }, periodical: function(periodical, bind, args){ return this.create({bind: bind, arguments: args, periodical: periodical})(); } }); /* --- script: Number.js description: Contains Number Prototypes like limit, round, times, and ceil. license: MIT-style license. requires: - /Native - /$util provides: [Number] ... */ Number.implement({ limit: function(min, max){ return Math.min(max, Math.max(min, this)); }, round: function(precision){ precision = Math.pow(10, precision || 0); return Math.round(this * precision) / precision; }, times: function(fn, bind){ for (var i = 0; i < this; i++) fn.call(bind, i, this); }, toFloat: function(){ return parseFloat(this); }, toInt: function(base){ return parseInt(this, base || 10); } }); Number.alias('times', 'each'); (function(math){ var methods = {}; math.each(function(name){ if (!Number[name]) methods[name] = function(){ return Math[name].apply(null, [this].concat($A(arguments))); }; }); Number.implement(methods); })(['abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', 'log', 'max', 'min', 'pow', 'sin', 'sqrt', 'tan']); /* --- script: String.js description: Contains String Prototypes like camelCase, capitalize, test, and toInt. license: MIT-style license. requires: - /Native provides: [String] ... */ String.implement({ test: function(regex, params){ return ((typeof regex == 'string') ? new RegExp(regex, params) : regex).test(this); }, contains: function(string, separator){ return (separator) ? (separator + this + separator).indexOf(separator + string + separator) > -1 : this.indexOf(string) > -1; }, trim: function(){ return this.replace(/^\s+|\s+$/g, ''); }, clean: function(){ return this.replace(/\s+/g, ' ').trim(); }, camelCase: function(){ return this.replace(/-\D/g, function(match){ return match.charAt(1).toUpperCase(); }); }, hyphenate: function(){ return this.replace(/[A-Z]/g, function(match){ return ('-' + match.charAt(0).toLowerCase()); }); }, capitalize: function(){ return this.replace(/\b[a-z]/g, function(match){ return match.toUpperCase(); }); }, escapeRegExp: function(){ return this.replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1'); }, toInt: function(base){ return parseInt(this, base || 10); }, toFloat: function(){ return parseFloat(this); }, hexToRgb: function(array){ var hex = this.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/); return (hex) ? hex.slice(1).hexToRgb(array) : null; }, rgbToHex: function(array){ var rgb = this.match(/\d{1,3}/g); return (rgb) ? rgb.rgbToHex(array) : null; }, stripScripts: function(option){ var scripts = ''; var text = this.replace(/]*>([\s\S]*?)<\/script>/gi, function(){ scripts += arguments[1] + '\n'; return ''; }); if (option === true) $exec(scripts); else if ($type(option) == 'function') option(scripts, text); return text; }, substitute: function(object, regexp){ return this.replace(regexp || (/\\?\{([^{}]+)\}/g), function(match, name){ if (match.charAt(0) == '\\') return match.slice(1); return (object[name] != undefined) ? object[name] : ''; }); } }); /* --- script: Hash.js description: Contains Hash Prototypes. Provides a means for overcoming the JavaScript practical impossibility of extending native Objects. license: MIT-style license. requires: - /Hash.base provides: [Hash] ... */ Hash.implement({ has: Object.prototype.hasOwnProperty, keyOf: function(value){ for (var key in this){ if (this.hasOwnProperty(key) && this[key] === value) return key; } return null; }, hasValue: function(value){ return (Hash.keyOf(this, value) !== null); }, extend: function(properties){ Hash.each(properties || {}, function(value, key){ Hash.set(this, key, value); }, this); return this; }, combine: function(properties){ Hash.each(properties || {}, function(value, key){ Hash.include(this, key, value); }, this); return this; }, erase: function(key){ if (this.hasOwnProperty(key)) delete this[key]; return this; }, get: function(key){ return (this.hasOwnProperty(key)) ? this[key] : null; }, set: function(key, value){ if (!this[key] || this.hasOwnProperty(key)) this[key] = value; return this; }, empty: function(){ Hash.each(this, function(value, key){ delete this[key]; }, this); return this; }, include: function(key, value){ if (this[key] == undefined) this[key] = value; return this; }, map: function(fn, bind){ var results = new Hash; Hash.each(this, function(value, key){ results.set(key, fn.call(bind, value, key, this)); }, this); return results; }, filter: function(fn, bind){ var results = new Hash; Hash.each(this, function(value, key){ if (fn.call(bind, value, key, this)) results.set(key, value); }, this); return results; }, every: function(fn, bind){ for (var key in this){ if (this.hasOwnProperty(key) && !fn.call(bind, this[key], key)) return false; } return true; }, some: function(fn, bind){ for (var key in this){ if (this.hasOwnProperty(key) && fn.call(bind, this[key], key)) return true; } return false; }, getKeys: function(){ var keys = []; Hash.each(this, function(value, key){ keys.push(key); }); return keys; }, getValues: function(){ var values = []; Hash.each(this, function(value){ values.push(value); }); return values; }, toQueryString: function(base){ var queryString = []; Hash.each(this, function(value, key){ if (base) key = base + '[' + key + ']'; var result; switch ($type(value)){ case 'object': result = Hash.toQueryString(value, key); break; case 'array': var qs = {}; value.each(function(val, i){ qs[i] = val; }); result = Hash.toQueryString(qs, key); break; default: result = key + '=' + encodeURIComponent(value); } if (value != undefined) queryString.push(result); }); return queryString.join('&'); } }); Hash.alias({keyOf: 'indexOf', hasValue: 'contains'}); /* --- script: Event.js description: Contains the Event Class, to make the event object cross-browser. license: MIT-style license. requires: - /Window - /Document - /Hash - /Array - /Function - /String provides: [Event] ... */ var Event = new Native({ name: 'Event', initialize: function(event, win){ win = win || window; var doc = win.document; event = event || win.event; if (event.$extended) return event; this.$extended = true; var type = event.type; var target = event.target || event.srcElement; while (target && target.nodeType == 3) target = target.parentNode; if (type.test(/key/)){ var code = event.which || event.keyCode; var key = Event.Keys.keyOf(code); if (type == 'keydown'){ var fKey = code - 111; if (fKey > 0 && fKey < 13) key = 'f' + fKey; } key = key || String.fromCharCode(code).toLowerCase(); } else if (type.match(/(click|mouse|menu)/i)){ doc = (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body; var page = { x: event.pageX || event.clientX + doc.scrollLeft, y: event.pageY || event.clientY + doc.scrollTop }; var client = { x: (event.pageX) ? event.pageX - win.pageXOffset : event.clientX, y: (event.pageY) ? event.pageY - win.pageYOffset : event.clientY }; if (type.match(/DOMMouseScroll|mousewheel/)){ var wheel = (event.wheelDelta) ? event.wheelDelta / 120 : -(event.detail || 0) / 3; } var rightClick = (event.which == 3) || (event.button == 2); var related = null; if (type.match(/over|out/)){ switch (type){ case 'mouseover': related = event.relatedTarget || event.fromElement; break; case 'mouseout': related = event.relatedTarget || event.toElement; } if (!(function(){ while (related && related.nodeType == 3) related = related.parentNode; return true; }).create({attempt: Browser.Engine.gecko})()) related = false; } } return $extend(this, { event: event, type: type, page: page, client: client, rightClick: rightClick, wheel: wheel, relatedTarget: related, target: target, code: code, key: key, shift: event.shiftKey, control: event.ctrlKey, alt: event.altKey, meta: event.metaKey }); } }); Event.Keys = new Hash({ 'enter': 13, 'up': 38, 'down': 40, 'left': 37, 'right': 39, 'esc': 27, 'space': 32, 'backspace': 8, 'tab': 9, 'delete': 46 }); Event.implement({ stop: function(){ return this.stopPropagation().preventDefault(); }, stopPropagation: function(){ if (this.event.stopPropagation) this.event.stopPropagation(); else this.event.cancelBubble = true; return this; }, preventDefault: function(){ if (this.event.preventDefault) this.event.preventDefault(); else this.event.returnValue = false; return this; } }); /* --- script: Class.js description: Contains the Class Function for easily creating, extending, and implementing reusable Classes. license: MIT-style license. requires: - /$util - /Native - /Array - /String - /Function - /Number - /Hash provides: [Class] ... */ function Class(params){ if (params instanceof Function) params = {initialize: params}; var newClass = function(){ Object.reset(this); if (newClass._prototyping) return this; this._current = $empty; var value = (this.initialize) ? this.initialize.apply(this, arguments) : this; delete this._current; delete this.caller; return value; }.extend(this); newClass.implement(params); newClass.constructor = Class; newClass.prototype.constructor = newClass; return newClass; }; Function.prototype.protect = function(){ this._protected = true; return this; }; Object.reset = function(object, key){ if (key == null){ for (var p in object) Object.reset(object, p); return object; } delete object[key]; switch ($type(object[key])){ case 'object': var F = function(){}; F.prototype = object[key]; var i = new F; object[key] = Object.reset(i); break; case 'array': object[key] = $unlink(object[key]); break; } return object; }; new Native({name: 'Class', initialize: Class}).extend({ instantiate: function(F){ F._prototyping = true; var proto = new F; delete F._prototyping; return proto; }, wrap: function(self, key, method){ if (method._origin) method = method._origin; return function(){ if (method._protected && this._current == null) throw new Error('The method "' + key + '" cannot be called.'); var caller = this.caller, current = this._current; this.caller = current; this._current = arguments.callee; var result = method.apply(this, arguments); this._current = current; this.caller = caller; return result; }.extend({_owner: self, _origin: method, _name: key}); } }); Class.implement({ implement: function(key, value){ if ($type(key) == 'object'){ for (var p in key) this.implement(p, key[p]); return this; } var mutator = Class.Mutators[key]; if (mutator){ value = mutator.call(this, value); if (value == null) return this; } var proto = this.prototype; switch ($type(value)){ case 'function': if (value._hidden) return this; proto[key] = Class.wrap(this, key, value); break; case 'object': var previous = proto[key]; if ($type(previous) == 'object') $mixin(previous, value); else proto[key] = $unlink(value); break; case 'array': proto[key] = $unlink(value); break; default: proto[key] = value; } return this; } }); Class.Mutators = { Extends: function(parent){ this.parent = parent; this.prototype = Class.instantiate(parent); this.implement('parent', function(){ var name = this.caller._name, previous = this.caller._owner.parent.prototype[name]; if (!previous) throw new Error('The method "' + name + '" has no parent.'); return previous.apply(this, arguments); }.protect()); }, Implements: function(items){ $splat(items).each(function(item){ if (item instanceof Function) item = Class.instantiate(item); this.implement(item); }, this); } }; /* --- script: Class.Extras.js description: Contains Utility Classes that can be implemented into your own Classes to ease the execution of many common tasks. license: MIT-style license. requires: - /Class provides: [Chain, Events, Options] ... */ var Chain = new Class({ $chain: [], chain: function(){ this.$chain.extend(Array.flatten(arguments)); return this; }, callChain: function(){ return (this.$chain.length) ? this.$chain.shift().apply(this, arguments) : false; }, clearChain: function(){ this.$chain.empty(); return this; } }); var Events = new Class({ $events: {}, addEvent: function(type, fn, internal){ type = Events.removeOn(type); if (fn != $empty){ this.$events[type] = this.$events[type] || []; this.$events[type].include(fn); if (internal) fn.internal = true; } return this; }, addEvents: function(events){ for (var type in events) this.addEvent(type, events[type]); return this; }, fireEvent: function(type, args, delay){ type = Events.removeOn(type); if (!this.$events || !this.$events[type]) return this; this.$events[type].each(function(fn){ fn.create({'bind': this, 'delay': delay, 'arguments': args})(); }, this); return this; }, removeEvent: function(type, fn){ type = Events.removeOn(type); if (!this.$events[type]) return this; if (!fn.internal) this.$events[type].erase(fn); return this; }, removeEvents: function(events){ var type; if ($type(events) == 'object'){ for (type in events) this.removeEvent(type, events[type]); return this; } if (events) events = Events.removeOn(events); for (type in this.$events){ if (events && events != type) continue; var fns = this.$events[type]; for (var i = fns.length; i--; i) this.removeEvent(type, fns[i]); } return this; } }); Events.removeOn = function(string){ return string.replace(/^on([A-Z])/, function(full, first){ return first.toLowerCase(); }); }; var Options = new Class({ setOptions: function(){ this.options = $merge.run([this.options].extend(arguments)); if (!this.addEvent) return this; for (var option in this.options){ if ($type(this.options[option]) != 'function' || !(/^on[A-Z]/).test(option)) continue; this.addEvent(option, this.options[option]); delete this.options[option]; } return this; } }); /* --- script: Element.js description: One of the most important items in MooTools. Contains the dollar function, the dollars function, and an handful of cross-browser, time-saver methods to let you easily work with HTML Elements. license: MIT-style license. requires: - /Window - /Document - /Array - /String - /Function - /Number - /Hash provides: [Element, Elements, $, $$, Iframe] ... */ var Element = new Native({ name: 'Element', legacy: window.Element, initialize: function(tag, props){ var konstructor = Element.Constructors.get(tag); if (konstructor) return konstructor(props); if (typeof tag == 'string') return document.newElement(tag, props); return document.id(tag).set(props); }, afterImplement: function(key, value){ Element.Prototype[key] = value; if (Array[key]) return; Elements.implement(key, function(){ var items = [], elements = true; for (var i = 0, j = this.length; i < j; i++){ var returns = this[i][key].apply(this[i], arguments); items.push(returns); if (elements) elements = ($type(returns) == 'element'); } return (elements) ? new Elements(items) : items; }); } }); Element.Prototype = {$family: {name: 'element'}}; Element.Constructors = new Hash; var IFrame = new Native({ name: 'IFrame', generics: false, initialize: function(){ var params = Array.link(arguments, {properties: Object.type, iframe: $defined}); var props = params.properties || {}; var iframe = document.id(params.iframe); var onload = props.onload || $empty; delete props.onload; props.id = props.name = $pick(props.id, props.name, iframe ? (iframe.id || iframe.name) : 'IFrame_' + $time()); iframe = new Element(iframe || 'iframe', props); var onFrameLoad = function(){ var host = $try(function(){ return iframe.contentWindow.location.host; }); if (!host || host == window.location.host){ var win = new Window(iframe.contentWindow); new Document(iframe.contentWindow.document); $extend(win.Element.prototype, Element.Prototype); } onload.call(iframe.contentWindow, iframe.contentWindow.document); }; var contentWindow = $try(function(){ return iframe.contentWindow; }); ((contentWindow && contentWindow.document.body) || window.frames[props.id]) ? onFrameLoad() : iframe.addListener('load', onFrameLoad); return iframe; } }); var Elements = new Native({ initialize: function(elements, options){ options = $extend({ddup: true, cash: true}, options); elements = elements || []; if (options.ddup || options.cash){ var uniques = {}, returned = []; for (var i = 0, l = elements.length; i < l; i++){ var el = document.id(elements[i], !options.cash); if (options.ddup){ if (uniques[el.uid]) continue; uniques[el.uid] = true; } if (el) returned.push(el); } elements = returned; } return (options.cash) ? $extend(elements, this) : elements; } }); Elements.implement({ filter: function(filter, bind){ if (!filter) return this; return new Elements(Array.filter(this, (typeof filter == 'string') ? function(item){ return item.match(filter); } : filter, bind)); } }); Document.implement({ newElement: function(tag, props){ if (Browser.Engine.trident && props){ ['name', 'type', 'checked'].each(function(attribute){ if (!props[attribute]) return; tag += ' ' + attribute + '="' + props[attribute] + '"'; if (attribute != 'checked') delete props[attribute]; }); tag = '<' + tag + '>'; } return document.id(this.createElement(tag)).set(props); }, newTextNode: function(text){ return this.createTextNode(text); }, getDocument: function(){ return this; }, getWindow: function(){ return this.window; }, id: (function(){ var types = { string: function(id, nocash, doc){ id = doc.getElementById(id); return (id) ? types.element(id, nocash) : null; }, element: function(el, nocash){ $uid(el); if (!nocash && !el.$family && !(/^object|embed$/i).test(el.tagName)){ var proto = Element.Prototype; for (var p in proto) el[p] = proto[p]; }; return el; }, object: function(obj, nocash, doc){ if (obj.toElement) return types.element(obj.toElement(doc), nocash); return null; } }; types.textnode = types.whitespace = types.window = types.document = $arguments(0); return function(el, nocash, doc){ if (el && el.$family && el.uid) return el; var type = $type(el); return (types[type]) ? types[type](el, nocash, doc || document) : null; }; })() }); if (window.$ == null) Window.implement({ $: function(el, nc){ return document.id(el, nc, this.document); } }); Window.implement({ $$: function(selector){ if (arguments.length == 1 && typeof selector == 'string') return this.document.getElements(selector); var elements = []; var args = Array.flatten(arguments); for (var i = 0, l = args.length; i < l; i++){ var item = args[i]; switch ($type(item)){ case 'element': elements.push(item); break; case 'string': elements.extend(this.document.getElements(item, true)); } } return new Elements(elements); }, getDocument: function(){ return this.document; }, getWindow: function(){ return this; } }); Native.implement([Element, Document], { getElement: function(selector, nocash){ return document.id(this.getElements(selector, true)[0] || null, nocash); }, getElements: function(tags, nocash){ tags = tags.split(','); var elements = []; var ddup = (tags.length > 1); tags.each(function(tag){ var partial = this.getElementsByTagName(tag.trim()); (ddup) ? elements.extend(partial) : elements = partial; }, this); return new Elements(elements, {ddup: ddup, cash: !nocash}); } }); (function(){ var collected = {}, storage = {}; var props = {input: 'checked', option: 'selected', textarea: (Browser.Engine.webkit && Browser.Engine.version < 420) ? 'innerHTML' : 'value'}; var get = function(uid){ return (storage[uid] || (storage[uid] = {})); }; var clean = function(item, retain){ if (!item) return; var uid = item.uid; if (Browser.Engine.trident){ if (item.clearAttributes){ var clone = retain && item.cloneNode(false); item.clearAttributes(); if (clone) item.mergeAttributes(clone); } else if (item.removeEvents){ item.removeEvents(); } if ((/object/i).test(item.tagName)){ for (var p in item){ if (typeof item[p] == 'function') item[p] = $empty; } Element.dispose(item); } } if (!uid) return; collected[uid] = storage[uid] = null; }; var purge = function(){ Hash.each(collected, clean); if (Browser.Engine.trident) $A(document.getElementsByTagName('object')).each(clean); if (window.CollectGarbage) CollectGarbage(); collected = storage = null; }; var walk = function(element, walk, start, match, all, nocash){ var el = element[start || walk]; var elements = []; while (el){ if (el.nodeType == 1 && (!match || Element.match(el, match))){ if (!all) return document.id(el, nocash); elements.push(el); } el = el[walk]; } return (all) ? new Elements(elements, {ddup: false, cash: !nocash}) : null; }; var attributes = { 'html': 'innerHTML', 'class': 'className', 'for': 'htmlFor', 'defaultValue': 'defaultValue', 'text': (Browser.Engine.trident || (Browser.Engine.webkit && Browser.Engine.version < 420)) ? 'innerText' : 'textContent' }; var bools = ['compact', 'nowrap', 'ismap', 'declare', 'noshade', 'checked', 'disabled', 'readonly', 'multiple', 'selected', 'noresize', 'defer']; var camels = ['value', 'type', 'defaultValue', 'accessKey', 'cellPadding', 'cellSpacing', 'colSpan', 'frameBorder', 'maxLength', 'readOnly', 'rowSpan', 'tabIndex', 'useMap']; bools = bools.associate(bools); Hash.extend(attributes, bools); Hash.extend(attributes, camels.associate(camels.map(String.toLowerCase))); var inserters = { before: function(context, element){ if (element.parentNode) element.parentNode.insertBefore(context, element); }, after: function(context, element){ if (!element.parentNode) return; var next = element.nextSibling; (next) ? element.parentNode.insertBefore(context, next) : element.parentNode.appendChild(context); }, bottom: function(context, element){ element.appendChild(context); }, top: function(context, element){ var first = element.firstChild; (first) ? element.insertBefore(context, first) : element.appendChild(context); } }; inserters.inside = inserters.bottom; Hash.each(inserters, function(inserter, where){ where = where.capitalize(); Element.implement('inject' + where, function(el){ inserter(this, document.id(el, true)); return this; }); Element.implement('grab' + where, function(el){ inserter(document.id(el, true), this); return this; }); }); Element.implement({ set: function(prop, value){ switch ($type(prop)){ case 'object': for (var p in prop) this.set(p, prop[p]); break; case 'string': var property = Element.Properties.get(prop); (property && property.set) ? property.set.apply(this, Array.slice(arguments, 1)) : this.setProperty(prop, value); } return this; }, get: function(prop){ var property = Element.Properties.get(prop); return (property && property.get) ? property.get.apply(this, Array.slice(arguments, 1)) : this.getProperty(prop); }, erase: function(prop){ var property = Element.Properties.get(prop); (property && property.erase) ? property.erase.apply(this) : this.removeProperty(prop); return this; }, setProperty: function(attribute, value){ var key = attributes[attribute]; if (value == undefined) return this.removeProperty(attribute); if (key && bools[attribute]) value = !!value; (key) ? this[key] = value : this.setAttribute(attribute, '' + value); return this; }, setProperties: function(attributes){ for (var attribute in attributes) this.setProperty(attribute, attributes[attribute]); return this; }, getProperty: function(attribute){ var key = attributes[attribute]; var value = (key) ? this[key] : this.getAttribute(attribute, 2); return (bools[attribute]) ? !!value : (key) ? value : value || null; }, getProperties: function(){ var args = $A(arguments); return args.map(this.getProperty, this).associate(args); }, removeProperty: function(attribute){ var key = attributes[attribute]; (key) ? this[key] = (key && bools[attribute]) ? false : '' : this.removeAttribute(attribute); return this; }, removeProperties: function(){ Array.each(arguments, this.removeProperty, this); return this; }, hasClass: function(className){ return this.className.contains(className, ' '); }, addClass: function(className){ if (!this.hasClass(className)) this.className = (this.className + ' ' + className).clean(); return this; }, removeClass: function(className){ this.className = this.className.replace(new RegExp('(^|\\s)' + className + '(?:\\s|$)'), '$1'); return this; }, toggleClass: function(className){ return this.hasClass(className) ? this.removeClass(className) : this.addClass(className); }, adopt: function(){ Array.flatten(arguments).each(function(element){ element = document.id(element, true); if (element) this.appendChild(element); }, this); return this; }, appendText: function(text, where){ return this.grab(this.getDocument().newTextNode(text), where); }, grab: function(el, where){ inserters[where || 'bottom'](document.id(el, true), this); return this; }, inject: function(el, where){ inserters[where || 'bottom'](this, document.id(el, true)); return this; }, replaces: function(el){ el = document.id(el, true); el.parentNode.replaceChild(this, el); return this; }, wraps: function(el, where){ el = document.id(el, true); return this.replaces(el).grab(el, where); }, getPrevious: function(match, nocash){ return walk(this, 'previousSibling', null, match, false, nocash); }, getAllPrevious: function(match, nocash){ return walk(this, 'previousSibling', null, match, true, nocash); }, getNext: function(match, nocash){ return walk(this, 'nextSibling', null, match, false, nocash); }, getAllNext: function(match, nocash){ return walk(this, 'nextSibling', null, match, true, nocash); }, getFirst: function(match, nocash){ return walk(this, 'nextSibling', 'firstChild', match, false, nocash); }, getLast: function(match, nocash){ return walk(this, 'previousSibling', 'lastChild', match, false, nocash); }, getParent: function(match, nocash){ return walk(this, 'parentNode', null, match, false, nocash); }, getParents: function(match, nocash){ return walk(this, 'parentNode', null, match, true, nocash); }, getSiblings: function(match, nocash){ return this.getParent().getChildren(match, nocash).erase(this); }, getChildren: function(match, nocash){ return walk(this, 'nextSibling', 'firstChild', match, true, nocash); }, getWindow: function(){ return this.ownerDocument.window; }, getDocument: function(){ return this.ownerDocument; }, getElementById: function(id, nocash){ var el = this.ownerDocument.getElementById(id); if (!el) return null; for (var parent = el.parentNode; parent != this; parent = parent.parentNode){ if (!parent) return null; } return document.id(el, nocash); }, getSelected: function(){ return new Elements($A(this.options).filter(function(option){ return option.selected; })); }, getComputedStyle: function(property){ if (this.currentStyle) return this.currentStyle[property.camelCase()]; var computed = this.getDocument().defaultView.getComputedStyle(this, null); return (computed) ? computed.getPropertyValue([property.hyphenate()]) : null; }, toQueryString: function(){ var queryString = []; this.getElements('input, select, textarea', true).each(function(el){ if (!el.name || el.disabled || el.type == 'submit' || el.type == 'reset' || el.type == 'file') return; var value = (el.tagName.toLowerCase() == 'select') ? Element.getSelected(el).map(function(opt){ return opt.value; }) : ((el.type == 'radio' || el.type == 'checkbox') && !el.checked) ? null : el.value; $splat(value).each(function(val){ if (typeof val != 'undefined') queryString.push(el.name + '=' + encodeURIComponent(val)); }); }); return queryString.join('&'); }, clone: function(contents, keepid){ contents = contents !== false; var clone = this.cloneNode(contents); var clean = function(node, element){ if (!keepid) node.removeAttribute('id'); if (Browser.Engine.trident){ node.clearAttributes(); node.mergeAttributes(element); node.removeAttribute('uid'); if (node.options){ var no = node.options, eo = element.options; for (var j = no.length; j--;) no[j].selected = eo[j].selected; } } var prop = props[element.tagName.toLowerCase()]; if (prop && element[prop]) node[prop] = element[prop]; }; if (contents){ var ce = clone.getElementsByTagName('*'), te = this.getElementsByTagName('*'); for (var i = ce.length; i--;) clean(ce[i], te[i]); } clean(clone, this); return document.id(clone); }, destroy: function(){ Element.empty(this); Element.dispose(this); clean(this, true); return null; }, empty: function(){ $A(this.childNodes).each(function(node){ Element.destroy(node); }); return this; }, dispose: function(){ return (this.parentNode) ? this.parentNode.removeChild(this) : this; }, hasChild: function(el){ el = document.id(el, true); if (!el) return false; if (Browser.Engine.webkit && Browser.Engine.version < 420) return $A(this.getElementsByTagName(el.tagName)).contains(el); return (this.contains) ? (this != el && this.contains(el)) : !!(this.compareDocumentPosition(el) & 16); }, match: function(tag){ return (!tag || (tag == this) || (Element.get(this, 'tag') == tag)); } }); Native.implement([Element, Window, Document], { addListener: function(type, fn){ if (type == 'unload'){ var old = fn, self = this; fn = function(){ self.removeListener('unload', fn); old(); }; } else { collected[this.uid] = this; } if (this.addEventListener) this.addEventListener(type, fn, false); else this.attachEvent('on' + type, fn); return this; }, removeListener: function(type, fn){ if (this.removeEventListener) this.removeEventListener(type, fn, false); else this.detachEvent('on' + type, fn); return this; }, retrieve: function(property, dflt){ var storage = get(this.uid), prop = storage[property]; if (dflt != undefined && prop == undefined) prop = storage[property] = dflt; return $pick(prop); }, store: function(property, value){ var storage = get(this.uid); storage[property] = value; return this; }, eliminate: function(property){ var storage = get(this.uid); delete storage[property]; return this; } }); window.addListener('unload', purge); })(); Element.Properties = new Hash; Element.Properties.style = { set: function(style){ this.style.cssText = style; }, get: function(){ return this.style.cssText; }, erase: function(){ this.style.cssText = ''; } }; Element.Properties.tag = { get: function(){ return this.tagName.toLowerCase(); } }; Element.Properties.html = (function(){ var wrapper = document.createElement('div'); var translations = { table: [1, '', '
'], select: [1, ''], tbody: [2, '', '
'], tr: [3, '', '
'] }; translations.thead = translations.tfoot = translations.tbody; var html = { set: function(){ var html = Array.flatten(arguments).join(''); var wrap = Browser.Engine.trident && translations[this.get('tag')]; if (wrap){ var first = wrapper; first.innerHTML = wrap[1] + html + wrap[2]; for (var i = wrap[0]; i--;) first = first.firstChild; this.empty().adopt(first.childNodes); } else { this.innerHTML = html; } } }; html.erase = html.set; return html; })(); if (Browser.Engine.webkit && Browser.Engine.version < 420) Element.Properties.text = { get: function(){ if (this.innerText) return this.innerText; var temp = this.ownerDocument.newElement('div', {html: this.innerHTML}).inject(this.ownerDocument.body); var text = temp.innerText; temp.destroy(); return text; } }; /* --- script: Element.Event.js description: Contains Element methods for dealing with events. This file also includes mouseenter and mouseleave custom Element Events. license: MIT-style license. requires: - /Element - /Event provides: [Element.Event] ... */ Element.Properties.events = {set: function(events){ this.addEvents(events); }}; Native.implement([Element, Window, Document], { addEvent: function(type, fn){ var events = this.retrieve('events', {}); events[type] = events[type] || {'keys': [], 'values': []}; if (events[type].keys.contains(fn)) return this; events[type].keys.push(fn); var realType = type, custom = Element.Events.get(type), condition = fn, self = this; if (custom){ if (custom.onAdd) custom.onAdd.call(this, fn); if (custom.condition){ condition = function(event){ if (custom.condition.call(this, event)) return fn.call(this, event); return true; }; } realType = custom.base || realType; } var defn = function(){ return fn.call(self); }; var nativeEvent = Element.NativeEvents[realType]; if (nativeEvent){ if (nativeEvent == 2){ defn = function(event){ event = new Event(event, self.getWindow()); if (condition.call(self, event) === false) event.stop(); }; } this.addListener(realType, defn); } events[type].values.push(defn); return this; }, removeEvent: function(type, fn){ var events = this.retrieve('events'); if (!events || !events[type]) return this; var pos = events[type].keys.indexOf(fn); if (pos == -1) return this; events[type].keys.splice(pos, 1); var value = events[type].values.splice(pos, 1)[0]; var custom = Element.Events.get(type); if (custom){ if (custom.onRemove) custom.onRemove.call(this, fn); type = custom.base || type; } return (Element.NativeEvents[type]) ? this.removeListener(type, value) : this; }, addEvents: function(events){ for (var event in events) this.addEvent(event, events[event]); return this; }, removeEvents: function(events){ var type; if ($type(events) == 'object'){ for (type in events) this.removeEvent(type, events[type]); return this; } var attached = this.retrieve('events'); if (!attached) return this; if (!events){ for (type in attached) this.removeEvents(type); this.eliminate('events'); } else if (attached[events]){ while (attached[events].keys[0]) this.removeEvent(events, attached[events].keys[0]); attached[events] = null; } return this; }, fireEvent: function(type, args, delay){ var events = this.retrieve('events'); if (!events || !events[type]) return this; events[type].keys.each(function(fn){ fn.create({'bind': this, 'delay': delay, 'arguments': args})(); }, this); return this; }, cloneEvents: function(from, type){ from = document.id(from); var fevents = from.retrieve('events'); if (!fevents) return this; if (!type){ for (var evType in fevents) this.cloneEvents(from, evType); } else if (fevents[type]){ fevents[type].keys.each(function(fn){ this.addEvent(type, fn); }, this); } return this; } }); Element.NativeEvents = { click: 2, dblclick: 2, mouseup: 2, mousedown: 2, contextmenu: 2, //mouse buttons mousewheel: 2, DOMMouseScroll: 2, //mouse wheel mouseover: 2, mouseout: 2, mousemove: 2, selectstart: 2, selectend: 2, //mouse movement keydown: 2, keypress: 2, keyup: 2, //keyboard focus: 2, blur: 2, change: 2, reset: 2, select: 2, submit: 2, //form elements load: 1, unload: 1, beforeunload: 2, resize: 1, move: 1, DOMContentLoaded: 1, readystatechange: 1, //window error: 1, abort: 1, scroll: 1 //misc }; (function(){ var $check = function(event){ var related = event.relatedTarget; if (related == undefined) return true; if (related === false) return false; return ($type(this) != 'document' && related != this && related.prefix != 'xul' && !this.hasChild(related)); }; Element.Events = new Hash({ mouseenter: { base: 'mouseover', condition: $check }, mouseleave: { base: 'mouseout', condition: $check }, mousewheel: { base: (Browser.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel' } }); })(); /* --- script: Element.Style.js description: Contains methods for interacting with the styles of Elements in a fashionable way. license: MIT-style license. requires: - /Element provides: [Element.Style] ... */ Element.Properties.styles = {set: function(styles){ this.setStyles(styles); }}; Element.Properties.opacity = { set: function(opacity, novisibility){ if (!novisibility){ if (opacity == 0){ if (this.style.visibility != 'hidden') this.style.visibility = 'hidden'; } else { if (this.style.visibility != 'visible') this.style.visibility = 'visible'; } } if (!this.currentStyle || !this.currentStyle.hasLayout) this.style.zoom = 1; if (Browser.Engine.trident) this.style.filter = (opacity == 1) ? '' : 'alpha(opacity=' + opacity * 100 + ')'; this.style.opacity = opacity; this.store('opacity', opacity); }, get: function(){ return this.retrieve('opacity', 1); } }; Element.implement({ setOpacity: function(value){ return this.set('opacity', value, true); }, getOpacity: function(){ return this.get('opacity'); }, setStyle: function(property, value){ switch (property){ case 'opacity': return this.set('opacity', parseFloat(value)); case 'float': property = (Browser.Engine.trident) ? 'styleFloat' : 'cssFloat'; } property = property.camelCase(); if ($type(value) != 'string'){ var map = (Element.Styles.get(property) || '@').split(' '); value = $splat(value).map(function(val, i){ if (!map[i]) return ''; return ($type(val) == 'number') ? map[i].replace('@', Math.round(val)) : val; }).join(' '); } else if (value == String(Number(value))){ value = Math.round(value); } this.style[property] = value; return this; }, getStyle: function(property){ switch (property){ case 'opacity': return this.get('opacity'); case 'float': property = (Browser.Engine.trident) ? 'styleFloat' : 'cssFloat'; } property = property.camelCase(); var result = this.style[property]; if (!$chk(result)){ result = []; for (var style in Element.ShortStyles){ if (property != style) continue; for (var s in Element.ShortStyles[style]) result.push(this.getStyle(s)); return result.join(' '); } result = this.getComputedStyle(property); } if (result){ result = String(result); var color = result.match(/rgba?\([\d\s,]+\)/); if (color) result = result.replace(color[0], color[0].rgbToHex()); } if (Browser.Engine.presto || (Browser.Engine.trident && !$chk(parseInt(result, 10)))){ if (property.test(/^(height|width)$/)){ var values = (property == 'width') ? ['left', 'right'] : ['top', 'bottom'], size = 0; values.each(function(value){ size += this.getStyle('border-' + value + '-width').toInt() + this.getStyle('padding-' + value).toInt(); }, this); return this['offset' + property.capitalize()] - size + 'px'; } if ((Browser.Engine.presto) && String(result).test('px')) return result; if (property.test(/(border(.+)Width|margin|padding)/)) return '0px'; } return result; }, setStyles: function(styles){ for (var style in styles) this.setStyle(style, styles[style]); return this; }, getStyles: function(){ var result = {}; Array.flatten(arguments).each(function(key){ result[key] = this.getStyle(key); }, this); return result; } }); Element.Styles = new Hash({ left: '@px', top: '@px', bottom: '@px', right: '@px', width: '@px', height: '@px', maxWidth: '@px', maxHeight: '@px', minWidth: '@px', minHeight: '@px', backgroundColor: 'rgb(@, @, @)', backgroundPosition: '@px @px', color: 'rgb(@, @, @)', fontSize: '@px', letterSpacing: '@px', lineHeight: '@px', clip: 'rect(@px @px @px @px)', margin: '@px @px @px @px', padding: '@px @px @px @px', border: '@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)', borderWidth: '@px @px @px @px', borderStyle: '@ @ @ @', borderColor: 'rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)', zIndex: '@', 'zoom': '@', fontWeight: '@', textIndent: '@px', opacity: '@' }); Element.ShortStyles = {margin: {}, padding: {}, border: {}, borderWidth: {}, borderStyle: {}, borderColor: {}}; ['Top', 'Right', 'Bottom', 'Left'].each(function(direction){ var Short = Element.ShortStyles; var All = Element.Styles; ['margin', 'padding'].each(function(style){ var sd = style + direction; Short[style][sd] = All[sd] = '@px'; }); var bd = 'border' + direction; Short.border[bd] = All[bd] = '@px @ rgb(@, @, @)'; var bdw = bd + 'Width', bds = bd + 'Style', bdc = bd + 'Color'; Short[bd] = {}; Short.borderWidth[bdw] = Short[bd][bdw] = All[bdw] = '@px'; Short.borderStyle[bds] = Short[bd][bds] = All[bds] = '@'; Short.borderColor[bdc] = Short[bd][bdc] = All[bdc] = 'rgb(@, @, @)'; }); /* --- script: Element.Dimensions.js description: Contains methods to work with size, scroll, or positioning of Elements and the window object. license: MIT-style license. credits: - Element positioning based on the [qooxdoo](http://qooxdoo.org/) code and smart browser fixes, [LGPL License](http://www.gnu.org/licenses/lgpl.html). - Viewport dimensions based on [YUI](http://developer.yahoo.com/yui/) code, [BSD License](http://developer.yahoo.com/yui/license.html). requires: - /Element provides: [Element.Dimensions] ... */ (function(){ Element.implement({ scrollTo: function(x, y){ if (isBody(this)){ this.getWindow().scrollTo(x, y); } else { this.scrollLeft = x; this.scrollTop = y; } return this; }, getSize: function(){ if (isBody(this)) return this.getWindow().getSize(); return {x: this.offsetWidth, y: this.offsetHeight}; }, getScrollSize: function(){ if (isBody(this)) return this.getWindow().getScrollSize(); return {x: this.scrollWidth, y: this.scrollHeight}; }, getScroll: function(){ if (isBody(this)) return this.getWindow().getScroll(); return {x: this.scrollLeft, y: this.scrollTop}; }, getScrolls: function(){ var element = this, position = {x: 0, y: 0}; while (element && !isBody(element)){ position.x += element.scrollLeft; position.y += element.scrollTop; element = element.parentNode; } return position; }, getOffsetParent: function(){ var element = this; if (isBody(element)) return null; if (!Browser.Engine.trident) return element.offsetParent; while ((element = element.parentNode) && !isBody(element)){ if (styleString(element, 'position') != 'static') return element; } return null; }, getOffsets: function(){ if (this.getBoundingClientRect){ var bound = this.getBoundingClientRect(), html = document.id(this.getDocument().documentElement), htmlScroll = html.getScroll(), elemScrolls = this.getScrolls(), elemScroll = this.getScroll(), isFixed = (styleString(this, 'position') == 'fixed'); return { x: bound.left.toInt() + elemScrolls.x - elemScroll.x + ((isFixed) ? 0 : htmlScroll.x) - html.clientLeft, y: bound.top.toInt() + elemScrolls.y - elemScroll.y + ((isFixed) ? 0 : htmlScroll.y) - html.clientTop }; } var element = this, position = {x: 0, y: 0}; if (isBody(this)) return position; while (element && !isBody(element)){ position.x += element.offsetLeft; position.y += element.offsetTop; if (Browser.Engine.gecko){ if (!borderBox(element)){ position.x += leftBorder(element); position.y += topBorder(element); } var parent = element.parentNode; if (parent && styleString(parent, 'overflow') != 'visible'){ position.x += leftBorder(parent); position.y += topBorder(parent); } } else if (element != this && Browser.Engine.webkit){ position.x += leftBorder(element); position.y += topBorder(element); } element = element.offsetParent; } if (Browser.Engine.gecko && !borderBox(this)){ position.x -= leftBorder(this); position.y -= topBorder(this); } return position; }, getPosition: function(relative){ if (isBody(this)) return {x: 0, y: 0}; var offset = this.getOffsets(), scroll = this.getScrolls(); var position = { x: offset.x - scroll.x, y: offset.y - scroll.y }; var relativePosition = (relative && (relative = document.id(relative))) ? relative.getPosition() : {x: 0, y: 0}; return {x: position.x - relativePosition.x, y: position.y - relativePosition.y}; }, getCoordinates: function(element){ if (isBody(this)) return this.getWindow().getCoordinates(); var position = this.getPosition(element), size = this.getSize(); var obj = { left: position.x, top: position.y, width: size.x, height: size.y }; obj.right = obj.left + obj.width; obj.bottom = obj.top + obj.height; return obj; }, computePosition: function(obj){ return { left: obj.x - styleNumber(this, 'margin-left'), top: obj.y - styleNumber(this, 'margin-top') }; }, setPosition: function(obj){ return this.setStyles(this.computePosition(obj)); } }); Native.implement([Document, Window], { getSize: function(){ if (Browser.Engine.presto || Browser.Engine.webkit){ var win = this.getWindow(); return {x: win.innerWidth, y: win.innerHeight}; } var doc = getCompatElement(this); return {x: doc.clientWidth, y: doc.clientHeight}; }, getScroll: function(){ var win = this.getWindow(), doc = getCompatElement(this); return {x: win.pageXOffset || doc.scrollLeft, y: win.pageYOffset || doc.scrollTop}; }, getScrollSize: function(){ var doc = getCompatElement(this), min = this.getSize(); return {x: Math.max(doc.scrollWidth, min.x), y: Math.max(doc.scrollHeight, min.y)}; }, getPosition: function(){ return {x: 0, y: 0}; }, getCoordinates: function(){ var size = this.getSize(); return {top: 0, left: 0, bottom: size.y, right: size.x, height: size.y, width: size.x}; } }); // private methods var styleString = Element.getComputedStyle; function styleNumber(element, style){ return styleString(element, style).toInt() || 0; }; function borderBox(element){ return styleString(element, '-moz-box-sizing') == 'border-box'; }; function topBorder(element){ return styleNumber(element, 'border-top-width'); }; function leftBorder(element){ return styleNumber(element, 'border-left-width'); }; function isBody(element){ return (/^(?:body|html)$/i).test(element.tagName); }; function getCompatElement(element){ var doc = element.getDocument(); return (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body; }; })(); //aliases Element.alias('setPosition', 'position'); //compatability Native.implement([Window, Document, Element], { getHeight: function(){ return this.getSize().y; }, getWidth: function(){ return this.getSize().x; }, getScrollTop: function(){ return this.getScroll().y; }, getScrollLeft: function(){ return this.getScroll().x; }, getScrollHeight: function(){ return this.getScrollSize().y; }, getScrollWidth: function(){ return this.getScrollSize().x; }, getTop: function(){ return this.getPosition().y; }, getLeft: function(){ return this.getPosition().x; } }); /* --- script: Selectors.js description: Adds advanced CSS-style querying capabilities for targeting HTML Elements. Includes pseudo selectors. license: MIT-style license. requires: - /Element provides: [Selectors] ... */ Native.implement([Document, Element], { getElements: function(expression, nocash){ expression = expression.split(','); var items, local = {}; for (var i = 0, l = expression.length; i < l; i++){ var selector = expression[i], elements = Selectors.Utils.search(this, selector, local); if (i != 0 && elements.item) elements = $A(elements); items = (i == 0) ? elements : (items.item) ? $A(items).concat(elements) : items.concat(elements); } return new Elements(items, {ddup: (expression.length > 1), cash: !nocash}); } }); Element.implement({ match: function(selector){ if (!selector || (selector == this)) return true; var tagid = Selectors.Utils.parseTagAndID(selector); var tag = tagid[0], id = tagid[1]; if (!Selectors.Filters.byID(this, id) || !Selectors.Filters.byTag(this, tag)) return false; var parsed = Selectors.Utils.parseSelector(selector); return (parsed) ? Selectors.Utils.filter(this, parsed, {}) : true; } }); var Selectors = {Cache: {nth: {}, parsed: {}}}; Selectors.RegExps = { id: (/#([\w-]+)/), tag: (/^(\w+|\*)/), quick: (/^(\w+|\*)$/), splitter: (/\s*([+>~\s])\s*([a-zA-Z#.*:\[])/g), combined: (/\.([\w-]+)|\[(\w+)(?:([!*^$~|]?=)(["']?)([^\4]*?)\4)?\]|:([\w-]+)(?:\(["']?(.*?)?["']?\)|$)/g) }; Selectors.Utils = { chk: function(item, uniques){ if (!uniques) return true; var uid = $uid(item); if (!uniques[uid]) return uniques[uid] = true; return false; }, parseNthArgument: function(argument){ if (Selectors.Cache.nth[argument]) return Selectors.Cache.nth[argument]; var parsed = argument.match(/^([+-]?\d*)?([a-z]+)?([+-]?\d*)?$/); if (!parsed) return false; var inta = parseInt(parsed[1], 10); var a = (inta || inta === 0) ? inta : 1; var special = parsed[2] || false; var b = parseInt(parsed[3], 10) || 0; if (a != 0){ b--; while (b < 1) b += a; while (b >= a) b -= a; } else { a = b; special = 'index'; } switch (special){ case 'n': parsed = {a: a, b: b, special: 'n'}; break; case 'odd': parsed = {a: 2, b: 0, special: 'n'}; break; case 'even': parsed = {a: 2, b: 1, special: 'n'}; break; case 'first': parsed = {a: 0, special: 'index'}; break; case 'last': parsed = {special: 'last-child'}; break; case 'only': parsed = {special: 'only-child'}; break; default: parsed = {a: (a - 1), special: 'index'}; } return Selectors.Cache.nth[argument] = parsed; }, parseSelector: function(selector){ if (Selectors.Cache.parsed[selector]) return Selectors.Cache.parsed[selector]; var m, parsed = {classes: [], pseudos: [], attributes: []}; while ((m = Selectors.RegExps.combined.exec(selector))){ var cn = m[1], an = m[2], ao = m[3], av = m[5], pn = m[6], pa = m[7]; if (cn){ parsed.classes.push(cn); } else if (pn){ var parser = Selectors.Pseudo.get(pn); if (parser) parsed.pseudos.push({parser: parser, argument: pa}); else parsed.attributes.push({name: pn, operator: '=', value: pa}); } else if (an){ parsed.attributes.push({name: an, operator: ao, value: av}); } } if (!parsed.classes.length) delete parsed.classes; if (!parsed.attributes.length) delete parsed.attributes; if (!parsed.pseudos.length) delete parsed.pseudos; if (!parsed.classes && !parsed.attributes && !parsed.pseudos) parsed = null; return Selectors.Cache.parsed[selector] = parsed; }, parseTagAndID: function(selector){ var tag = selector.match(Selectors.RegExps.tag); var id = selector.match(Selectors.RegExps.id); return [(tag) ? tag[1] : '*', (id) ? id[1] : false]; }, filter: function(item, parsed, local){ var i; if (parsed.classes){ for (i = parsed.classes.length; i--; i){ var cn = parsed.classes[i]; if (!Selectors.Filters.byClass(item, cn)) return false; } } if (parsed.attributes){ for (i = parsed.attributes.length; i--; i){ var att = parsed.attributes[i]; if (!Selectors.Filters.byAttribute(item, att.name, att.operator, att.value)) return false; } } if (parsed.pseudos){ for (i = parsed.pseudos.length; i--; i){ var psd = parsed.pseudos[i]; if (!Selectors.Filters.byPseudo(item, psd.parser, psd.argument, local)) return false; } } return true; }, getByTagAndID: function(ctx, tag, id){ if (id){ var item = (ctx.getElementById) ? ctx.getElementById(id, true) : Element.getElementById(ctx, id, true); return (item && Selectors.Filters.byTag(item, tag)) ? [item] : []; } else { return ctx.getElementsByTagName(tag); } }, search: function(self, expression, local){ var splitters = []; var selectors = expression.trim().replace(Selectors.RegExps.splitter, function(m0, m1, m2){ splitters.push(m1); return ':)' + m2; }).split(':)'); var items, filtered, item; for (var i = 0, l = selectors.length; i < l; i++){ var selector = selectors[i]; if (i == 0 && Selectors.RegExps.quick.test(selector)){ items = self.getElementsByTagName(selector); continue; } var splitter = splitters[i - 1]; var tagid = Selectors.Utils.parseTagAndID(selector); var tag = tagid[0], id = tagid[1]; if (i == 0){ items = Selectors.Utils.getByTagAndID(self, tag, id); } else { var uniques = {}, found = []; for (var j = 0, k = items.length; j < k; j++) found = Selectors.Getters[splitter](found, items[j], tag, id, uniques); items = found; } var parsed = Selectors.Utils.parseSelector(selector); if (parsed){ filtered = []; for (var m = 0, n = items.length; m < n; m++){ item = items[m]; if (Selectors.Utils.filter(item, parsed, local)) filtered.push(item); } items = filtered; } } return items; } }; Selectors.Getters = { ' ': function(found, self, tag, id, uniques){ var items = Selectors.Utils.getByTagAndID(self, tag, id); for (var i = 0, l = items.length; i < l; i++){ var item = items[i]; if (Selectors.Utils.chk(item, uniques)) found.push(item); } return found; }, '>': function(found, self, tag, id, uniques){ var children = Selectors.Utils.getByTagAndID(self, tag, id); for (var i = 0, l = children.length; i < l; i++){ var child = children[i]; if (child.parentNode == self && Selectors.Utils.chk(child, uniques)) found.push(child); } return found; }, '+': function(found, self, tag, id, uniques){ while ((self = self.nextSibling)){ if (self.nodeType == 1){ if (Selectors.Utils.chk(self, uniques) && Selectors.Filters.byTag(self, tag) && Selectors.Filters.byID(self, id)) found.push(self); break; } } return found; }, '~': function(found, self, tag, id, uniques){ while ((self = self.nextSibling)){ if (self.nodeType == 1){ if (!Selectors.Utils.chk(self, uniques)) break; if (Selectors.Filters.byTag(self, tag) && Selectors.Filters.byID(self, id)) found.push(self); } } return found; } }; Selectors.Filters = { byTag: function(self, tag){ return (tag == '*' || (self.tagName && self.tagName.toLowerCase() == tag)); }, byID: function(self, id){ return (!id || (self.id && self.id == id)); }, byClass: function(self, klass){ return (self.className && self.className.contains && self.className.contains(klass, ' ')); }, byPseudo: function(self, parser, argument, local){ return parser.call(self, argument, local); }, byAttribute: function(self, name, operator, value){ var result = Element.prototype.getProperty.call(self, name); if (!result) return (operator == '!='); if (!operator || value == undefined) return true; switch (operator){ case '=': return (result == value); case '*=': return (result.contains(value)); case '^=': return (result.substr(0, value.length) == value); case '$=': return (result.substr(result.length - value.length) == value); case '!=': return (result != value); case '~=': return result.contains(value, ' '); case '|=': return result.contains(value, '-'); } return false; } }; Selectors.Pseudo = new Hash({ // w3c pseudo selectors checked: function(){ return this.checked; }, empty: function(){ return !(this.innerText || this.textContent || '').length; }, not: function(selector){ return !Element.match(this, selector); }, contains: function(text){ return (this.innerText || this.textContent || '').contains(text); }, 'first-child': function(){ return Selectors.Pseudo.index.call(this, 0); }, 'last-child': function(){ var element = this; while ((element = element.nextSibling)){ if (element.nodeType == 1) return false; } return true; }, 'only-child': function(){ var prev = this; while ((prev = prev.previousSibling)){ if (prev.nodeType == 1) return false; } var next = this; while ((next = next.nextSibling)){ if (next.nodeType == 1) return false; } return true; }, 'nth-child': function(argument, local){ argument = (argument == undefined) ? 'n' : argument; var parsed = Selectors.Utils.parseNthArgument(argument); if (parsed.special != 'n') return Selectors.Pseudo[parsed.special].call(this, parsed.a, local); var count = 0; local.positions = local.positions || {}; var uid = $uid(this); if (!local.positions[uid]){ var self = this; while ((self = self.previousSibling)){ if (self.nodeType != 1) continue; count ++; var position = local.positions[$uid(self)]; if (position != undefined){ count = position + count; break; } } local.positions[uid] = count; } return (local.positions[uid] % parsed.a == parsed.b); }, // custom pseudo selectors index: function(index){ var element = this, count = 0; while ((element = element.previousSibling)){ if (element.nodeType == 1 && ++count > index) return false; } return (count == index); }, even: function(argument, local){ return Selectors.Pseudo['nth-child'].call(this, '2n+1', local); }, odd: function(argument, local){ return Selectors.Pseudo['nth-child'].call(this, '2n', local); }, selected: function(){ return this.selected; }, enabled: function(){ return (this.disabled === false); } }); /* --- script: DomReady.js description: Contains the custom event domready. license: MIT-style license. requires: - /Element.Event provides: [DomReady] ... */ Element.Events.domready = { onAdd: function(fn){ if (Browser.loaded) fn.call(this); } }; (function(){ var domready = function(){ if (Browser.loaded) return; Browser.loaded = true; window.fireEvent('domready'); document.fireEvent('domready'); }; window.addEvent('load', domready); if (Browser.Engine.trident){ var temp = document.createElement('div'); (function(){ ($try(function(){ temp.doScroll(); // Technique by Diego Perini return document.id(temp).inject(document.body).set('html', 'temp').dispose(); })) ? domready() : arguments.callee.delay(50); })(); } else if (Browser.Engine.webkit && Browser.Engine.version < 525){ (function(){ (['loaded', 'complete'].contains(document.readyState)) ? domready() : arguments.callee.delay(50); })(); } else { document.addEvent('DOMContentLoaded', domready); } })(); /* --- script: JSON.js description: JSON encoder and decoder. license: MIT-style license. See Also: requires: - /Array - /String - /Number - /Function - /Hash provides: [JSON] ... */ var JSON = new Hash(this.JSON && { stringify: JSON.stringify, parse: JSON.parse }).extend({ $specialChars: {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\'}, $replaceChars: function(chr){ return JSON.$specialChars[chr] || '\\u00' + Math.floor(chr.charCodeAt() / 16).toString(16) + (chr.charCodeAt() % 16).toString(16); }, encode: function(obj){ switch ($type(obj)){ case 'string': return '"' + obj.replace(/[\x00-\x1f\\"]/g, JSON.$replaceChars) + '"'; case 'array': return '[' + String(obj.map(JSON.encode).clean()) + ']'; case 'object': case 'hash': var string = []; Hash.each(obj, function(value, key){ var json = JSON.encode(value); if (json) string.push(JSON.encode(key) + ':' + json); }); return '{' + string + '}'; case 'number': case 'boolean': return String(obj); case false: return 'null'; } return null; }, decode: function(string, secure){ if ($type(string) != 'string' || !string.length) return null; if (secure && !(/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(string.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''))) return null; return eval('(' + string + ')'); } }); Native.implement([Hash, Array, String, Number], { toJSON: function(){ return JSON.encode(this); } }); /* --- script: Cookie.js description: Class for creating, reading, and deleting browser Cookies. license: MIT-style license. credits: - Based on the functions by Peter-Paul Koch (http://quirksmode.org). requires: - /Options provides: [Cookie] ... */ var Cookie = new Class({ Implements: Options, options: { path: false, domain: false, duration: false, secure: false, document: document }, initialize: function(key, options){ this.key = key; this.setOptions(options); }, write: function(value){ value = encodeURIComponent(value); if (this.options.domain) value += '; domain=' + this.options.domain; if (this.options.path) value += '; path=' + this.options.path; if (this.options.duration){ var date = new Date(); date.setTime(date.getTime() + this.options.duration * 24 * 60 * 60 * 1000); value += '; expires=' + date.toGMTString(); } if (this.options.secure) value += '; secure'; this.options.document.cookie = this.key + '=' + value; return this; }, read: function(){ var value = this.options.document.cookie.match('(?:^|;)\\s*' + this.key.escapeRegExp() + '=([^;]*)'); return (value) ? decodeURIComponent(value[1]) : null; }, dispose: function(){ new Cookie(this.key, $merge(this.options, {duration: -1})).write(''); return this; } }); Cookie.write = function(key, value, options){ return new Cookie(key, options).write(value); }; Cookie.read = function(key){ return new Cookie(key).read(); }; Cookie.dispose = function(key, options){ return new Cookie(key, options).dispose(); }; /* --- script: Swiff.js description: Wrapper for embedding SWF movies. Supports External Interface Communication. license: MIT-style license. credits: - Flash detection & Internet Explorer + Flash Player 9 fix inspired by SWFObject. requires: - /Options - /$util provides: [Swiff] ... */ var Swiff = new Class({ Implements: [Options], options: { id: null, height: 1, width: 1, container: null, properties: {}, params: { quality: 'high', allowScriptAccess: 'always', wMode: 'transparent', swLiveConnect: true }, callBacks: {}, vars: {} }, toElement: function(){ return this.object; }, initialize: function(path, options){ this.instance = 'Swiff_' + $time(); this.setOptions(options); options = this.options; var id = this.id = options.id || this.instance; var container = document.id(options.container); Swiff.CallBacks[this.instance] = {}; var params = options.params, vars = options.vars, callBacks = options.callBacks; var properties = $extend({height: options.height, width: options.width}, options.properties); var self = this; for (var callBack in callBacks){ Swiff.CallBacks[this.instance][callBack] = (function(option){ return function(){ return option.apply(self.object, arguments); }; })(callBacks[callBack]); vars[callBack] = 'Swiff.CallBacks.' + this.instance + '.' + callBack; } params.flashVars = Hash.toQueryString(vars); if (Browser.Engine.trident){ properties.classid = 'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000'; params.movie = path; } else { properties.type = 'application/x-shockwave-flash'; properties.data = path; } var build = ''; } build += ''; this.object = ((container) ? container.empty() : new Element('div')).set('html', build).firstChild; }, replaces: function(element){ element = document.id(element, true); element.parentNode.replaceChild(this.toElement(), element); return this; }, inject: function(element){ document.id(element, true).appendChild(this.toElement()); return this; }, remote: function(){ return Swiff.remote.apply(Swiff, [this.toElement()].extend(arguments)); } }); Swiff.CallBacks = {}; Swiff.remote = function(obj, fn){ var rs = obj.CallFunction('' + __flash__argumentsToXML(arguments, 2) + ''); return eval(rs); }; /* --- script: Fx.js description: Contains the basic animation logic to be extended by all other Fx Classes. license: MIT-style license. requires: - /Chain - /Events - /Options provides: [Fx] ... */ var Fx = new Class({ Implements: [Chain, Events, Options], options: { /* onStart: $empty, onCancel: $empty, onComplete: $empty, */ fps: 50, unit: false, duration: 500, link: 'ignore' }, initialize: function(options){ this.subject = this.subject || this; this.setOptions(options); this.options.duration = Fx.Durations[this.options.duration] || this.options.duration.toInt(); var wait = this.options.wait; if (wait === false) this.options.link = 'cancel'; }, getTransition: function(){ return function(p){ return -(Math.cos(Math.PI * p) - 1) / 2; }; }, step: function(){ var time = $time(); if (time < this.time + this.options.duration){ var delta = this.transition((time - this.time) / this.options.duration); this.set(this.compute(this.from, this.to, delta)); } else { this.set(this.compute(this.from, this.to, 1)); this.complete(); } }, set: function(now){ return now; }, compute: function(from, to, delta){ return Fx.compute(from, to, delta); }, check: function(){ if (!this.timer) return true; switch (this.options.link){ case 'cancel': this.cancel(); return true; case 'chain': this.chain(this.caller.bind(this, arguments)); return false; } return false; }, start: function(from, to){ if (!this.check(from, to)) return this; this.from = from; this.to = to; this.time = 0; this.transition = this.getTransition(); this.startTimer(); this.onStart(); return this; }, complete: function(){ if (this.stopTimer()) this.onComplete(); return this; }, cancel: function(){ if (this.stopTimer()) this.onCancel(); return this; }, onStart: function(){ this.fireEvent('start', this.subject); }, onComplete: function(){ this.fireEvent('complete', this.subject); if (!this.callChain()) this.fireEvent('chainComplete', this.subject); }, onCancel: function(){ this.fireEvent('cancel', this.subject).clearChain(); }, pause: function(){ this.stopTimer(); return this; }, resume: function(){ this.startTimer(); return this; }, stopTimer: function(){ if (!this.timer) return false; this.time = $time() - this.time; this.timer = $clear(this.timer); return true; }, startTimer: function(){ if (this.timer) return false; this.time = $time() - this.time; this.timer = this.step.periodical(Math.round(1000 / this.options.fps), this); return true; } }); Fx.compute = function(from, to, delta){ return (to - from) * delta + from; }; Fx.Durations = {'short': 250, 'normal': 500, 'long': 1000}; /* --- script: Fx.CSS.js description: Contains the CSS animation logic. Used by Fx.Tween, Fx.Morph, Fx.Elements. license: MIT-style license. requires: - /Fx - /Element.Style provides: [Fx.CSS] ... */ Fx.CSS = new Class({ Extends: Fx, //prepares the base from/to object prepare: function(element, property, values){ values = $splat(values); var values1 = values[1]; if (!$chk(values1)){ values[1] = values[0]; values[0] = element.getStyle(property); } var parsed = values.map(this.parse); return {from: parsed[0], to: parsed[1]}; }, //parses a value into an array parse: function(value){ value = $lambda(value)(); value = (typeof value == 'string') ? value.split(' ') : $splat(value); return value.map(function(val){ val = String(val); var found = false; Fx.CSS.Parsers.each(function(parser, key){ if (found) return; var parsed = parser.parse(val); if ($chk(parsed)) found = {value: parsed, parser: parser}; }); found = found || {value: val, parser: Fx.CSS.Parsers.String}; return found; }); }, //computes by a from and to prepared objects, using their parsers. compute: function(from, to, delta){ var computed = []; (Math.min(from.length, to.length)).times(function(i){ computed.push({value: from[i].parser.compute(from[i].value, to[i].value, delta), parser: from[i].parser}); }); computed.$family = {name: 'fx:css:value'}; return computed; }, //serves the value as settable serve: function(value, unit){ if ($type(value) != 'fx:css:value') value = this.parse(value); var returned = []; value.each(function(bit){ returned = returned.concat(bit.parser.serve(bit.value, unit)); }); return returned; }, //renders the change to an element render: function(element, property, value, unit){ element.setStyle(property, this.serve(value, unit)); }, //searches inside the page css to find the values for a selector search: function(selector){ if (Fx.CSS.Cache[selector]) return Fx.CSS.Cache[selector]; var to = {}; Array.each(document.styleSheets, function(sheet, j){ var href = sheet.href; if (href && href.contains('://') && !href.contains(document.domain)) return; var rules = sheet.rules || sheet.cssRules; Array.each(rules, function(rule, i){ if (!rule.style) return; var selectorText = (rule.selectorText) ? rule.selectorText.replace(/^\w+/, function(m){ return m.toLowerCase(); }) : null; if (!selectorText || !selectorText.test('^' + selector + '$')) return; Element.Styles.each(function(value, style){ if (!rule.style[style] || Element.ShortStyles[style]) return; value = String(rule.style[style]); to[style] = (value.test(/^rgb/)) ? value.rgbToHex() : value; }); }); }); return Fx.CSS.Cache[selector] = to; } }); Fx.CSS.Cache = {}; Fx.CSS.Parsers = new Hash({ Color: { parse: function(value){ if (value.match(/^#[0-9a-f]{3,6}$/i)) return value.hexToRgb(true); return ((value = value.match(/(\d+),\s*(\d+),\s*(\d+)/))) ? [value[1], value[2], value[3]] : false; }, compute: function(from, to, delta){ return from.map(function(value, i){ return Math.round(Fx.compute(from[i], to[i], delta)); }); }, serve: function(value){ return value.map(Number); } }, Number: { parse: parseFloat, compute: Fx.compute, serve: function(value, unit){ return (unit) ? value + unit : value; } }, String: { parse: $lambda(false), compute: $arguments(1), serve: $arguments(0) } }); /* --- script: Fx.Tween.js description: Formerly Fx.Style, effect to transition any CSS property for an element. license: MIT-style license. requires: - /Fx.CSS provides: [Fx.Tween, Element.fade, Element.highlight] ... */ Fx.Tween = new Class({ Extends: Fx.CSS, initialize: function(element, options){ this.element = this.subject = document.id(element); this.parent(options); }, set: function(property, now){ if (arguments.length == 1){ now = property; property = this.property || this.options.property; } this.render(this.element, property, now, this.options.unit); return this; }, start: function(property, from, to){ if (!this.check(property, from, to)) return this; var args = Array.flatten(arguments); this.property = this.options.property || args.shift(); var parsed = this.prepare(this.element, this.property, args); return this.parent(parsed.from, parsed.to); } }); Element.Properties.tween = { set: function(options){ var tween = this.retrieve('tween'); if (tween) tween.cancel(); return this.eliminate('tween').store('tween:options', $extend({link: 'cancel'}, options)); }, get: function(options){ if (options || !this.retrieve('tween')){ if (options || !this.retrieve('tween:options')) this.set('tween', options); this.store('tween', new Fx.Tween(this, this.retrieve('tween:options'))); } return this.retrieve('tween'); } }; Element.implement({ tween: function(property, from, to){ this.get('tween').start(arguments); return this; }, fade: function(how){ var fade = this.get('tween'), o = 'opacity', toggle; how = $pick(how, 'toggle'); switch (how){ case 'in': fade.start(o, 1); break; case 'out': fade.start(o, 0); break; case 'show': fade.set(o, 1); break; case 'hide': fade.set(o, 0); break; case 'toggle': var flag = this.retrieve('fade:flag', this.get('opacity') == 1); fade.start(o, (flag) ? 0 : 1); this.store('fade:flag', !flag); toggle = true; break; default: fade.start(o, arguments); } if (!toggle) this.eliminate('fade:flag'); return this; }, highlight: function(start, end){ if (!end){ end = this.retrieve('highlight:original', this.getStyle('background-color')); end = (end == 'transparent') ? '#fff' : end; } var tween = this.get('tween'); tween.start('background-color', start || '#ffff88', end).chain(function(){ this.setStyle('background-color', this.retrieve('highlight:original')); tween.callChain(); }.bind(this)); return this; } }); /* --- script: Fx.Morph.js description: Formerly Fx.Styles, effect to transition any number of CSS properties for an element using an object of rules, or CSS based selector rules. license: MIT-style license. requires: - /Fx.CSS provides: [Fx.Morph] ... */ Fx.Morph = new Class({ Extends: Fx.CSS, initialize: function(element, options){ this.element = this.subject = document.id(element); this.parent(options); }, set: function(now){ if (typeof now == 'string') now = this.search(now); for (var p in now) this.render(this.element, p, now[p], this.options.unit); return this; }, compute: function(from, to, delta){ var now = {}; for (var p in from) now[p] = this.parent(from[p], to[p], delta); return now; }, start: function(properties){ if (!this.check(properties)) return this; if (typeof properties == 'string') properties = this.search(properties); var from = {}, to = {}; for (var p in properties){ var parsed = this.prepare(this.element, p, properties[p]); from[p] = parsed.from; to[p] = parsed.to; } return this.parent(from, to); } }); Element.Properties.morph = { set: function(options){ var morph = this.retrieve('morph'); if (morph) morph.cancel(); return this.eliminate('morph').store('morph:options', $extend({link: 'cancel'}, options)); }, get: function(options){ if (options || !this.retrieve('morph')){ if (options || !this.retrieve('morph:options')) this.set('morph', options); this.store('morph', new Fx.Morph(this, this.retrieve('morph:options'))); } return this.retrieve('morph'); } }; Element.implement({ morph: function(props){ this.get('morph').start(props); return this; } }); /* --- script: Fx.Transitions.js description: Contains a set of advanced transitions to be used with any of the Fx Classes. license: MIT-style license. credits: - Easing Equations by Robert Penner, , modified and optimized to be used with MooTools. requires: - /Fx provides: [Fx.Transitions] ... */ Fx.implement({ getTransition: function(){ var trans = this.options.transition || Fx.Transitions.Sine.easeInOut; if (typeof trans == 'string'){ var data = trans.split(':'); trans = Fx.Transitions; trans = trans[data[0]] || trans[data[0].capitalize()]; if (data[1]) trans = trans['ease' + data[1].capitalize() + (data[2] ? data[2].capitalize() : '')]; } return trans; } }); Fx.Transition = function(transition, params){ params = $splat(params); return $extend(transition, { easeIn: function(pos){ return transition(pos, params); }, easeOut: function(pos){ return 1 - transition(1 - pos, params); }, easeInOut: function(pos){ return (pos <= 0.5) ? transition(2 * pos, params) / 2 : (2 - transition(2 * (1 - pos), params)) / 2; } }); }; Fx.Transitions = new Hash({ linear: $arguments(0) }); Fx.Transitions.extend = function(transitions){ for (var transition in transitions) Fx.Transitions[transition] = new Fx.Transition(transitions[transition]); }; Fx.Transitions.extend({ Pow: function(p, x){ return Math.pow(p, x[0] || 6); }, Expo: function(p){ return Math.pow(2, 8 * (p - 1)); }, Circ: function(p){ return 1 - Math.sin(Math.acos(p)); }, Sine: function(p){ return 1 - Math.sin((1 - p) * Math.PI / 2); }, Back: function(p, x){ x = x[0] || 1.618; return Math.pow(p, 2) * ((x + 1) * p - x); }, Bounce: function(p){ var value; for (var a = 0, b = 1; 1; a += b, b /= 2){ if (p >= (7 - 4 * a) / 11){ value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2); break; } } return value; }, Elastic: function(p, x){ return Math.pow(2, 10 * --p) * Math.cos(20 * p * Math.PI * (x[0] || 1) / 3); } }); ['Quad', 'Cubic', 'Quart', 'Quint'].each(function(transition, i){ Fx.Transitions[transition] = new Fx.Transition(function(p){ return Math.pow(p, [i + 2]); }); }); /* --- script: Request.js description: Powerful all purpose Request Class. Uses XMLHTTPRequest. license: MIT-style license. requires: - /Element - /Chain - /Events - /Options - /Browser provides: [Request] ... */ var Request = new Class({ Implements: [Chain, Events, Options], options: {/* onRequest: $empty, onComplete: $empty, onCancel: $empty, onSuccess: $empty, onFailure: $empty, onException: $empty,*/ url: '', data: '', headers: { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' }, async: true, format: false, method: 'post', link: 'ignore', isSuccess: null, emulation: true, urlEncoded: true, encoding: 'utf-8', evalScripts: false, evalResponse: false, noCache: false }, initialize: function(options){ this.xhr = new Browser.Request(); this.setOptions(options); this.options.isSuccess = this.options.isSuccess || this.isSuccess; this.headers = new Hash(this.options.headers); }, onStateChange: function(){ if (this.xhr.readyState != 4 || !this.running) return; this.running = false; this.status = 0; $try(function(){ this.status = this.xhr.status; }.bind(this)); this.xhr.onreadystatechange = $empty; if (this.options.isSuccess.call(this, this.status)){ this.response = {text: this.xhr.responseText, xml: this.xhr.responseXML}; this.success(this.response.text, this.response.xml); } else { this.response = {text: null, xml: null}; this.failure(); } }, isSuccess: function(){ return ((this.status >= 200) && (this.status < 300)); }, processScripts: function(text){ if (this.options.evalResponse || (/(ecma|java)script/).test(this.getHeader('Content-type'))) return $exec(text); return text.stripScripts(this.options.evalScripts); }, success: function(text, xml){ this.onSuccess(this.processScripts(text), xml); }, onSuccess: function(){ this.fireEvent('complete', arguments).fireEvent('success', arguments).callChain(); }, failure: function(){ this.onFailure(); }, onFailure: function(){ this.fireEvent('complete').fireEvent('failure', this.xhr); }, setHeader: function(name, value){ this.headers.set(name, value); return this; }, getHeader: function(name){ return $try(function(){ return this.xhr.getResponseHeader(name); }.bind(this)); }, check: function(){ if (!this.running) return true; switch (this.options.link){ case 'cancel': this.cancel(); return true; case 'chain': this.chain(this.caller.bind(this, arguments)); return false; } return false; }, send: function(options){ if (!this.check(options)) return this; this.running = true; var type = $type(options); if (type == 'string' || type == 'element') options = {data: options}; var old = this.options; options = $extend({data: old.data, url: old.url, method: old.method}, options); var data = options.data, url = String(options.url), method = options.method.toLowerCase(); switch ($type(data)){ case 'element': data = document.id(data).toQueryString(); break; case 'object': case 'hash': data = Hash.toQueryString(data); } if (this.options.format){ var format = 'format=' + this.options.format; data = (data) ? format + '&' + data : format; } if (this.options.emulation && !['get', 'post'].contains(method)){ var _method = '_method=' + method; data = (data) ? _method + '&' + data : _method; method = 'post'; } if (this.options.urlEncoded && method == 'post'){ var encoding = (this.options.encoding) ? '; charset=' + this.options.encoding : ''; this.headers.set('Content-type', 'application/x-www-form-urlencoded' + encoding); } if (this.options.noCache){ var noCache = 'noCache=' + new Date().getTime(); data = (data) ? noCache + '&' + data : noCache; } var trimPosition = url.lastIndexOf('/'); if (trimPosition > -1 && (trimPosition = url.indexOf('#')) > -1) url = url.substr(0, trimPosition); if (data && method == 'get'){ url = url + (url.contains('?') ? '&' : '?') + data; data = null; } this.xhr.open(method.toUpperCase(), url, this.options.async); this.xhr.onreadystatechange = this.onStateChange.bind(this); this.headers.each(function(value, key){ try { this.xhr.setRequestHeader(key, value); } catch (e){ this.fireEvent('exception', [key, value]); } }, this); this.fireEvent('request'); this.xhr.send(data); if (!this.options.async) this.onStateChange(); return this; }, cancel: function(){ if (!this.running) return this; this.running = false; this.xhr.abort(); this.xhr.onreadystatechange = $empty; this.xhr = new Browser.Request(); this.fireEvent('cancel'); return this; } }); (function(){ var methods = {}; ['get', 'post', 'put', 'delete', 'GET', 'POST', 'PUT', 'DELETE'].each(function(method){ methods[method] = function(){ var params = Array.link(arguments, {url: String.type, data: $defined}); return this.send($extend(params, {method: method})); }; }); Request.implement(methods); })(); Element.Properties.send = { set: function(options){ var send = this.retrieve('send'); if (send) send.cancel(); return this.eliminate('send').store('send:options', $extend({ data: this, link: 'cancel', method: this.get('method') || 'post', url: this.get('action') }, options)); }, get: function(options){ if (options || !this.retrieve('send')){ if (options || !this.retrieve('send:options')) this.set('send', options); this.store('send', new Request(this.retrieve('send:options'))); } return this.retrieve('send'); } }; Element.implement({ send: function(url){ var sender = this.get('send'); sender.send({data: this, url: url || sender.options.url}); return this; } }); /* --- script: Request.HTML.js description: Extends the basic Request Class with additional methods for interacting with HTML responses. license: MIT-style license. requires: - /Request - /Element provides: [Request.HTML] ... */ Request.HTML = new Class({ Extends: Request, options: { update: false, append: false, evalScripts: true, filter: false }, processHTML: function(text){ var match = text.match(/]*>([\s\S]*?)<\/body>/i); text = (match) ? match[1] : text; var container = new Element('div'); return $try(function(){ var root = '' + text + '', doc; if (Browser.Engine.trident){ doc = new ActiveXObject('Microsoft.XMLDOM'); doc.async = false; doc.loadXML(root); } else { doc = new DOMParser().parseFromString(root, 'text/xml'); } root = doc.getElementsByTagName('root')[0]; if (!root) return null; for (var i = 0, k = root.childNodes.length; i < k; i++){ var child = Element.clone(root.childNodes[i], true, true); if (child) container.grab(child); } return container; }) || container.set('html', text); }, success: function(text){ var options = this.options, response = this.response; response.html = text.stripScripts(function(script){ response.javascript = script; }); var temp = this.processHTML(response.html); response.tree = temp.childNodes; response.elements = temp.getElements('*'); if (options.filter) response.tree = response.elements.filter(options.filter); if (options.update) document.id(options.update).empty().set('html', response.html); else if (options.append) document.id(options.append).adopt(temp.getChildren()); if (options.evalScripts) $exec(response.javascript); this.onSuccess(response.tree, response.elements, response.html, response.javascript); } }); Element.Properties.load = { set: function(options){ var load = this.retrieve('load'); if (load) load.cancel(); return this.eliminate('load').store('load:options', $extend({data: this, link: 'cancel', update: this, method: 'get'}, options)); }, get: function(options){ if (options || ! this.retrieve('load')){ if (options || !this.retrieve('load:options')) this.set('load', options); this.store('load', new Request.HTML(this.retrieve('load:options'))); } return this.retrieve('load'); } }; Element.implement({ load: function(){ this.get('load').send(Array.link(arguments, {data: Object.type, url: String.type})); return this; } }); /* --- script: Request.JSON.js description: Extends the basic Request Class with additional methods for sending and receiving JSON data. license: MIT-style license. requires: - /Request JSON provides: [Request.HTML] ... */ Request.JSON = new Class({ Extends: Request, options: { secure: true }, initialize: function(options){ this.parent(options); this.headers.extend({'Accept': 'application/json', 'X-Request': 'JSON'}); }, success: function(text){ this.response.json = JSON.decode(text, this.options.secure); this.onSuccess(this.response.json, text); } }); faye-1.4.0/examples/public/prototype.js000066400000000000000000004211161371104377200201510ustar00rootroot00000000000000/* Prototype JavaScript framework, version 1.6.1 * (c) 2005-2009 Sam Stephenson * * Prototype is freely distributable under the terms of an MIT-style license. * For details, see the Prototype web site: http://www.prototypejs.org/ * *--------------------------------------------------------------------------*/ var Prototype = { Version: '1.6.1', Browser: (function(){ var ua = navigator.userAgent; var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]'; return { IE: !!window.attachEvent && !isOpera, Opera: isOpera, WebKit: ua.indexOf('AppleWebKit/') > -1, Gecko: ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1, MobileSafari: /Apple.*Mobile.*Safari/.test(ua) } })(), BrowserFeatures: { XPath: !!document.evaluate, SelectorsAPI: !!document.querySelector, ElementExtensions: (function() { var constructor = window.Element || window.HTMLElement; return !!(constructor && constructor.prototype); })(), SpecificElementExtensions: (function() { if (typeof window.HTMLDivElement !== 'undefined') return true; var div = document.createElement('div'); var form = document.createElement('form'); var isSupported = false; if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) { isSupported = true; } div = form = null; return isSupported; })() }, ScriptFragment: ']*>([\\S\\s]*?)<\/script>', JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, emptyFunction: function() { }, K: function(x) { return x } }; if (Prototype.Browser.MobileSafari) Prototype.BrowserFeatures.SpecificElementExtensions = false; var Abstract = { }; var Try = { these: function() { var returnValue; for (var i = 0, length = arguments.length; i < length; i++) { var lambda = arguments[i]; try { returnValue = lambda(); break; } catch (e) { } } return returnValue; } }; /* Based on Alex Arnell's inheritance implementation. */ var Class = (function() { function subclass() {}; function create() { var parent = null, properties = $A(arguments); if (Object.isFunction(properties[0])) parent = properties.shift(); function klass() { this.initialize.apply(this, arguments); } Object.extend(klass, Class.Methods); klass.superclass = parent; klass.subclasses = []; if (parent) { subclass.prototype = parent.prototype; klass.prototype = new subclass; parent.subclasses.push(klass); } for (var i = 0; i < properties.length; i++) klass.addMethods(properties[i]); if (!klass.prototype.initialize) klass.prototype.initialize = Prototype.emptyFunction; klass.prototype.constructor = klass; return klass; } function addMethods(source) { var ancestor = this.superclass && this.superclass.prototype; var properties = Object.keys(source); if (!Object.keys({ toString: true }).length) { if (source.toString != Object.prototype.toString) properties.push("toString"); if (source.valueOf != Object.prototype.valueOf) properties.push("valueOf"); } for (var i = 0, length = properties.length; i < length; i++) { var property = properties[i], value = source[property]; if (ancestor && Object.isFunction(value) && value.argumentNames().first() == "$super") { var method = value; value = (function(m) { return function() { return ancestor[m].apply(this, arguments); }; })(property).wrap(method); value.valueOf = method.valueOf.bind(method); value.toString = method.toString.bind(method); } this.prototype[property] = value; } return this; } return { create: create, Methods: { addMethods: addMethods } }; })(); (function() { var _toString = Object.prototype.toString; function extend(destination, source) { for (var property in source) destination[property] = source[property]; return destination; } function inspect(object) { try { if (isUndefined(object)) return 'undefined'; if (object === null) return 'null'; return object.inspect ? object.inspect() : String(object); } catch (e) { if (e instanceof RangeError) return '...'; throw e; } } function toJSON(object) { var type = typeof object; switch (type) { case 'undefined': case 'function': case 'unknown': return; case 'boolean': return object.toString(); } if (object === null) return 'null'; if (object.toJSON) return object.toJSON(); if (isElement(object)) return; var results = []; for (var property in object) { var value = toJSON(object[property]); if (!isUndefined(value)) results.push(property.toJSON() + ': ' + value); } return '{' + results.join(', ') + '}'; } function toQueryString(object) { return $H(object).toQueryString(); } function toHTML(object) { return object && object.toHTML ? object.toHTML() : String.interpret(object); } function keys(object) { var results = []; for (var property in object) results.push(property); return results; } function values(object) { var results = []; for (var property in object) results.push(object[property]); return results; } function clone(object) { return extend({ }, object); } function isElement(object) { return !!(object && object.nodeType == 1); } function isArray(object) { return _toString.call(object) == "[object Array]"; } function isHash(object) { return object instanceof Hash; } function isFunction(object) { return typeof object === "function"; } function isString(object) { return _toString.call(object) == "[object String]"; } function isNumber(object) { return _toString.call(object) == "[object Number]"; } function isUndefined(object) { return typeof object === "undefined"; } extend(Object, { extend: extend, inspect: inspect, toJSON: toJSON, toQueryString: toQueryString, toHTML: toHTML, keys: keys, values: values, clone: clone, isElement: isElement, isArray: isArray, isHash: isHash, isFunction: isFunction, isString: isString, isNumber: isNumber, isUndefined: isUndefined }); })(); Object.extend(Function.prototype, (function() { var slice = Array.prototype.slice; function update(array, args) { var arrayLength = array.length, length = args.length; while (length--) array[arrayLength + length] = args[length]; return array; } function merge(array, args) { array = slice.call(array, 0); return update(array, args); } function argumentNames() { var names = this.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1] .replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '') .replace(/\s+/g, '').split(','); return names.length == 1 && !names[0] ? [] : names; } function bind(context) { if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this; var __method = this, args = slice.call(arguments, 1); return function() { var a = merge(args, arguments); return __method.apply(context, a); } } function bindAsEventListener(context) { var __method = this, args = slice.call(arguments, 1); return function(event) { var a = update([event || window.event], args); return __method.apply(context, a); } } function curry() { if (!arguments.length) return this; var __method = this, args = slice.call(arguments, 0); return function() { var a = merge(args, arguments); return __method.apply(this, a); } } function delay(timeout) { var __method = this, args = slice.call(arguments, 1); timeout = timeout * 1000 return window.setTimeout(function() { return __method.apply(__method, args); }, timeout); } function defer() { var args = update([0.01], arguments); return this.delay.apply(this, args); } function wrap(wrapper) { var __method = this; return function() { var a = update([__method.bind(this)], arguments); return wrapper.apply(this, a); } } function methodize() { if (this._methodized) return this._methodized; var __method = this; return this._methodized = function() { var a = update([this], arguments); return __method.apply(null, a); }; } return { argumentNames: argumentNames, bind: bind, bindAsEventListener: bindAsEventListener, curry: curry, delay: delay, defer: defer, wrap: wrap, methodize: methodize } })()); Date.prototype.toJSON = function() { return '"' + this.getUTCFullYear() + '-' + (this.getUTCMonth() + 1).toPaddedString(2) + '-' + this.getUTCDate().toPaddedString(2) + 'T' + this.getUTCHours().toPaddedString(2) + ':' + this.getUTCMinutes().toPaddedString(2) + ':' + this.getUTCSeconds().toPaddedString(2) + 'Z"'; }; RegExp.prototype.match = RegExp.prototype.test; RegExp.escape = function(str) { return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); }; var PeriodicalExecuter = Class.create({ initialize: function(callback, frequency) { this.callback = callback; this.frequency = frequency; this.currentlyExecuting = false; this.registerCallback(); }, registerCallback: function() { this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); }, execute: function() { this.callback(this); }, stop: function() { if (!this.timer) return; clearInterval(this.timer); this.timer = null; }, onTimerEvent: function() { if (!this.currentlyExecuting) { try { this.currentlyExecuting = true; this.execute(); this.currentlyExecuting = false; } catch(e) { this.currentlyExecuting = false; throw e; } } } }); Object.extend(String, { interpret: function(value) { return value == null ? '' : String(value); }, specialChar: { '\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '\\': '\\\\' } }); Object.extend(String.prototype, (function() { function prepareReplacement(replacement) { if (Object.isFunction(replacement)) return replacement; var template = new Template(replacement); return function(match) { return template.evaluate(match) }; } function gsub(pattern, replacement) { var result = '', source = this, match; replacement = prepareReplacement(replacement); if (Object.isString(pattern)) pattern = RegExp.escape(pattern); if (!(pattern.length || pattern.source)) { replacement = replacement(''); return replacement + source.split('').join(replacement) + replacement; } while (source.length > 0) { if (match = source.match(pattern)) { result += source.slice(0, match.index); result += String.interpret(replacement(match)); source = source.slice(match.index + match[0].length); } else { result += source, source = ''; } } return result; } function sub(pattern, replacement, count) { replacement = prepareReplacement(replacement); count = Object.isUndefined(count) ? 1 : count; return this.gsub(pattern, function(match) { if (--count < 0) return match[0]; return replacement(match); }); } function scan(pattern, iterator) { this.gsub(pattern, iterator); return String(this); } function truncate(length, truncation) { length = length || 30; truncation = Object.isUndefined(truncation) ? '...' : truncation; return this.length > length ? this.slice(0, length - truncation.length) + truncation : String(this); } function strip() { return this.replace(/^\s+/, '').replace(/\s+$/, ''); } function stripTags() { return this.replace(/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi, ''); } function stripScripts() { return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); } function extractScripts() { var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); return (this.match(matchAll) || []).map(function(scriptTag) { return (scriptTag.match(matchOne) || ['', ''])[1]; }); } function evalScripts() { return this.extractScripts().map(function(script) { return eval(script) }); } function escapeHTML() { return this.replace(/&/g,'&').replace(//g,'>'); } function unescapeHTML() { return this.stripTags().replace(/</g,'<').replace(/>/g,'>').replace(/&/g,'&'); } function toQueryParams(separator) { var match = this.strip().match(/([^?#]*)(#.*)?$/); if (!match) return { }; return match[1].split(separator || '&').inject({ }, function(hash, pair) { if ((pair = pair.split('='))[0]) { var key = decodeURIComponent(pair.shift()); var value = pair.length > 1 ? pair.join('=') : pair[0]; if (value != undefined) value = decodeURIComponent(value); if (key in hash) { if (!Object.isArray(hash[key])) hash[key] = [hash[key]]; hash[key].push(value); } else hash[key] = value; } return hash; }); } function toArray() { return this.split(''); } function succ() { return this.slice(0, this.length - 1) + String.fromCharCode(this.charCodeAt(this.length - 1) + 1); } function times(count) { return count < 1 ? '' : new Array(count + 1).join(this); } function camelize() { var parts = this.split('-'), len = parts.length; if (len == 1) return parts[0]; var camelized = this.charAt(0) == '-' ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1) : parts[0]; for (var i = 1; i < len; i++) camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1); return camelized; } function capitalize() { return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); } function underscore() { return this.replace(/::/g, '/') .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2') .replace(/([a-z\d])([A-Z])/g, '$1_$2') .replace(/-/g, '_') .toLowerCase(); } function dasherize() { return this.replace(/_/g, '-'); } function inspect(useDoubleQuotes) { var escapedString = this.replace(/[\x00-\x1f\\]/g, function(character) { if (character in String.specialChar) { return String.specialChar[character]; } return '\\u00' + character.charCodeAt().toPaddedString(2, 16); }); if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; return "'" + escapedString.replace(/'/g, '\\\'') + "'"; } function toJSON() { return this.inspect(true); } function unfilterJSON(filter) { return this.replace(filter || Prototype.JSONFilter, '$1'); } function isJSON() { var str = this; if (str.blank()) return false; str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''); return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str); } function evalJSON(sanitize) { var json = this.unfilterJSON(); try { if (!sanitize || json.isJSON()) return eval('(' + json + ')'); } catch (e) { } throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); } function include(pattern) { return this.indexOf(pattern) > -1; } function startsWith(pattern) { return this.indexOf(pattern) === 0; } function endsWith(pattern) { var d = this.length - pattern.length; return d >= 0 && this.lastIndexOf(pattern) === d; } function empty() { return this == ''; } function blank() { return /^\s*$/.test(this); } function interpolate(object, pattern) { return new Template(this, pattern).evaluate(object); } return { gsub: gsub, sub: sub, scan: scan, truncate: truncate, strip: String.prototype.trim ? String.prototype.trim : strip, stripTags: stripTags, stripScripts: stripScripts, extractScripts: extractScripts, evalScripts: evalScripts, escapeHTML: escapeHTML, unescapeHTML: unescapeHTML, toQueryParams: toQueryParams, parseQuery: toQueryParams, toArray: toArray, succ: succ, times: times, camelize: camelize, capitalize: capitalize, underscore: underscore, dasherize: dasherize, inspect: inspect, toJSON: toJSON, unfilterJSON: unfilterJSON, isJSON: isJSON, evalJSON: evalJSON, include: include, startsWith: startsWith, endsWith: endsWith, empty: empty, blank: blank, interpolate: interpolate }; })()); var Template = Class.create({ initialize: function(template, pattern) { this.template = template.toString(); this.pattern = pattern || Template.Pattern; }, evaluate: function(object) { if (object && Object.isFunction(object.toTemplateReplacements)) object = object.toTemplateReplacements(); return this.template.gsub(this.pattern, function(match) { if (object == null) return (match[1] + ''); var before = match[1] || ''; if (before == '\\') return match[2]; var ctx = object, expr = match[3]; var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; match = pattern.exec(expr); if (match == null) return before; while (match != null) { var comp = match[1].startsWith('[') ? match[2].replace(/\\\\]/g, ']') : match[1]; ctx = ctx[comp]; if (null == ctx || '' == match[3]) break; expr = expr.substring('[' == match[3] ? match[1].length : match[0].length); match = pattern.exec(expr); } return before + String.interpret(ctx); }); } }); Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; var $break = { }; var Enumerable = (function() { function each(iterator, context) { var index = 0; try { this._each(function(value) { iterator.call(context, value, index++); }); } catch (e) { if (e != $break) throw e; } return this; } function eachSlice(number, iterator, context) { var index = -number, slices = [], array = this.toArray(); if (number < 1) return array; while ((index += number) < array.length) slices.push(array.slice(index, index+number)); return slices.collect(iterator, context); } function all(iterator, context) { iterator = iterator || Prototype.K; var result = true; this.each(function(value, index) { result = result && !!iterator.call(context, value, index); if (!result) throw $break; }); return result; } function any(iterator, context) { iterator = iterator || Prototype.K; var result = false; this.each(function(value, index) { if (result = !!iterator.call(context, value, index)) throw $break; }); return result; } function collect(iterator, context) { iterator = iterator || Prototype.K; var results = []; this.each(function(value, index) { results.push(iterator.call(context, value, index)); }); return results; } function detect(iterator, context) { var result; this.each(function(value, index) { if (iterator.call(context, value, index)) { result = value; throw $break; } }); return result; } function findAll(iterator, context) { var results = []; this.each(function(value, index) { if (iterator.call(context, value, index)) results.push(value); }); return results; } function grep(filter, iterator, context) { iterator = iterator || Prototype.K; var results = []; if (Object.isString(filter)) filter = new RegExp(RegExp.escape(filter)); this.each(function(value, index) { if (filter.match(value)) results.push(iterator.call(context, value, index)); }); return results; } function include(object) { if (Object.isFunction(this.indexOf)) if (this.indexOf(object) != -1) return true; var found = false; this.each(function(value) { if (value == object) { found = true; throw $break; } }); return found; } function inGroupsOf(number, fillWith) { fillWith = Object.isUndefined(fillWith) ? null : fillWith; return this.eachSlice(number, function(slice) { while(slice.length < number) slice.push(fillWith); return slice; }); } function inject(memo, iterator, context) { this.each(function(value, index) { memo = iterator.call(context, memo, value, index); }); return memo; } function invoke(method) { var args = $A(arguments).slice(1); return this.map(function(value) { return value[method].apply(value, args); }); } function max(iterator, context) { iterator = iterator || Prototype.K; var result; this.each(function(value, index) { value = iterator.call(context, value, index); if (result == null || value >= result) result = value; }); return result; } function min(iterator, context) { iterator = iterator || Prototype.K; var result; this.each(function(value, index) { value = iterator.call(context, value, index); if (result == null || value < result) result = value; }); return result; } function partition(iterator, context) { iterator = iterator || Prototype.K; var trues = [], falses = []; this.each(function(value, index) { (iterator.call(context, value, index) ? trues : falses).push(value); }); return [trues, falses]; } function pluck(property) { var results = []; this.each(function(value) { results.push(value[property]); }); return results; } function reject(iterator, context) { var results = []; this.each(function(value, index) { if (!iterator.call(context, value, index)) results.push(value); }); return results; } function sortBy(iterator, context) { return this.map(function(value, index) { return { value: value, criteria: iterator.call(context, value, index) }; }).sort(function(left, right) { var a = left.criteria, b = right.criteria; return a < b ? -1 : a > b ? 1 : 0; }).pluck('value'); } function toArray() { return this.map(); } function zip() { var iterator = Prototype.K, args = $A(arguments); if (Object.isFunction(args.last())) iterator = args.pop(); var collections = [this].concat(args).map($A); return this.map(function(value, index) { return iterator(collections.pluck(index)); }); } function size() { return this.toArray().length; } function inspect() { return '#'; } return { each: each, eachSlice: eachSlice, all: all, every: all, any: any, some: any, collect: collect, map: collect, detect: detect, findAll: findAll, select: findAll, filter: findAll, grep: grep, include: include, member: include, inGroupsOf: inGroupsOf, inject: inject, invoke: invoke, max: max, min: min, partition: partition, pluck: pluck, reject: reject, sortBy: sortBy, toArray: toArray, entries: toArray, zip: zip, size: size, inspect: inspect, find: detect }; })(); function $A(iterable) { if (!iterable) return []; if ('toArray' in Object(iterable)) return iterable.toArray(); var length = iterable.length || 0, results = new Array(length); while (length--) results[length] = iterable[length]; return results; } function $w(string) { if (!Object.isString(string)) return []; string = string.strip(); return string ? string.split(/\s+/) : []; } Array.from = $A; (function() { var arrayProto = Array.prototype, slice = arrayProto.slice, _each = arrayProto.forEach; // use native browser JS 1.6 implementation if available function each(iterator) { for (var i = 0, length = this.length; i < length; i++) iterator(this[i]); } if (!_each) _each = each; function clear() { this.length = 0; return this; } function first() { return this[0]; } function last() { return this[this.length - 1]; } function compact() { return this.select(function(value) { return value != null; }); } function flatten() { return this.inject([], function(array, value) { if (Object.isArray(value)) return array.concat(value.flatten()); array.push(value); return array; }); } function without() { var values = slice.call(arguments, 0); return this.select(function(value) { return !values.include(value); }); } function reverse(inline) { return (inline !== false ? this : this.toArray())._reverse(); } function uniq(sorted) { return this.inject([], function(array, value, index) { if (0 == index || (sorted ? array.last() != value : !array.include(value))) array.push(value); return array; }); } function intersect(array) { return this.uniq().findAll(function(item) { return array.detect(function(value) { return item === value }); }); } function clone() { return slice.call(this, 0); } function size() { return this.length; } function inspect() { return '[' + this.map(Object.inspect).join(', ') + ']'; } function toJSON() { var results = []; this.each(function(object) { var value = Object.toJSON(object); if (!Object.isUndefined(value)) results.push(value); }); return '[' + results.join(', ') + ']'; } function indexOf(item, i) { i || (i = 0); var length = this.length; if (i < 0) i = length + i; for (; i < length; i++) if (this[i] === item) return i; return -1; } function lastIndexOf(item, i) { i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1; var n = this.slice(0, i).reverse().indexOf(item); return (n < 0) ? n : i - n - 1; } function concat() { var array = slice.call(this, 0), item; for (var i = 0, length = arguments.length; i < length; i++) { item = arguments[i]; if (Object.isArray(item) && !('callee' in item)) { for (var j = 0, arrayLength = item.length; j < arrayLength; j++) array.push(item[j]); } else { array.push(item); } } return array; } Object.extend(arrayProto, Enumerable); if (!arrayProto._reverse) arrayProto._reverse = arrayProto.reverse; Object.extend(arrayProto, { _each: _each, clear: clear, first: first, last: last, compact: compact, flatten: flatten, without: without, reverse: reverse, uniq: uniq, intersect: intersect, clone: clone, toArray: clone, size: size, inspect: inspect, toJSON: toJSON }); var CONCAT_ARGUMENTS_BUGGY = (function() { return [].concat(arguments)[0][0] !== 1; })(1,2) if (CONCAT_ARGUMENTS_BUGGY) arrayProto.concat = concat; if (!arrayProto.indexOf) arrayProto.indexOf = indexOf; if (!arrayProto.lastIndexOf) arrayProto.lastIndexOf = lastIndexOf; })(); function $H(object) { return new Hash(object); }; var Hash = Class.create(Enumerable, (function() { function initialize(object) { this._object = Object.isHash(object) ? object.toObject() : Object.clone(object); } function _each(iterator) { for (var key in this._object) { var value = this._object[key], pair = [key, value]; pair.key = key; pair.value = value; iterator(pair); } } function set(key, value) { return this._object[key] = value; } function get(key) { if (this._object[key] !== Object.prototype[key]) return this._object[key]; } function unset(key) { var value = this._object[key]; delete this._object[key]; return value; } function toObject() { return Object.clone(this._object); } function keys() { return this.pluck('key'); } function values() { return this.pluck('value'); } function index(value) { var match = this.detect(function(pair) { return pair.value === value; }); return match && match.key; } function merge(object) { return this.clone().update(object); } function update(object) { return new Hash(object).inject(this, function(result, pair) { result.set(pair.key, pair.value); return result; }); } function toQueryPair(key, value) { if (Object.isUndefined(value)) return key; return key + '=' + encodeURIComponent(String.interpret(value)); } function toQueryString() { return this.inject([], function(results, pair) { var key = encodeURIComponent(pair.key), values = pair.value; if (values && typeof values == 'object') { if (Object.isArray(values)) return results.concat(values.map(toQueryPair.curry(key))); } else results.push(toQueryPair(key, values)); return results; }).join('&'); } function inspect() { return '#'; } function toJSON() { return Object.toJSON(this.toObject()); } function clone() { return new Hash(this); } return { initialize: initialize, _each: _each, set: set, get: get, unset: unset, toObject: toObject, toTemplateReplacements: toObject, keys: keys, values: values, index: index, merge: merge, update: update, toQueryString: toQueryString, inspect: inspect, toJSON: toJSON, clone: clone }; })()); Hash.from = $H; Object.extend(Number.prototype, (function() { function toColorPart() { return this.toPaddedString(2, 16); } function succ() { return this + 1; } function times(iterator, context) { $R(0, this, true).each(iterator, context); return this; } function toPaddedString(length, radix) { var string = this.toString(radix || 10); return '0'.times(length - string.length) + string; } function toJSON() { return isFinite(this) ? this.toString() : 'null'; } function abs() { return Math.abs(this); } function round() { return Math.round(this); } function ceil() { return Math.ceil(this); } function floor() { return Math.floor(this); } return { toColorPart: toColorPart, succ: succ, times: times, toPaddedString: toPaddedString, toJSON: toJSON, abs: abs, round: round, ceil: ceil, floor: floor }; })()); function $R(start, end, exclusive) { return new ObjectRange(start, end, exclusive); } var ObjectRange = Class.create(Enumerable, (function() { function initialize(start, end, exclusive) { this.start = start; this.end = end; this.exclusive = exclusive; } function _each(iterator) { var value = this.start; while (this.include(value)) { iterator(value); value = value.succ(); } } function include(value) { if (value < this.start) return false; if (this.exclusive) return value < this.end; return value <= this.end; } return { initialize: initialize, _each: _each, include: include }; })()); var Ajax = { getTransport: function() { return Try.these( function() {return new XMLHttpRequest()}, function() {return new ActiveXObject('Msxml2.XMLHTTP')}, function() {return new ActiveXObject('Microsoft.XMLHTTP')} ) || false; }, activeRequestCount: 0 }; Ajax.Responders = { responders: [], _each: function(iterator) { this.responders._each(iterator); }, register: function(responder) { if (!this.include(responder)) this.responders.push(responder); }, unregister: function(responder) { this.responders = this.responders.without(responder); }, dispatch: function(callback, request, transport, json) { this.each(function(responder) { if (Object.isFunction(responder[callback])) { try { responder[callback].apply(responder, [request, transport, json]); } catch (e) { } } }); } }; Object.extend(Ajax.Responders, Enumerable); Ajax.Responders.register({ onCreate: function() { Ajax.activeRequestCount++ }, onComplete: function() { Ajax.activeRequestCount-- } }); Ajax.Base = Class.create({ initialize: function(options) { this.options = { method: 'post', asynchronous: true, contentType: 'application/x-www-form-urlencoded', encoding: 'UTF-8', parameters: '', evalJSON: true, evalJS: true }; Object.extend(this.options, options || { }); this.options.method = this.options.method.toLowerCase(); if (Object.isString(this.options.parameters)) this.options.parameters = this.options.parameters.toQueryParams(); else if (Object.isHash(this.options.parameters)) this.options.parameters = this.options.parameters.toObject(); } }); Ajax.Request = Class.create(Ajax.Base, { _complete: false, initialize: function($super, url, options) { $super(options); this.transport = Ajax.getTransport(); this.request(url); }, request: function(url) { this.url = url; this.method = this.options.method; var params = Object.clone(this.options.parameters); if (!['get', 'post'].include(this.method)) { params['_method'] = this.method; this.method = 'post'; } this.parameters = params; if (params = Object.toQueryString(params)) { if (this.method == 'get') this.url += (this.url.include('?') ? '&' : '?') + params; else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) params += '&_='; } try { var response = new Ajax.Response(this); if (this.options.onCreate) this.options.onCreate(response); Ajax.Responders.dispatch('onCreate', this, response); this.transport.open(this.method.toUpperCase(), this.url, this.options.asynchronous); if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1); this.transport.onreadystatechange = this.onStateChange.bind(this); this.setRequestHeaders(); this.body = this.method == 'post' ? (this.options.postBody || params) : null; this.transport.send(this.body); /* Force Firefox to handle ready state 4 for synchronous requests */ if (!this.options.asynchronous && this.transport.overrideMimeType) this.onStateChange(); } catch (e) { this.dispatchException(e); } }, onStateChange: function() { var readyState = this.transport.readyState; if (readyState > 1 && !((readyState == 4) && this._complete)) this.respondToReadyState(this.transport.readyState); }, setRequestHeaders: function() { var headers = { 'X-Requested-With': 'XMLHttpRequest', 'X-Prototype-Version': Prototype.Version, 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' }; if (this.method == 'post') { headers['Content-type'] = this.options.contentType + (this.options.encoding ? '; charset=' + this.options.encoding : ''); /* Force "Connection: close" for older Mozilla browsers to work * around a bug where XMLHttpRequest sends an incorrect * Content-length header. See Mozilla Bugzilla #246651. */ if (this.transport.overrideMimeType && (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) headers['Connection'] = 'close'; } if (typeof this.options.requestHeaders == 'object') { var extras = this.options.requestHeaders; if (Object.isFunction(extras.push)) for (var i = 0, length = extras.length; i < length; i += 2) headers[extras[i]] = extras[i+1]; else $H(extras).each(function(pair) { headers[pair.key] = pair.value }); } for (var name in headers) this.transport.setRequestHeader(name, headers[name]); }, success: function() { var status = this.getStatus(); return !status || (status >= 200 && status < 300); }, getStatus: function() { try { return this.transport.status || 0; } catch (e) { return 0 } }, respondToReadyState: function(readyState) { var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this); if (state == 'Complete') { try { this._complete = true; (this.options['on' + response.status] || this.options['on' + (this.success() ? 'Success' : 'Failure')] || Prototype.emptyFunction)(response, response.headerJSON); } catch (e) { this.dispatchException(e); } var contentType = response.getHeader('Content-type'); if (this.options.evalJS == 'force' || (this.options.evalJS && this.isSameOrigin() && contentType && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))) this.evalResponse(); } try { (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON); Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON); } catch (e) { this.dispatchException(e); } if (state == 'Complete') { this.transport.onreadystatechange = Prototype.emptyFunction; } }, isSameOrigin: function() { var m = this.url.match(/^\s*https?:\/\/[^\/]*/); return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({ protocol: location.protocol, domain: document.domain, port: location.port ? ':' + location.port : '' })); }, getHeader: function(name) { try { return this.transport.getResponseHeader(name) || null; } catch (e) { return null; } }, evalResponse: function() { try { return eval((this.transport.responseText || '').unfilterJSON()); } catch (e) { this.dispatchException(e); } }, dispatchException: function(exception) { (this.options.onException || Prototype.emptyFunction)(this, exception); Ajax.Responders.dispatch('onException', this, exception); } }); Ajax.Request.Events = ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; Ajax.Response = Class.create({ initialize: function(request){ this.request = request; var transport = this.transport = request.transport, readyState = this.readyState = transport.readyState; if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { this.status = this.getStatus(); this.statusText = this.getStatusText(); this.responseText = String.interpret(transport.responseText); this.headerJSON = this._getHeaderJSON(); } if(readyState == 4) { var xml = transport.responseXML; this.responseXML = Object.isUndefined(xml) ? null : xml; this.responseJSON = this._getResponseJSON(); } }, status: 0, statusText: '', getStatus: Ajax.Request.prototype.getStatus, getStatusText: function() { try { return this.transport.statusText || ''; } catch (e) { return '' } }, getHeader: Ajax.Request.prototype.getHeader, getAllHeaders: function() { try { return this.getAllResponseHeaders(); } catch (e) { return null } }, getResponseHeader: function(name) { return this.transport.getResponseHeader(name); }, getAllResponseHeaders: function() { return this.transport.getAllResponseHeaders(); }, _getHeaderJSON: function() { var json = this.getHeader('X-JSON'); if (!json) return null; json = decodeURIComponent(escape(json)); try { return json.evalJSON(this.request.options.sanitizeJSON || !this.request.isSameOrigin()); } catch (e) { this.request.dispatchException(e); } }, _getResponseJSON: function() { var options = this.request.options; if (!options.evalJSON || (options.evalJSON != 'force' && !(this.getHeader('Content-type') || '').include('application/json')) || this.responseText.blank()) return null; try { return this.responseText.evalJSON(options.sanitizeJSON || !this.request.isSameOrigin()); } catch (e) { this.request.dispatchException(e); } } }); Ajax.Updater = Class.create(Ajax.Request, { initialize: function($super, container, url, options) { this.container = { success: (container.success || container), failure: (container.failure || (container.success ? null : container)) }; options = Object.clone(options); var onComplete = options.onComplete; options.onComplete = (function(response, json) { this.updateContent(response.responseText); if (Object.isFunction(onComplete)) onComplete(response, json); }).bind(this); $super(url, options); }, updateContent: function(responseText) { var receiver = this.container[this.success() ? 'success' : 'failure'], options = this.options; if (!options.evalScripts) responseText = responseText.stripScripts(); if (receiver = $(receiver)) { if (options.insertion) { if (Object.isString(options.insertion)) { var insertion = { }; insertion[options.insertion] = responseText; receiver.insert(insertion); } else options.insertion(receiver, responseText); } else receiver.update(responseText); } } }); Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { initialize: function($super, container, url, options) { $super(options); this.onComplete = this.options.onComplete; this.frequency = (this.options.frequency || 2); this.decay = (this.options.decay || 1); this.updater = { }; this.container = container; this.url = url; this.start(); }, start: function() { this.options.onComplete = this.updateComplete.bind(this); this.onTimerEvent(); }, stop: function() { this.updater.options.onComplete = undefined; clearTimeout(this.timer); (this.onComplete || Prototype.emptyFunction).apply(this, arguments); }, updateComplete: function(response) { if (this.options.decay) { this.decay = (response.responseText == this.lastText ? this.decay * this.options.decay : 1); this.lastText = response.responseText; } this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency); }, onTimerEvent: function() { this.updater = new Ajax.Updater(this.container, this.url, this.options); } }); function $(element) { if (arguments.length > 1) { for (var i = 0, elements = [], length = arguments.length; i < length; i++) elements.push($(arguments[i])); return elements; } if (Object.isString(element)) element = document.getElementById(element); return Element.extend(element); } if (Prototype.BrowserFeatures.XPath) { document._getElementsByXPath = function(expression, parentElement) { var results = []; var query = document.evaluate(expression, $(parentElement) || document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); for (var i = 0, length = query.snapshotLength; i < length; i++) results.push(Element.extend(query.snapshotItem(i))); return results; }; } /*--------------------------------------------------------------------------*/ if (!window.Node) var Node = { }; if (!Node.ELEMENT_NODE) { Object.extend(Node, { ELEMENT_NODE: 1, ATTRIBUTE_NODE: 2, TEXT_NODE: 3, CDATA_SECTION_NODE: 4, ENTITY_REFERENCE_NODE: 5, ENTITY_NODE: 6, PROCESSING_INSTRUCTION_NODE: 7, COMMENT_NODE: 8, DOCUMENT_NODE: 9, DOCUMENT_TYPE_NODE: 10, DOCUMENT_FRAGMENT_NODE: 11, NOTATION_NODE: 12 }); } (function(global) { var SETATTRIBUTE_IGNORES_NAME = (function(){ var elForm = document.createElement("form"); var elInput = document.createElement("input"); var root = document.documentElement; elInput.setAttribute("name", "test"); elForm.appendChild(elInput); root.appendChild(elForm); var isBuggy = elForm.elements ? (typeof elForm.elements.test == "undefined") : null; root.removeChild(elForm); elForm = elInput = null; return isBuggy; })(); var element = global.Element; global.Element = function(tagName, attributes) { attributes = attributes || { }; tagName = tagName.toLowerCase(); var cache = Element.cache; if (SETATTRIBUTE_IGNORES_NAME && attributes.name) { tagName = '<' + tagName + ' name="' + attributes.name + '">'; delete attributes.name; return Element.writeAttribute(document.createElement(tagName), attributes); } if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName)); return Element.writeAttribute(cache[tagName].cloneNode(false), attributes); }; Object.extend(global.Element, element || { }); if (element) global.Element.prototype = element.prototype; })(this); Element.cache = { }; Element.idCounter = 1; Element.Methods = { visible: function(element) { return $(element).style.display != 'none'; }, toggle: function(element) { element = $(element); Element[Element.visible(element) ? 'hide' : 'show'](element); return element; }, hide: function(element) { element = $(element); element.style.display = 'none'; return element; }, show: function(element) { element = $(element); element.style.display = ''; return element; }, remove: function(element) { element = $(element); element.parentNode.removeChild(element); return element; }, update: (function(){ var SELECT_ELEMENT_INNERHTML_BUGGY = (function(){ var el = document.createElement("select"), isBuggy = true; el.innerHTML = ""; if (el.options && el.options[0]) { isBuggy = el.options[0].nodeName.toUpperCase() !== "OPTION"; } el = null; return isBuggy; })(); var TABLE_ELEMENT_INNERHTML_BUGGY = (function(){ try { var el = document.createElement("table"); if (el && el.tBodies) { el.innerHTML = "test"; var isBuggy = typeof el.tBodies[0] == "undefined"; el = null; return isBuggy; } } catch (e) { return true; } })(); var SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING = (function () { var s = document.createElement("script"), isBuggy = false; try { s.appendChild(document.createTextNode("")); isBuggy = !s.firstChild || s.firstChild && s.firstChild.nodeType !== 3; } catch (e) { isBuggy = true; } s = null; return isBuggy; })(); function update(element, content) { element = $(element); if (content && content.toElement) content = content.toElement(); if (Object.isElement(content)) return element.update().insert(content); content = Object.toHTML(content); var tagName = element.tagName.toUpperCase(); if (tagName === 'SCRIPT' && SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING) { element.text = content; return element; } if (SELECT_ELEMENT_INNERHTML_BUGGY || TABLE_ELEMENT_INNERHTML_BUGGY) { if (tagName in Element._insertionTranslations.tags) { while (element.firstChild) { element.removeChild(element.firstChild); } Element._getContentFromAnonymousElement(tagName, content.stripScripts()) .each(function(node) { element.appendChild(node) }); } else { element.innerHTML = content.stripScripts(); } } else { element.innerHTML = content.stripScripts(); } content.evalScripts.bind(content).defer(); return element; } return update; })(), replace: function(element, content) { element = $(element); if (content && content.toElement) content = content.toElement(); else if (!Object.isElement(content)) { content = Object.toHTML(content); var range = element.ownerDocument.createRange(); range.selectNode(element); content.evalScripts.bind(content).defer(); content = range.createContextualFragment(content.stripScripts()); } element.parentNode.replaceChild(content, element); return element; }, insert: function(element, insertions) { element = $(element); if (Object.isString(insertions) || Object.isNumber(insertions) || Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) insertions = {bottom:insertions}; var content, insert, tagName, childNodes; for (var position in insertions) { content = insertions[position]; position = position.toLowerCase(); insert = Element._insertionTranslations[position]; if (content && content.toElement) content = content.toElement(); if (Object.isElement(content)) { insert(element, content); continue; } content = Object.toHTML(content); tagName = ((position == 'before' || position == 'after') ? element.parentNode : element).tagName.toUpperCase(); childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); if (position == 'top' || position == 'after') childNodes.reverse(); childNodes.each(insert.curry(element)); content.evalScripts.bind(content).defer(); } return element; }, wrap: function(element, wrapper, attributes) { element = $(element); if (Object.isElement(wrapper)) $(wrapper).writeAttribute(attributes || { }); else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes); else wrapper = new Element('div', wrapper); if (element.parentNode) element.parentNode.replaceChild(wrapper, element); wrapper.appendChild(element); return wrapper; }, inspect: function(element) { element = $(element); var result = '<' + element.tagName.toLowerCase(); $H({'id': 'id', 'className': 'class'}).each(function(pair) { var property = pair.first(), attribute = pair.last(); var value = (element[property] || '').toString(); if (value) result += ' ' + attribute + '=' + value.inspect(true); }); return result + '>'; }, recursivelyCollect: function(element, property) { element = $(element); var elements = []; while (element = element[property]) if (element.nodeType == 1) elements.push(Element.extend(element)); return elements; }, ancestors: function(element) { return Element.recursivelyCollect(element, 'parentNode'); }, descendants: function(element) { return Element.select(element, "*"); }, firstDescendant: function(element) { element = $(element).firstChild; while (element && element.nodeType != 1) element = element.nextSibling; return $(element); }, immediateDescendants: function(element) { if (!(element = $(element).firstChild)) return []; while (element && element.nodeType != 1) element = element.nextSibling; if (element) return [element].concat($(element).nextSiblings()); return []; }, previousSiblings: function(element) { return Element.recursivelyCollect(element, 'previousSibling'); }, nextSiblings: function(element) { return Element.recursivelyCollect(element, 'nextSibling'); }, siblings: function(element) { element = $(element); return Element.previousSiblings(element).reverse() .concat(Element.nextSiblings(element)); }, match: function(element, selector) { if (Object.isString(selector)) selector = new Selector(selector); return selector.match($(element)); }, up: function(element, expression, index) { element = $(element); if (arguments.length == 1) return $(element.parentNode); var ancestors = Element.ancestors(element); return Object.isNumber(expression) ? ancestors[expression] : Selector.findElement(ancestors, expression, index); }, down: function(element, expression, index) { element = $(element); if (arguments.length == 1) return Element.firstDescendant(element); return Object.isNumber(expression) ? Element.descendants(element)[expression] : Element.select(element, expression)[index || 0]; }, previous: function(element, expression, index) { element = $(element); if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element)); var previousSiblings = Element.previousSiblings(element); return Object.isNumber(expression) ? previousSiblings[expression] : Selector.findElement(previousSiblings, expression, index); }, next: function(element, expression, index) { element = $(element); if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element)); var nextSiblings = Element.nextSiblings(element); return Object.isNumber(expression) ? nextSiblings[expression] : Selector.findElement(nextSiblings, expression, index); }, select: function(element) { var args = Array.prototype.slice.call(arguments, 1); return Selector.findChildElements(element, args); }, adjacent: function(element) { var args = Array.prototype.slice.call(arguments, 1); return Selector.findChildElements(element.parentNode, args).without(element); }, identify: function(element) { element = $(element); var id = Element.readAttribute(element, 'id'); if (id) return id; do { id = 'anonymous_element_' + Element.idCounter++ } while ($(id)); Element.writeAttribute(element, 'id', id); return id; }, readAttribute: function(element, name) { element = $(element); if (Prototype.Browser.IE) { var t = Element._attributeTranslations.read; if (t.values[name]) return t.values[name](element, name); if (t.names[name]) name = t.names[name]; if (name.include(':')) { return (!element.attributes || !element.attributes[name]) ? null : element.attributes[name].value; } } return element.getAttribute(name); }, writeAttribute: function(element, name, value) { element = $(element); var attributes = { }, t = Element._attributeTranslations.write; if (typeof name == 'object') attributes = name; else attributes[name] = Object.isUndefined(value) ? true : value; for (var attr in attributes) { name = t.names[attr] || attr; value = attributes[attr]; if (t.values[attr]) name = t.values[attr](element, value); if (value === false || value === null) element.removeAttribute(name); else if (value === true) element.setAttribute(name, name); else element.setAttribute(name, value); } return element; }, getHeight: function(element) { return Element.getDimensions(element).height; }, getWidth: function(element) { return Element.getDimensions(element).width; }, classNames: function(element) { return new Element.ClassNames(element); }, hasClassName: function(element, className) { if (!(element = $(element))) return; var elementClassName = element.className; return (elementClassName.length > 0 && (elementClassName == className || new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName))); }, addClassName: function(element, className) { if (!(element = $(element))) return; if (!Element.hasClassName(element, className)) element.className += (element.className ? ' ' : '') + className; return element; }, removeClassName: function(element, className) { if (!(element = $(element))) return; element.className = element.className.replace( new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip(); return element; }, toggleClassName: function(element, className) { if (!(element = $(element))) return; return Element[Element.hasClassName(element, className) ? 'removeClassName' : 'addClassName'](element, className); }, cleanWhitespace: function(element) { element = $(element); var node = element.firstChild; while (node) { var nextNode = node.nextSibling; if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) element.removeChild(node); node = nextNode; } return element; }, empty: function(element) { return $(element).innerHTML.blank(); }, descendantOf: function(element, ancestor) { element = $(element), ancestor = $(ancestor); if (element.compareDocumentPosition) return (element.compareDocumentPosition(ancestor) & 8) === 8; if (ancestor.contains) return ancestor.contains(element) && ancestor !== element; while (element = element.parentNode) if (element == ancestor) return true; return false; }, scrollTo: function(element) { element = $(element); var pos = Element.cumulativeOffset(element); window.scrollTo(pos[0], pos[1]); return element; }, getStyle: function(element, style) { element = $(element); style = style == 'float' ? 'cssFloat' : style.camelize(); var value = element.style[style]; if (!value || value == 'auto') { var css = document.defaultView.getComputedStyle(element, null); value = css ? css[style] : null; } if (style == 'opacity') return value ? parseFloat(value) : 1.0; return value == 'auto' ? null : value; }, getOpacity: function(element) { return $(element).getStyle('opacity'); }, setStyle: function(element, styles) { element = $(element); var elementStyle = element.style, match; if (Object.isString(styles)) { element.style.cssText += ';' + styles; return styles.include('opacity') ? element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element; } for (var property in styles) if (property == 'opacity') element.setOpacity(styles[property]); else elementStyle[(property == 'float' || property == 'cssFloat') ? (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') : property] = styles[property]; return element; }, setOpacity: function(element, value) { element = $(element); element.style.opacity = (value == 1 || value === '') ? '' : (value < 0.00001) ? 0 : value; return element; }, getDimensions: function(element) { element = $(element); var display = Element.getStyle(element, 'display'); if (display != 'none' && display != null) // Safari bug return {width: element.offsetWidth, height: element.offsetHeight}; var els = element.style; var originalVisibility = els.visibility; var originalPosition = els.position; var originalDisplay = els.display; els.visibility = 'hidden'; if (originalPosition != 'fixed') // Switching fixed to absolute causes issues in Safari els.position = 'absolute'; els.display = 'block'; var originalWidth = element.clientWidth; var originalHeight = element.clientHeight; els.display = originalDisplay; els.position = originalPosition; els.visibility = originalVisibility; return {width: originalWidth, height: originalHeight}; }, makePositioned: function(element) { element = $(element); var pos = Element.getStyle(element, 'position'); if (pos == 'static' || !pos) { element._madePositioned = true; element.style.position = 'relative'; if (Prototype.Browser.Opera) { element.style.top = 0; element.style.left = 0; } } return element; }, undoPositioned: function(element) { element = $(element); if (element._madePositioned) { element._madePositioned = undefined; element.style.position = element.style.top = element.style.left = element.style.bottom = element.style.right = ''; } return element; }, makeClipping: function(element) { element = $(element); if (element._overflow) return element; element._overflow = Element.getStyle(element, 'overflow') || 'auto'; if (element._overflow !== 'hidden') element.style.overflow = 'hidden'; return element; }, undoClipping: function(element) { element = $(element); if (!element._overflow) return element; element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; element._overflow = null; return element; }, cumulativeOffset: function(element) { var valueT = 0, valueL = 0; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; element = element.offsetParent; } while (element); return Element._returnOffset(valueL, valueT); }, positionedOffset: function(element) { var valueT = 0, valueL = 0; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; element = element.offsetParent; if (element) { if (element.tagName.toUpperCase() == 'BODY') break; var p = Element.getStyle(element, 'position'); if (p !== 'static') break; } } while (element); return Element._returnOffset(valueL, valueT); }, absolutize: function(element) { element = $(element); if (Element.getStyle(element, 'position') == 'absolute') return element; var offsets = Element.positionedOffset(element); var top = offsets[1]; var left = offsets[0]; var width = element.clientWidth; var height = element.clientHeight; element._originalLeft = left - parseFloat(element.style.left || 0); element._originalTop = top - parseFloat(element.style.top || 0); element._originalWidth = element.style.width; element._originalHeight = element.style.height; element.style.position = 'absolute'; element.style.top = top + 'px'; element.style.left = left + 'px'; element.style.width = width + 'px'; element.style.height = height + 'px'; return element; }, relativize: function(element) { element = $(element); if (Element.getStyle(element, 'position') == 'relative') return element; element.style.position = 'relative'; var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); element.style.top = top + 'px'; element.style.left = left + 'px'; element.style.height = element._originalHeight; element.style.width = element._originalWidth; return element; }, cumulativeScrollOffset: function(element) { var valueT = 0, valueL = 0; do { valueT += element.scrollTop || 0; valueL += element.scrollLeft || 0; element = element.parentNode; } while (element); return Element._returnOffset(valueL, valueT); }, getOffsetParent: function(element) { if (element.offsetParent) return $(element.offsetParent); if (element == document.body) return $(element); while ((element = element.parentNode) && element != document.body) if (Element.getStyle(element, 'position') != 'static') return $(element); return $(document.body); }, viewportOffset: function(forElement) { var valueT = 0, valueL = 0; var element = forElement; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; if (element.offsetParent == document.body && Element.getStyle(element, 'position') == 'absolute') break; } while (element = element.offsetParent); element = forElement; do { if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) { valueT -= element.scrollTop || 0; valueL -= element.scrollLeft || 0; } } while (element = element.parentNode); return Element._returnOffset(valueL, valueT); }, clonePosition: function(element, source) { var options = Object.extend({ setLeft: true, setTop: true, setWidth: true, setHeight: true, offsetTop: 0, offsetLeft: 0 }, arguments[2] || { }); source = $(source); var p = Element.viewportOffset(source); element = $(element); var delta = [0, 0]; var parent = null; if (Element.getStyle(element, 'position') == 'absolute') { parent = Element.getOffsetParent(element); delta = Element.viewportOffset(parent); } if (parent == document.body) { delta[0] -= document.body.offsetLeft; delta[1] -= document.body.offsetTop; } if (options.setLeft) element.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; if (options.setTop) element.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; if (options.setWidth) element.style.width = source.offsetWidth + 'px'; if (options.setHeight) element.style.height = source.offsetHeight + 'px'; return element; } }; Object.extend(Element.Methods, { getElementsBySelector: Element.Methods.select, childElements: Element.Methods.immediateDescendants }); Element._attributeTranslations = { write: { names: { className: 'class', htmlFor: 'for' }, values: { } } }; if (Prototype.Browser.Opera) { Element.Methods.getStyle = Element.Methods.getStyle.wrap( function(proceed, element, style) { switch (style) { case 'left': case 'top': case 'right': case 'bottom': if (proceed(element, 'position') === 'static') return null; case 'height': case 'width': if (!Element.visible(element)) return null; var dim = parseInt(proceed(element, style), 10); if (dim !== element['offset' + style.capitalize()]) return dim + 'px'; var properties; if (style === 'height') { properties = ['border-top-width', 'padding-top', 'padding-bottom', 'border-bottom-width']; } else { properties = ['border-left-width', 'padding-left', 'padding-right', 'border-right-width']; } return properties.inject(dim, function(memo, property) { var val = proceed(element, property); return val === null ? memo : memo - parseInt(val, 10); }) + 'px'; default: return proceed(element, style); } } ); Element.Methods.readAttribute = Element.Methods.readAttribute.wrap( function(proceed, element, attribute) { if (attribute === 'title') return element.title; return proceed(element, attribute); } ); } else if (Prototype.Browser.IE) { Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap( function(proceed, element) { element = $(element); try { element.offsetParent } catch(e) { return $(document.body) } var position = element.getStyle('position'); if (position !== 'static') return proceed(element); element.setStyle({ position: 'relative' }); var value = proceed(element); element.setStyle({ position: position }); return value; } ); $w('positionedOffset viewportOffset').each(function(method) { Element.Methods[method] = Element.Methods[method].wrap( function(proceed, element) { element = $(element); try { element.offsetParent } catch(e) { return Element._returnOffset(0,0) } var position = element.getStyle('position'); if (position !== 'static') return proceed(element); var offsetParent = element.getOffsetParent(); if (offsetParent && offsetParent.getStyle('position') === 'fixed') offsetParent.setStyle({ zoom: 1 }); element.setStyle({ position: 'relative' }); var value = proceed(element); element.setStyle({ position: position }); return value; } ); }); Element.Methods.cumulativeOffset = Element.Methods.cumulativeOffset.wrap( function(proceed, element) { try { element.offsetParent } catch(e) { return Element._returnOffset(0,0) } return proceed(element); } ); Element.Methods.getStyle = function(element, style) { element = $(element); style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); var value = element.style[style]; if (!value && element.currentStyle) value = element.currentStyle[style]; if (style == 'opacity') { if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) if (value[1]) return parseFloat(value[1]) / 100; return 1.0; } if (value == 'auto') { if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none')) return element['offset' + style.capitalize()] + 'px'; return null; } return value; }; Element.Methods.setOpacity = function(element, value) { function stripAlpha(filter){ return filter.replace(/alpha\([^\)]*\)/gi,''); } element = $(element); var currentStyle = element.currentStyle; if ((currentStyle && !currentStyle.hasLayout) || (!currentStyle && element.style.zoom == 'normal')) element.style.zoom = 1; var filter = element.getStyle('filter'), style = element.style; if (value == 1 || value === '') { (filter = stripAlpha(filter)) ? style.filter = filter : style.removeAttribute('filter'); return element; } else if (value < 0.00001) value = 0; style.filter = stripAlpha(filter) + 'alpha(opacity=' + (value * 100) + ')'; return element; }; Element._attributeTranslations = (function(){ var classProp = 'className'; var forProp = 'for'; var el = document.createElement('div'); el.setAttribute(classProp, 'x'); if (el.className !== 'x') { el.setAttribute('class', 'x'); if (el.className === 'x') { classProp = 'class'; } } el = null; el = document.createElement('label'); el.setAttribute(forProp, 'x'); if (el.htmlFor !== 'x') { el.setAttribute('htmlFor', 'x'); if (el.htmlFor === 'x') { forProp = 'htmlFor'; } } el = null; return { read: { names: { 'class': classProp, 'className': classProp, 'for': forProp, 'htmlFor': forProp }, values: { _getAttr: function(element, attribute) { return element.getAttribute(attribute); }, _getAttr2: function(element, attribute) { return element.getAttribute(attribute, 2); }, _getAttrNode: function(element, attribute) { var node = element.getAttributeNode(attribute); return node ? node.value : ""; }, _getEv: (function(){ var el = document.createElement('div'); el.onclick = Prototype.emptyFunction; var value = el.getAttribute('onclick'); var f; if (String(value).indexOf('{') > -1) { f = function(element, attribute) { attribute = element.getAttribute(attribute); if (!attribute) return null; attribute = attribute.toString(); attribute = attribute.split('{')[1]; attribute = attribute.split('}')[0]; return attribute.strip(); }; } else if (value === '') { f = function(element, attribute) { attribute = element.getAttribute(attribute); if (!attribute) return null; return attribute.strip(); }; } el = null; return f; })(), _flag: function(element, attribute) { return $(element).hasAttribute(attribute) ? attribute : null; }, style: function(element) { return element.style.cssText.toLowerCase(); }, title: function(element) { return element.title; } } } } })(); Element._attributeTranslations.write = { names: Object.extend({ cellpadding: 'cellPadding', cellspacing: 'cellSpacing' }, Element._attributeTranslations.read.names), values: { checked: function(element, value) { element.checked = !!value; }, style: function(element, value) { element.style.cssText = value ? value : ''; } } }; Element._attributeTranslations.has = {}; $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' + 'encType maxLength readOnly longDesc frameBorder').each(function(attr) { Element._attributeTranslations.write.names[attr.toLowerCase()] = attr; Element._attributeTranslations.has[attr.toLowerCase()] = attr; }); (function(v) { Object.extend(v, { href: v._getAttr2, src: v._getAttr2, type: v._getAttr, action: v._getAttrNode, disabled: v._flag, checked: v._flag, readonly: v._flag, multiple: v._flag, onload: v._getEv, onunload: v._getEv, onclick: v._getEv, ondblclick: v._getEv, onmousedown: v._getEv, onmouseup: v._getEv, onmouseover: v._getEv, onmousemove: v._getEv, onmouseout: v._getEv, onfocus: v._getEv, onblur: v._getEv, onkeypress: v._getEv, onkeydown: v._getEv, onkeyup: v._getEv, onsubmit: v._getEv, onreset: v._getEv, onselect: v._getEv, onchange: v._getEv }); })(Element._attributeTranslations.read.values); if (Prototype.BrowserFeatures.ElementExtensions) { (function() { function _descendants(element) { var nodes = element.getElementsByTagName('*'), results = []; for (var i = 0, node; node = nodes[i]; i++) if (node.tagName !== "!") // Filter out comment nodes. results.push(node); return results; } Element.Methods.down = function(element, expression, index) { element = $(element); if (arguments.length == 1) return element.firstDescendant(); return Object.isNumber(expression) ? _descendants(element)[expression] : Element.select(element, expression)[index || 0]; } })(); } } else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) { Element.Methods.setOpacity = function(element, value) { element = $(element); element.style.opacity = (value == 1) ? 0.999999 : (value === '') ? '' : (value < 0.00001) ? 0 : value; return element; }; } else if (Prototype.Browser.WebKit) { Element.Methods.setOpacity = function(element, value) { element = $(element); element.style.opacity = (value == 1 || value === '') ? '' : (value < 0.00001) ? 0 : value; if (value == 1) if(element.tagName.toUpperCase() == 'IMG' && element.width) { element.width++; element.width--; } else try { var n = document.createTextNode(' '); element.appendChild(n); element.removeChild(n); } catch (e) { } return element; }; Element.Methods.cumulativeOffset = function(element) { var valueT = 0, valueL = 0; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; if (element.offsetParent == document.body) if (Element.getStyle(element, 'position') == 'absolute') break; element = element.offsetParent; } while (element); return Element._returnOffset(valueL, valueT); }; } if ('outerHTML' in document.documentElement) { Element.Methods.replace = function(element, content) { element = $(element); if (content && content.toElement) content = content.toElement(); if (Object.isElement(content)) { element.parentNode.replaceChild(content, element); return element; } content = Object.toHTML(content); var parent = element.parentNode, tagName = parent.tagName.toUpperCase(); if (Element._insertionTranslations.tags[tagName]) { var nextSibling = element.next(); var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); parent.removeChild(element); if (nextSibling) fragments.each(function(node) { parent.insertBefore(node, nextSibling) }); else fragments.each(function(node) { parent.appendChild(node) }); } else element.outerHTML = content.stripScripts(); content.evalScripts.bind(content).defer(); return element; }; } Element._returnOffset = function(l, t) { var result = [l, t]; result.left = l; result.top = t; return result; }; Element._getContentFromAnonymousElement = function(tagName, html) { var div = new Element('div'), t = Element._insertionTranslations.tags[tagName]; if (t) { div.innerHTML = t[0] + html + t[1]; t[2].times(function() { div = div.firstChild }); } else div.innerHTML = html; return $A(div.childNodes); }; Element._insertionTranslations = { before: function(element, node) { element.parentNode.insertBefore(node, element); }, top: function(element, node) { element.insertBefore(node, element.firstChild); }, bottom: function(element, node) { element.appendChild(node); }, after: function(element, node) { element.parentNode.insertBefore(node, element.nextSibling); }, tags: { TABLE: ['', '
', 1], TBODY: ['', '
', 2], TR: ['', '
', 3], TD: ['
', '
', 4], SELECT: ['', 1] } }; (function() { var tags = Element._insertionTranslations.tags; Object.extend(tags, { THEAD: tags.TBODY, TFOOT: tags.TBODY, TH: tags.TD }); })(); Element.Methods.Simulated = { hasAttribute: function(element, attribute) { attribute = Element._attributeTranslations.has[attribute] || attribute; var node = $(element).getAttributeNode(attribute); return !!(node && node.specified); } }; Element.Methods.ByTag = { }; Object.extend(Element, Element.Methods); (function(div) { if (!Prototype.BrowserFeatures.ElementExtensions && div['__proto__']) { window.HTMLElement = { }; window.HTMLElement.prototype = div['__proto__']; Prototype.BrowserFeatures.ElementExtensions = true; } div = null; })(document.createElement('div')) Element.extend = (function() { function checkDeficiency(tagName) { if (typeof window.Element != 'undefined') { var proto = window.Element.prototype; if (proto) { var id = '_' + (Math.random()+'').slice(2); var el = document.createElement(tagName); proto[id] = 'x'; var isBuggy = (el[id] !== 'x'); delete proto[id]; el = null; return isBuggy; } } return false; } function extendElementWith(element, methods) { for (var property in methods) { var value = methods[property]; if (Object.isFunction(value) && !(property in element)) element[property] = value.methodize(); } } var HTMLOBJECTELEMENT_PROTOTYPE_BUGGY = checkDeficiency('object'); if (Prototype.BrowserFeatures.SpecificElementExtensions) { if (HTMLOBJECTELEMENT_PROTOTYPE_BUGGY) { return function(element) { if (element && typeof element._extendedByPrototype == 'undefined') { var t = element.tagName; if (t && (/^(?:object|applet|embed)$/i.test(t))) { extendElementWith(element, Element.Methods); extendElementWith(element, Element.Methods.Simulated); extendElementWith(element, Element.Methods.ByTag[t.toUpperCase()]); } } return element; } } return Prototype.K; } var Methods = { }, ByTag = Element.Methods.ByTag; var extend = Object.extend(function(element) { if (!element || typeof element._extendedByPrototype != 'undefined' || element.nodeType != 1 || element == window) return element; var methods = Object.clone(Methods), tagName = element.tagName.toUpperCase(); if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]); extendElementWith(element, methods); element._extendedByPrototype = Prototype.emptyFunction; return element; }, { refresh: function() { if (!Prototype.BrowserFeatures.ElementExtensions) { Object.extend(Methods, Element.Methods); Object.extend(Methods, Element.Methods.Simulated); } } }); extend.refresh(); return extend; })(); Element.hasAttribute = function(element, attribute) { if (element.hasAttribute) return element.hasAttribute(attribute); return Element.Methods.Simulated.hasAttribute(element, attribute); }; Element.addMethods = function(methods) { var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag; if (!methods) { Object.extend(Form, Form.Methods); Object.extend(Form.Element, Form.Element.Methods); Object.extend(Element.Methods.ByTag, { "FORM": Object.clone(Form.Methods), "INPUT": Object.clone(Form.Element.Methods), "SELECT": Object.clone(Form.Element.Methods), "TEXTAREA": Object.clone(Form.Element.Methods) }); } if (arguments.length == 2) { var tagName = methods; methods = arguments[1]; } if (!tagName) Object.extend(Element.Methods, methods || { }); else { if (Object.isArray(tagName)) tagName.each(extend); else extend(tagName); } function extend(tagName) { tagName = tagName.toUpperCase(); if (!Element.Methods.ByTag[tagName]) Element.Methods.ByTag[tagName] = { }; Object.extend(Element.Methods.ByTag[tagName], methods); } function copy(methods, destination, onlyIfAbsent) { onlyIfAbsent = onlyIfAbsent || false; for (var property in methods) { var value = methods[property]; if (!Object.isFunction(value)) continue; if (!onlyIfAbsent || !(property in destination)) destination[property] = value.methodize(); } } function findDOMClass(tagName) { var klass; var trans = { "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph", "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList", "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading", "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote", "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION": "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD": "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR": "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET": "FrameSet", "IFRAME": "IFrame" }; if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element'; if (window[klass]) return window[klass]; klass = 'HTML' + tagName + 'Element'; if (window[klass]) return window[klass]; klass = 'HTML' + tagName.capitalize() + 'Element'; if (window[klass]) return window[klass]; var element = document.createElement(tagName); var proto = element['__proto__'] || element.constructor.prototype; element = null; return proto; } var elementPrototype = window.HTMLElement ? HTMLElement.prototype : Element.prototype; if (F.ElementExtensions) { copy(Element.Methods, elementPrototype); copy(Element.Methods.Simulated, elementPrototype, true); } if (F.SpecificElementExtensions) { for (var tag in Element.Methods.ByTag) { var klass = findDOMClass(tag); if (Object.isUndefined(klass)) continue; copy(T[tag], klass.prototype); } } Object.extend(Element, Element.Methods); delete Element.ByTag; if (Element.extend.refresh) Element.extend.refresh(); Element.cache = { }; }; document.viewport = { getDimensions: function() { return { width: this.getWidth(), height: this.getHeight() }; }, getScrollOffsets: function() { return Element._returnOffset( window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft, window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); } }; (function(viewport) { var B = Prototype.Browser, doc = document, element, property = {}; function getRootElement() { if (B.WebKit && !doc.evaluate) return document; if (B.Opera && window.parseFloat(window.opera.version()) < 9.5) return document.body; return document.documentElement; } function define(D) { if (!element) element = getRootElement(); property[D] = 'client' + D; viewport['get' + D] = function() { return element[property[D]] }; return viewport['get' + D](); } viewport.getWidth = define.curry('Width'); viewport.getHeight = define.curry('Height'); })(document.viewport); Element.Storage = { UID: 1 }; Element.addMethods({ getStorage: function(element) { if (!(element = $(element))) return; var uid; if (element === window) { uid = 0; } else { if (typeof element._prototypeUID === "undefined") element._prototypeUID = [Element.Storage.UID++]; uid = element._prototypeUID[0]; } if (!Element.Storage[uid]) Element.Storage[uid] = $H(); return Element.Storage[uid]; }, store: function(element, key, value) { if (!(element = $(element))) return; if (arguments.length === 2) { Element.getStorage(element).update(key); } else { Element.getStorage(element).set(key, value); } return element; }, retrieve: function(element, key, defaultValue) { if (!(element = $(element))) return; var hash = Element.getStorage(element), value = hash.get(key); if (Object.isUndefined(value)) { hash.set(key, defaultValue); value = defaultValue; } return value; }, clone: function(element, deep) { if (!(element = $(element))) return; var clone = element.cloneNode(deep); clone._prototypeUID = void 0; if (deep) { var descendants = Element.select(clone, '*'), i = descendants.length; while (i--) { descendants[i]._prototypeUID = void 0; } } return Element.extend(clone); } }); /* Portions of the Selector class are derived from Jack Slocum's DomQuery, * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style * license. Please see http://www.yui-ext.com/ for more information. */ var Selector = Class.create({ initialize: function(expression) { this.expression = expression.strip(); if (this.shouldUseSelectorsAPI()) { this.mode = 'selectorsAPI'; } else if (this.shouldUseXPath()) { this.mode = 'xpath'; this.compileXPathMatcher(); } else { this.mode = "normal"; this.compileMatcher(); } }, shouldUseXPath: (function() { var IS_DESCENDANT_SELECTOR_BUGGY = (function(){ var isBuggy = false; if (document.evaluate && window.XPathResult) { var el = document.createElement('div'); el.innerHTML = '
'; var xpath = ".//*[local-name()='ul' or local-name()='UL']" + "//*[local-name()='li' or local-name()='LI']"; var result = document.evaluate(xpath, el, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); isBuggy = (result.snapshotLength !== 2); el = null; } return isBuggy; })(); return function() { if (!Prototype.BrowserFeatures.XPath) return false; var e = this.expression; if (Prototype.Browser.WebKit && (e.include("-of-type") || e.include(":empty"))) return false; if ((/(\[[\w-]*?:|:checked)/).test(e)) return false; if (IS_DESCENDANT_SELECTOR_BUGGY) return false; return true; } })(), shouldUseSelectorsAPI: function() { if (!Prototype.BrowserFeatures.SelectorsAPI) return false; if (Selector.CASE_INSENSITIVE_CLASS_NAMES) return false; if (!Selector._div) Selector._div = new Element('div'); try { Selector._div.querySelector(this.expression); } catch(e) { return false; } return true; }, compileMatcher: function() { var e = this.expression, ps = Selector.patterns, h = Selector.handlers, c = Selector.criteria, le, p, m, len = ps.length, name; if (Selector._cache[e]) { this.matcher = Selector._cache[e]; return; } this.matcher = ["this.matcher = function(root) {", "var r = root, h = Selector.handlers, c = false, n;"]; while (e && le != e && (/\S/).test(e)) { le = e; for (var i = 0; i"; } }); if (Prototype.BrowserFeatures.SelectorsAPI && document.compatMode === 'BackCompat') { Selector.CASE_INSENSITIVE_CLASS_NAMES = (function(){ var div = document.createElement('div'), span = document.createElement('span'); div.id = "prototype_test_id"; span.className = 'Test'; div.appendChild(span); var isIgnored = (div.querySelector('#prototype_test_id .test') !== null); div = span = null; return isIgnored; })(); } Object.extend(Selector, { _cache: { }, xpath: { descendant: "//*", child: "/*", adjacent: "/following-sibling::*[1]", laterSibling: '/following-sibling::*', tagName: function(m) { if (m[1] == '*') return ''; return "[local-name()='" + m[1].toLowerCase() + "' or local-name()='" + m[1].toUpperCase() + "']"; }, className: "[contains(concat(' ', @class, ' '), ' #{1} ')]", id: "[@id='#{1}']", attrPresence: function(m) { m[1] = m[1].toLowerCase(); return new Template("[@#{1}]").evaluate(m); }, attr: function(m) { m[1] = m[1].toLowerCase(); m[3] = m[5] || m[6]; return new Template(Selector.xpath.operators[m[2]]).evaluate(m); }, pseudo: function(m) { var h = Selector.xpath.pseudos[m[1]]; if (!h) return ''; if (Object.isFunction(h)) return h(m); return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m); }, operators: { '=': "[@#{1}='#{3}']", '!=': "[@#{1}!='#{3}']", '^=': "[starts-with(@#{1}, '#{3}')]", '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']", '*=': "[contains(@#{1}, '#{3}')]", '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]", '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]" }, pseudos: { 'first-child': '[not(preceding-sibling::*)]', 'last-child': '[not(following-sibling::*)]', 'only-child': '[not(preceding-sibling::* or following-sibling::*)]', 'empty': "[count(*) = 0 and (count(text()) = 0)]", 'checked': "[@checked]", 'disabled': "[(@disabled) and (@type!='hidden')]", 'enabled': "[not(@disabled) and (@type!='hidden')]", 'not': function(m) { var e = m[6], p = Selector.patterns, x = Selector.xpath, le, v, len = p.length, name; var exclusion = []; while (e && le != e && (/\S/).test(e)) { le = e; for (var i = 0; i= 0)]"; return new Template(predicate).evaluate({ fragment: fragment, a: a, b: b }); } } } }, criteria: { tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;', className: 'n = h.className(n, r, "#{1}", c); c = false;', id: 'n = h.id(n, r, "#{1}", c); c = false;', attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;', attr: function(m) { m[3] = (m[5] || m[6]); return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m); }, pseudo: function(m) { if (m[6]) m[6] = m[6].replace(/"/g, '\\"'); return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m); }, descendant: 'c = "descendant";', child: 'c = "child";', adjacent: 'c = "adjacent";', laterSibling: 'c = "laterSibling";' }, patterns: [ { name: 'laterSibling', re: /^\s*~\s*/ }, { name: 'child', re: /^\s*>\s*/ }, { name: 'adjacent', re: /^\s*\+\s*/ }, { name: 'descendant', re: /^\s/ }, { name: 'tagName', re: /^\s*(\*|[\w\-]+)(\b|$)?/ }, { name: 'id', re: /^#([\w\-\*]+)(\b|$)/ }, { name: 'className', re: /^\.([\w\-\*]+)(\b|$)/ }, { name: 'pseudo', re: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/ }, { name: 'attrPresence', re: /^\[((?:[\w-]+:)?[\w-]+)\]/ }, { name: 'attr', re: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ } ], assertions: { tagName: function(element, matches) { return matches[1].toUpperCase() == element.tagName.toUpperCase(); }, className: function(element, matches) { return Element.hasClassName(element, matches[1]); }, id: function(element, matches) { return element.id === matches[1]; }, attrPresence: function(element, matches) { return Element.hasAttribute(element, matches[1]); }, attr: function(element, matches) { var nodeValue = Element.readAttribute(element, matches[1]); return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]); } }, handlers: { concat: function(a, b) { for (var i = 0, node; node = b[i]; i++) a.push(node); return a; }, mark: function(nodes) { var _true = Prototype.emptyFunction; for (var i = 0, node; node = nodes[i]; i++) node._countedByPrototype = _true; return nodes; }, unmark: (function(){ var PROPERTIES_ATTRIBUTES_MAP = (function(){ var el = document.createElement('div'), isBuggy = false, propName = '_countedByPrototype', value = 'x' el[propName] = value; isBuggy = (el.getAttribute(propName) === value); el = null; return isBuggy; })(); return PROPERTIES_ATTRIBUTES_MAP ? function(nodes) { for (var i = 0, node; node = nodes[i]; i++) node.removeAttribute('_countedByPrototype'); return nodes; } : function(nodes) { for (var i = 0, node; node = nodes[i]; i++) node._countedByPrototype = void 0; return nodes; } })(), index: function(parentNode, reverse, ofType) { parentNode._countedByPrototype = Prototype.emptyFunction; if (reverse) { for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) { var node = nodes[i]; if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; } } else { for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++) if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; } }, unique: function(nodes) { if (nodes.length == 0) return nodes; var results = [], n; for (var i = 0, l = nodes.length; i < l; i++) if (typeof (n = nodes[i])._countedByPrototype == 'undefined') { n._countedByPrototype = Prototype.emptyFunction; results.push(Element.extend(n)); } return Selector.handlers.unmark(results); }, descendant: function(nodes) { var h = Selector.handlers; for (var i = 0, results = [], node; node = nodes[i]; i++) h.concat(results, node.getElementsByTagName('*')); return results; }, child: function(nodes) { var h = Selector.handlers; for (var i = 0, results = [], node; node = nodes[i]; i++) { for (var j = 0, child; child = node.childNodes[j]; j++) if (child.nodeType == 1 && child.tagName != '!') results.push(child); } return results; }, adjacent: function(nodes) { for (var i = 0, results = [], node; node = nodes[i]; i++) { var next = this.nextElementSibling(node); if (next) results.push(next); } return results; }, laterSibling: function(nodes) { var h = Selector.handlers; for (var i = 0, results = [], node; node = nodes[i]; i++) h.concat(results, Element.nextSiblings(node)); return results; }, nextElementSibling: function(node) { while (node = node.nextSibling) if (node.nodeType == 1) return node; return null; }, previousElementSibling: function(node) { while (node = node.previousSibling) if (node.nodeType == 1) return node; return null; }, tagName: function(nodes, root, tagName, combinator) { var uTagName = tagName.toUpperCase(); var results = [], h = Selector.handlers; if (nodes) { if (combinator) { if (combinator == "descendant") { for (var i = 0, node; node = nodes[i]; i++) h.concat(results, node.getElementsByTagName(tagName)); return results; } else nodes = this[combinator](nodes); if (tagName == "*") return nodes; } for (var i = 0, node; node = nodes[i]; i++) if (node.tagName.toUpperCase() === uTagName) results.push(node); return results; } else return root.getElementsByTagName(tagName); }, id: function(nodes, root, id, combinator) { var targetNode = $(id), h = Selector.handlers; if (root == document) { if (!targetNode) return []; if (!nodes) return [targetNode]; } else { if (!root.sourceIndex || root.sourceIndex < 1) { var nodes = root.getElementsByTagName('*'); for (var j = 0, node; node = nodes[j]; j++) { if (node.id === id) return [node]; } } } if (nodes) { if (combinator) { if (combinator == 'child') { for (var i = 0, node; node = nodes[i]; i++) if (targetNode.parentNode == node) return [targetNode]; } else if (combinator == 'descendant') { for (var i = 0, node; node = nodes[i]; i++) if (Element.descendantOf(targetNode, node)) return [targetNode]; } else if (combinator == 'adjacent') { for (var i = 0, node; node = nodes[i]; i++) if (Selector.handlers.previousElementSibling(targetNode) == node) return [targetNode]; } else nodes = h[combinator](nodes); } for (var i = 0, node; node = nodes[i]; i++) if (node == targetNode) return [targetNode]; return []; } return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : []; }, className: function(nodes, root, className, combinator) { if (nodes && combinator) nodes = this[combinator](nodes); return Selector.handlers.byClassName(nodes, root, className); }, byClassName: function(nodes, root, className) { if (!nodes) nodes = Selector.handlers.descendant([root]); var needle = ' ' + className + ' '; for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) { nodeClassName = node.className; if (nodeClassName.length == 0) continue; if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle)) results.push(node); } return results; }, attrPresence: function(nodes, root, attr, combinator) { if (!nodes) nodes = root.getElementsByTagName("*"); if (nodes && combinator) nodes = this[combinator](nodes); var results = []; for (var i = 0, node; node = nodes[i]; i++) if (Element.hasAttribute(node, attr)) results.push(node); return results; }, attr: function(nodes, root, attr, value, operator, combinator) { if (!nodes) nodes = root.getElementsByTagName("*"); if (nodes && combinator) nodes = this[combinator](nodes); var handler = Selector.operators[operator], results = []; for (var i = 0, node; node = nodes[i]; i++) { var nodeValue = Element.readAttribute(node, attr); if (nodeValue === null) continue; if (handler(nodeValue, value)) results.push(node); } return results; }, pseudo: function(nodes, name, value, root, combinator) { if (nodes && combinator) nodes = this[combinator](nodes); if (!nodes) nodes = root.getElementsByTagName("*"); return Selector.pseudos[name](nodes, value, root); } }, pseudos: { 'first-child': function(nodes, value, root) { for (var i = 0, results = [], node; node = nodes[i]; i++) { if (Selector.handlers.previousElementSibling(node)) continue; results.push(node); } return results; }, 'last-child': function(nodes, value, root) { for (var i = 0, results = [], node; node = nodes[i]; i++) { if (Selector.handlers.nextElementSibling(node)) continue; results.push(node); } return results; }, 'only-child': function(nodes, value, root) { var h = Selector.handlers; for (var i = 0, results = [], node; node = nodes[i]; i++) if (!h.previousElementSibling(node) && !h.nextElementSibling(node)) results.push(node); return results; }, 'nth-child': function(nodes, formula, root) { return Selector.pseudos.nth(nodes, formula, root); }, 'nth-last-child': function(nodes, formula, root) { return Selector.pseudos.nth(nodes, formula, root, true); }, 'nth-of-type': function(nodes, formula, root) { return Selector.pseudos.nth(nodes, formula, root, false, true); }, 'nth-last-of-type': function(nodes, formula, root) { return Selector.pseudos.nth(nodes, formula, root, true, true); }, 'first-of-type': function(nodes, formula, root) { return Selector.pseudos.nth(nodes, "1", root, false, true); }, 'last-of-type': function(nodes, formula, root) { return Selector.pseudos.nth(nodes, "1", root, true, true); }, 'only-of-type': function(nodes, formula, root) { var p = Selector.pseudos; return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root); }, getIndices: function(a, b, total) { if (a == 0) return b > 0 ? [b] : []; return $R(1, total).inject([], function(memo, i) { if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i); return memo; }); }, nth: function(nodes, formula, root, reverse, ofType) { if (nodes.length == 0) return []; if (formula == 'even') formula = '2n+0'; if (formula == 'odd') formula = '2n+1'; var h = Selector.handlers, results = [], indexed = [], m; h.mark(nodes); for (var i = 0, node; node = nodes[i]; i++) { if (!node.parentNode._countedByPrototype) { h.index(node.parentNode, reverse, ofType); indexed.push(node.parentNode); } } if (formula.match(/^\d+$/)) { // just a number formula = Number(formula); for (var i = 0, node; node = nodes[i]; i++) if (node.nodeIndex == formula) results.push(node); } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b if (m[1] == "-") m[1] = -1; var a = m[1] ? Number(m[1]) : 1; var b = m[2] ? Number(m[2]) : 0; var indices = Selector.pseudos.getIndices(a, b, nodes.length); for (var i = 0, node, l = indices.length; node = nodes[i]; i++) { for (var j = 0; j < l; j++) if (node.nodeIndex == indices[j]) results.push(node); } } h.unmark(nodes); h.unmark(indexed); return results; }, 'empty': function(nodes, value, root) { for (var i = 0, results = [], node; node = nodes[i]; i++) { if (node.tagName == '!' || node.firstChild) continue; results.push(node); } return results; }, 'not': function(nodes, selector, root) { var h = Selector.handlers, selectorType, m; var exclusions = new Selector(selector).findElements(root); h.mark(exclusions); for (var i = 0, results = [], node; node = nodes[i]; i++) if (!node._countedByPrototype) results.push(node); h.unmark(exclusions); return results; }, 'enabled': function(nodes, value, root) { for (var i = 0, results = [], node; node = nodes[i]; i++) if (!node.disabled && (!node.type || node.type !== 'hidden')) results.push(node); return results; }, 'disabled': function(nodes, value, root) { for (var i = 0, results = [], node; node = nodes[i]; i++) if (node.disabled) results.push(node); return results; }, 'checked': function(nodes, value, root) { for (var i = 0, results = [], node; node = nodes[i]; i++) if (node.checked) results.push(node); return results; } }, operators: { '=': function(nv, v) { return nv == v; }, '!=': function(nv, v) { return nv != v; }, '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); }, '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); }, '*=': function(nv, v) { return nv == v || nv && nv.include(v); }, '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); }, '|=': function(nv, v) { return ('-' + (nv || "").toUpperCase() + '-').include('-' + (v || "").toUpperCase() + '-'); } }, split: function(expression) { var expressions = []; expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { expressions.push(m[1].strip()); }); return expressions; }, matchElements: function(elements, expression) { var matches = $$(expression), h = Selector.handlers; h.mark(matches); for (var i = 0, results = [], element; element = elements[i]; i++) if (element._countedByPrototype) results.push(element); h.unmark(matches); return results; }, findElement: function(elements, expression, index) { if (Object.isNumber(expression)) { index = expression; expression = false; } return Selector.matchElements(elements, expression || '*')[index || 0]; }, findChildElements: function(element, expressions) { expressions = Selector.split(expressions.join(',')); var results = [], h = Selector.handlers; for (var i = 0, l = expressions.length, selector; i < l; i++) { selector = new Selector(expressions[i].strip()); h.concat(results, selector.findElements(element)); } return (l > 1) ? h.unique(results) : results; } }); if (Prototype.Browser.IE) { Object.extend(Selector.handlers, { concat: function(a, b) { for (var i = 0, node; node = b[i]; i++) if (node.tagName !== "!") a.push(node); return a; } }); } function $$() { return Selector.findChildElements(document, $A(arguments)); } var Form = { reset: function(form) { form = $(form); form.reset(); return form; }, serializeElements: function(elements, options) { if (typeof options != 'object') options = { hash: !!options }; else if (Object.isUndefined(options.hash)) options.hash = true; var key, value, submitted = false, submit = options.submit; var data = elements.inject({ }, function(result, element) { if (!element.disabled && element.name) { key = element.name; value = $(element).getValue(); if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted && submit !== false && (!submit || key == submit) && (submitted = true)))) { if (key in result) { if (!Object.isArray(result[key])) result[key] = [result[key]]; result[key].push(value); } else result[key] = value; } } return result; }); return options.hash ? data : Object.toQueryString(data); } }; Form.Methods = { serialize: function(form, options) { return Form.serializeElements(Form.getElements(form), options); }, getElements: function(form) { var elements = $(form).getElementsByTagName('*'), element, arr = [ ], serializers = Form.Element.Serializers; for (var i = 0; element = elements[i]; i++) { arr.push(element); } return arr.inject([], function(elements, child) { if (serializers[child.tagName.toLowerCase()]) elements.push(Element.extend(child)); return elements; }) }, getInputs: function(form, typeName, name) { form = $(form); var inputs = form.getElementsByTagName('input'); if (!typeName && !name) return $A(inputs).map(Element.extend); for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { var input = inputs[i]; if ((typeName && input.type != typeName) || (name && input.name != name)) continue; matchingInputs.push(Element.extend(input)); } return matchingInputs; }, disable: function(form) { form = $(form); Form.getElements(form).invoke('disable'); return form; }, enable: function(form) { form = $(form); Form.getElements(form).invoke('enable'); return form; }, findFirstElement: function(form) { var elements = $(form).getElements().findAll(function(element) { return 'hidden' != element.type && !element.disabled; }); var firstByIndex = elements.findAll(function(element) { return element.hasAttribute('tabIndex') && element.tabIndex >= 0; }).sortBy(function(element) { return element.tabIndex }).first(); return firstByIndex ? firstByIndex : elements.find(function(element) { return /^(?:input|select|textarea)$/i.test(element.tagName); }); }, focusFirstElement: function(form) { form = $(form); form.findFirstElement().activate(); return form; }, request: function(form, options) { form = $(form), options = Object.clone(options || { }); var params = options.parameters, action = form.readAttribute('action') || ''; if (action.blank()) action = window.location.href; options.parameters = form.serialize(true); if (params) { if (Object.isString(params)) params = params.toQueryParams(); Object.extend(options.parameters, params); } if (form.hasAttribute('method') && !options.method) options.method = form.method; return new Ajax.Request(action, options); } }; /*--------------------------------------------------------------------------*/ Form.Element = { focus: function(element) { $(element).focus(); return element; }, select: function(element) { $(element).select(); return element; } }; Form.Element.Methods = { serialize: function(element) { element = $(element); if (!element.disabled && element.name) { var value = element.getValue(); if (value != undefined) { var pair = { }; pair[element.name] = value; return Object.toQueryString(pair); } } return ''; }, getValue: function(element) { element = $(element); var method = element.tagName.toLowerCase(); return Form.Element.Serializers[method](element); }, setValue: function(element, value) { element = $(element); var method = element.tagName.toLowerCase(); Form.Element.Serializers[method](element, value); return element; }, clear: function(element) { $(element).value = ''; return element; }, present: function(element) { return $(element).value != ''; }, activate: function(element) { element = $(element); try { element.focus(); if (element.select && (element.tagName.toLowerCase() != 'input' || !(/^(?:button|reset|submit)$/i.test(element.type)))) element.select(); } catch (e) { } return element; }, disable: function(element) { element = $(element); element.disabled = true; return element; }, enable: function(element) { element = $(element); element.disabled = false; return element; } }; /*--------------------------------------------------------------------------*/ var Field = Form.Element; var $F = Form.Element.Methods.getValue; /*--------------------------------------------------------------------------*/ Form.Element.Serializers = { input: function(element, value) { switch (element.type.toLowerCase()) { case 'checkbox': case 'radio': return Form.Element.Serializers.inputSelector(element, value); default: return Form.Element.Serializers.textarea(element, value); } }, inputSelector: function(element, value) { if (Object.isUndefined(value)) return element.checked ? element.value : null; else element.checked = !!value; }, textarea: function(element, value) { if (Object.isUndefined(value)) return element.value; else element.value = value; }, select: function(element, value) { if (Object.isUndefined(value)) return this[element.type == 'select-one' ? 'selectOne' : 'selectMany'](element); else { var opt, currentValue, single = !Object.isArray(value); for (var i = 0, length = element.length; i < length; i++) { opt = element.options[i]; currentValue = this.optionValue(opt); if (single) { if (currentValue == value) { opt.selected = true; return; } } else opt.selected = value.include(currentValue); } } }, selectOne: function(element) { var index = element.selectedIndex; return index >= 0 ? this.optionValue(element.options[index]) : null; }, selectMany: function(element) { var values, length = element.length; if (!length) return null; for (var i = 0, values = []; i < length; i++) { var opt = element.options[i]; if (opt.selected) values.push(this.optionValue(opt)); } return values; }, optionValue: function(opt) { return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; } }; /*--------------------------------------------------------------------------*/ Abstract.TimedObserver = Class.create(PeriodicalExecuter, { initialize: function($super, element, frequency, callback) { $super(callback, frequency); this.element = $(element); this.lastValue = this.getValue(); }, execute: function() { var value = this.getValue(); if (Object.isString(this.lastValue) && Object.isString(value) ? this.lastValue != value : String(this.lastValue) != String(value)) { this.callback(this.element, value); this.lastValue = value; } } }); Form.Element.Observer = Class.create(Abstract.TimedObserver, { getValue: function() { return Form.Element.getValue(this.element); } }); Form.Observer = Class.create(Abstract.TimedObserver, { getValue: function() { return Form.serialize(this.element); } }); /*--------------------------------------------------------------------------*/ Abstract.EventObserver = Class.create({ initialize: function(element, callback) { this.element = $(element); this.callback = callback; this.lastValue = this.getValue(); if (this.element.tagName.toLowerCase() == 'form') this.registerFormCallbacks(); else this.registerCallback(this.element); }, onElementEvent: function() { var value = this.getValue(); if (this.lastValue != value) { this.callback(this.element, value); this.lastValue = value; } }, registerFormCallbacks: function() { Form.getElements(this.element).each(this.registerCallback, this); }, registerCallback: function(element) { if (element.type) { switch (element.type.toLowerCase()) { case 'checkbox': case 'radio': Event.observe(element, 'click', this.onElementEvent.bind(this)); break; default: Event.observe(element, 'change', this.onElementEvent.bind(this)); break; } } } }); Form.Element.EventObserver = Class.create(Abstract.EventObserver, { getValue: function() { return Form.Element.getValue(this.element); } }); Form.EventObserver = Class.create(Abstract.EventObserver, { getValue: function() { return Form.serialize(this.element); } }); (function() { var Event = { KEY_BACKSPACE: 8, KEY_TAB: 9, KEY_RETURN: 13, KEY_ESC: 27, KEY_LEFT: 37, KEY_UP: 38, KEY_RIGHT: 39, KEY_DOWN: 40, KEY_DELETE: 46, KEY_HOME: 36, KEY_END: 35, KEY_PAGEUP: 33, KEY_PAGEDOWN: 34, KEY_INSERT: 45, cache: {} }; var docEl = document.documentElement; var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl && 'onmouseleave' in docEl; var _isButton; if (Prototype.Browser.IE) { var buttonMap = { 0: 1, 1: 4, 2: 2 }; _isButton = function(event, code) { return event.button === buttonMap[code]; }; } else if (Prototype.Browser.WebKit) { _isButton = function(event, code) { switch (code) { case 0: return event.which == 1 && !event.metaKey; case 1: return event.which == 1 && event.metaKey; default: return false; } }; } else { _isButton = function(event, code) { return event.which ? (event.which === code + 1) : (event.button === code); }; } function isLeftClick(event) { return _isButton(event, 0) } function isMiddleClick(event) { return _isButton(event, 1) } function isRightClick(event) { return _isButton(event, 2) } function element(event) { event = Event.extend(event); var node = event.target, type = event.type, currentTarget = event.currentTarget; if (currentTarget && currentTarget.tagName) { if (type === 'load' || type === 'error' || (type === 'click' && currentTarget.tagName.toLowerCase() === 'input' && currentTarget.type === 'radio')) node = currentTarget; } if (node.nodeType == Node.TEXT_NODE) node = node.parentNode; return Element.extend(node); } function findElement(event, expression) { var element = Event.element(event); if (!expression) return element; var elements = [element].concat(element.ancestors()); return Selector.findElement(elements, expression, 0); } function pointer(event) { return { x: pointerX(event), y: pointerY(event) }; } function pointerX(event) { var docElement = document.documentElement, body = document.body || { scrollLeft: 0 }; return event.pageX || (event.clientX + (docElement.scrollLeft || body.scrollLeft) - (docElement.clientLeft || 0)); } function pointerY(event) { var docElement = document.documentElement, body = document.body || { scrollTop: 0 }; return event.pageY || (event.clientY + (docElement.scrollTop || body.scrollTop) - (docElement.clientTop || 0)); } function stop(event) { Event.extend(event); event.preventDefault(); event.stopPropagation(); event.stopped = true; } Event.Methods = { isLeftClick: isLeftClick, isMiddleClick: isMiddleClick, isRightClick: isRightClick, element: element, findElement: findElement, pointer: pointer, pointerX: pointerX, pointerY: pointerY, stop: stop }; var methods = Object.keys(Event.Methods).inject({ }, function(m, name) { m[name] = Event.Methods[name].methodize(); return m; }); if (Prototype.Browser.IE) { function _relatedTarget(event) { var element; switch (event.type) { case 'mouseover': element = event.fromElement; break; case 'mouseout': element = event.toElement; break; default: return null; } return Element.extend(element); } Object.extend(methods, { stopPropagation: function() { this.cancelBubble = true }, preventDefault: function() { this.returnValue = false }, inspect: function() { return '[object Event]' } }); Event.extend = function(event, element) { if (!event) return false; if (event._extendedByPrototype) return event; event._extendedByPrototype = Prototype.emptyFunction; var pointer = Event.pointer(event); Object.extend(event, { target: event.srcElement || element, relatedTarget: _relatedTarget(event), pageX: pointer.x, pageY: pointer.y }); return Object.extend(event, methods); }; } else { Event.prototype = window.Event.prototype || document.createEvent('HTMLEvents').__proto__; Object.extend(Event.prototype, methods); Event.extend = Prototype.K; } function _createResponder(element, eventName, handler) { var registry = Element.retrieve(element, 'prototype_event_registry'); if (Object.isUndefined(registry)) { CACHE.push(element); registry = Element.retrieve(element, 'prototype_event_registry', $H()); } var respondersForEvent = registry.get(eventName); if (Object.isUndefined(respondersForEvent)) { respondersForEvent = []; registry.set(eventName, respondersForEvent); } if (respondersForEvent.pluck('handler').include(handler)) return false; var responder; if (eventName.include(":")) { responder = function(event) { if (Object.isUndefined(event.eventName)) return false; if (event.eventName !== eventName) return false; Event.extend(event, element); handler.call(element, event); }; } else { if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED && (eventName === "mouseenter" || eventName === "mouseleave")) { if (eventName === "mouseenter" || eventName === "mouseleave") { responder = function(event) { Event.extend(event, element); var parent = event.relatedTarget; while (parent && parent !== element) { try { parent = parent.parentNode; } catch(e) { parent = element; } } if (parent === element) return; handler.call(element, event); }; } } else { responder = function(event) { Event.extend(event, element); handler.call(element, event); }; } } responder.handler = handler; respondersForEvent.push(responder); return responder; } function _destroyCache() { for (var i = 0, length = CACHE.length; i < length; i++) { Event.stopObserving(CACHE[i]); CACHE[i] = null; } } var CACHE = []; if (Prototype.Browser.IE) window.attachEvent('onunload', _destroyCache); if (Prototype.Browser.WebKit) window.addEventListener('unload', Prototype.emptyFunction, false); var _getDOMEventName = Prototype.K; if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED) { _getDOMEventName = function(eventName) { var translations = { mouseenter: "mouseover", mouseleave: "mouseout" }; return eventName in translations ? translations[eventName] : eventName; }; } function observe(element, eventName, handler) { element = $(element); var responder = _createResponder(element, eventName, handler); if (!responder) return element; if (eventName.include(':')) { if (element.addEventListener) element.addEventListener("dataavailable", responder, false); else { element.attachEvent("ondataavailable", responder); element.attachEvent("onfilterchange", responder); } } else { var actualEventName = _getDOMEventName(eventName); if (element.addEventListener) element.addEventListener(actualEventName, responder, false); else element.attachEvent("on" + actualEventName, responder); } return element; } function stopObserving(element, eventName, handler) { element = $(element); var registry = Element.retrieve(element, 'prototype_event_registry'); if (Object.isUndefined(registry)) return element; if (eventName && !handler) { var responders = registry.get(eventName); if (Object.isUndefined(responders)) return element; responders.each( function(r) { Element.stopObserving(element, eventName, r.handler); }); return element; } else if (!eventName) { registry.each( function(pair) { var eventName = pair.key, responders = pair.value; responders.each( function(r) { Element.stopObserving(element, eventName, r.handler); }); }); return element; } var responders = registry.get(eventName); if (!responders) return; var responder = responders.find( function(r) { return r.handler === handler; }); if (!responder) return element; var actualEventName = _getDOMEventName(eventName); if (eventName.include(':')) { if (element.removeEventListener) element.removeEventListener("dataavailable", responder, false); else { element.detachEvent("ondataavailable", responder); element.detachEvent("onfilterchange", responder); } } else { if (element.removeEventListener) element.removeEventListener(actualEventName, responder, false); else element.detachEvent('on' + actualEventName, responder); } registry.set(eventName, responders.without(responder)); return element; } function fire(element, eventName, memo, bubble) { element = $(element); if (Object.isUndefined(bubble)) bubble = true; if (element == document && document.createEvent && !element.dispatchEvent) element = document.documentElement; var event; if (document.createEvent) { event = document.createEvent('HTMLEvents'); event.initEvent('dataavailable', true, true); } else { event = document.createEventObject(); event.eventType = bubble ? 'ondataavailable' : 'onfilterchange'; } event.eventName = eventName; event.memo = memo || { }; if (document.createEvent) element.dispatchEvent(event); else element.fireEvent(event.eventType, event); return Event.extend(event); } Object.extend(Event, Event.Methods); Object.extend(Event, { fire: fire, observe: observe, stopObserving: stopObserving }); Element.addMethods({ fire: fire, observe: observe, stopObserving: stopObserving }); Object.extend(document, { fire: fire.methodize(), observe: observe.methodize(), stopObserving: stopObserving.methodize(), loaded: false }); if (window.Event) Object.extend(window.Event, Event); else window.Event = Event; })(); (function() { /* Support for the DOMContentLoaded event is based on work by Dan Webb, Matthias Miller, Dean Edwards, John Resig, and Diego Perini. */ var timer; function fireContentLoadedEvent() { if (document.loaded) return; if (timer) window.clearTimeout(timer); document.loaded = true; document.fire('dom:loaded'); } function checkReadyState() { if (document.readyState === 'complete') { document.stopObserving('readystatechange', checkReadyState); fireContentLoadedEvent(); } } function pollDoScroll() { try { document.documentElement.doScroll('left'); } catch(e) { timer = pollDoScroll.defer(); return; } fireContentLoadedEvent(); } if (document.addEventListener) { document.addEventListener('DOMContentLoaded', fireContentLoadedEvent, false); } else { document.observe('readystatechange', checkReadyState); if (window == top) timer = pollDoScroll.defer(); } Event.observe(window, 'load', fireContentLoadedEvent); })(); Element.addMethods(); /*------------------------------- DEPRECATED -------------------------------*/ Hash.toQueryString = Object.toQueryString; var Toggle = { display: Element.toggle }; Element.Methods.childOf = Element.Methods.descendantOf; var Insertion = { Before: function(element, content) { return Element.insert(element, {before:content}); }, Top: function(element, content) { return Element.insert(element, {top:content}); }, Bottom: function(element, content) { return Element.insert(element, {bottom:content}); }, After: function(element, content) { return Element.insert(element, {after:content}); } }; var $continue = new Error('"throw $continue" is deprecated, use "return" instead'); var Position = { includeScrollOffsets: false, prepare: function() { this.deltaX = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0; this.deltaY = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0; }, within: function(element, x, y) { if (this.includeScrollOffsets) return this.withinIncludingScrolloffsets(element, x, y); this.xcomp = x; this.ycomp = y; this.offset = Element.cumulativeOffset(element); return (y >= this.offset[1] && y < this.offset[1] + element.offsetHeight && x >= this.offset[0] && x < this.offset[0] + element.offsetWidth); }, withinIncludingScrolloffsets: function(element, x, y) { var offsetcache = Element.cumulativeScrollOffset(element); this.xcomp = x + offsetcache[0] - this.deltaX; this.ycomp = y + offsetcache[1] - this.deltaY; this.offset = Element.cumulativeOffset(element); return (this.ycomp >= this.offset[1] && this.ycomp < this.offset[1] + element.offsetHeight && this.xcomp >= this.offset[0] && this.xcomp < this.offset[0] + element.offsetWidth); }, overlap: function(mode, element) { if (!mode) return 0; if (mode == 'vertical') return ((this.offset[1] + element.offsetHeight) - this.ycomp) / element.offsetHeight; if (mode == 'horizontal') return ((this.offset[0] + element.offsetWidth) - this.xcomp) / element.offsetWidth; }, cumulativeOffset: Element.Methods.cumulativeOffset, positionedOffset: Element.Methods.positionedOffset, absolutize: function(element) { Position.prepare(); return Element.absolutize(element); }, relativize: function(element) { Position.prepare(); return Element.relativize(element); }, realOffset: Element.Methods.cumulativeScrollOffset, offsetParent: Element.Methods.getOffsetParent, page: Element.Methods.viewportOffset, clone: function(source, target, options) { options = options || { }; return Element.clonePosition(target, source, options); } }; /*--------------------------------------------------------------------------*/ if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){ function iter(name) { return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]"; } instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ? function(element, className) { className = className.toString().strip(); var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className); return cond ? document._getElementsByXPath('.//*' + cond, element) : []; } : function(element, className) { className = className.toString().strip(); var elements = [], classNames = (/\s/.test(className) ? $w(className) : null); if (!classNames && !className) return elements; var nodes = $(element).getElementsByTagName('*'); className = ' ' + className + ' '; for (var i = 0, child, cn; child = nodes[i]; i++) { if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) || (classNames && classNames.all(function(name) { return !name.toString().blank() && cn.include(' ' + name + ' '); })))) elements.push(Element.extend(child)); } return elements; }; return function(className, parentElement) { return $(parentElement || document.body).getElementsByClassName(className); }; }(Element.Methods); /*--------------------------------------------------------------------------*/ Element.ClassNames = Class.create(); Element.ClassNames.prototype = { initialize: function(element) { this.element = $(element); }, _each: function(iterator) { this.element.className.split(/\s+/).select(function(name) { return name.length > 0; })._each(iterator); }, set: function(className) { this.element.className = className; }, add: function(classNameToAdd) { if (this.include(classNameToAdd)) return; this.set($A(this).concat(classNameToAdd).join(' ')); }, remove: function(classNameToRemove) { if (!this.include(classNameToRemove)) return; this.set($A(this).without(classNameToRemove).join(' ')); }, toString: function() { return $A(this).join(' '); } }; Object.extend(Element.ClassNames.prototype, Enumerable); /*--------------------------------------------------------------------------*/ faye-1.4.0/examples/public/soapbox.js000066400000000000000000000070031371104377200175520ustar00rootroot00000000000000Soapbox = { /** * Initializes the application, passing in the globally shared Bayeux client. * Apps on the same page should share a Bayeux client so that they may share * an open HTTP connection with the server. */ init: function(bayeux) { var self = this; this._bayeux = bayeux; this._login = $('#enterUsername'); this._app = $('#app'); this._follow = $('#addFollowee'); this._post = $('#postMessage'); this._stream = $('#stream'); this._app.hide(); // When the user enters a username, store it and start the app this._login.submit(function() { self._username = $('#username').val(); self.launch(); return false; }); this._bayeux.addExtension({ outgoing: function(message, callback) { var type = message.connectionType; if (type) $('#transport').html('(' + type + ')'); callback(message); } }); }, /** * Starts the application after a username has been entered. A subscription is * made to receive messages that mention this user, and forms are set up to * accept new followers and send messages. */ launch: function() { var self = this; this._bayeux.subscribe('/members/' + this._username, this.accept, this); // Hide login form, show main application UI this._login.fadeOut('slow', function() { self._app.fadeIn('slow'); }); // When we add a follower, subscribe to a channel to which the followed user // will publish messages this._follow.submit(function() { var follow = $('#followee'), name = follow.val(); self._bayeux.subscribe('/chat/' + name, self.accept, self); follow.val(''); return false; }); // When we enter a message, send it and clear the message field. this._post.submit(function() { var msg = $('#message'); self.post(msg.val()); msg.val(''); return false; }); // Detect network problems and disable the form when offline this._bayeux.bind('transport:down', function() { this._post.find('textarea,input').attr('disabled', true); }, this); this._bayeux.bind('transport:up', function() { this._post.find('textarea,input').attr('disabled', false); }, this); }, /** * Sends messages that the user has entered. The message is scanned for * @reply-style mentions of other users, and the message is sent to those * users' channels. */ post: function(message) { var mentions = [], words = message.split(/\s+/), self = this, pattern = /\@[a-z0-9]+/i; // Extract @replies from the message $.each(words, function(i, word) { if (!pattern.test(word)) return; word = word.replace(/[^a-z0-9]/ig, ''); if (word !== self._username) mentions.push(word); }); // Message object to transmit over Bayeux channels message = { user: this._username, message: message }; // Publish to this user's 'from' channel, and to channels for any @replies // found in the message this._bayeux.publish('/chat/' + this._username, message); $.each(mentions, function(i, name) { self._bayeux.publish('/members/' + name, message); }); }, /** * Handler for messages received over subscribed channels. Takes the message * object sent by the post() method and displays it in the user's message list. */ accept: function(message) { this._stream.prepend('
  • ' + message.user + ': ' + message.message + '
  • '); } }; faye-1.4.0/examples/public/style.css000066400000000000000000000012251371104377200174130ustar00rootroot00000000000000body { margin: 0; padding: 0; font: 16px/1.5 FreeSans, Helvetica, Arial, sans-serif; text-align: center; } .container { text-align: left; width: 400px; margin: 0 auto; } h1, form, ul li { padding: 24px 0; border-bottom: 1px solid #c0c0c0; } h1 { font-size: 20px; } h1 em { font-style: normal; font-weight: normal; color: #444; text-transform: uppercase; letter-spacing: -0.06em; } label { color: #444; font-weight: bold; } ul, li { list-style: none; margin: 0; padding: 0; } ul li { padding: 12px 0; } faye-1.4.0/examples/public/ticker.html000066400000000000000000000014061371104377200177110ustar00rootroot00000000000000 Ticker

    faye-1.4.0/examples/ruby/000077500000000000000000000000001371104377200152445ustar00rootroot00000000000000faye-1.4.0/examples/ruby/app.rb000066400000000000000000000014551371104377200163560ustar00rootroot00000000000000require 'sinatra' require 'faye' require 'permessage_deflate' ROOT_DIR = File.expand_path('../..', __FILE__) set :root, ROOT_DIR set :logging, false get '/' do File.read(ROOT_DIR + '/public/index.html') end get '/post' do env['faye.client'].publish('/mentioning/*', { :user => 'sinatra', :message => params[:message] }) params[:message] end App = Faye::RackAdapter.new(Sinatra::Application, :mount => '/bayeux', :timeout => 25 ) App.add_websocket_extension(PermessageDeflate) def App.log(message) end App.on(:subscribe) do |client_id, channel| puts "[ SUBSCRIBE] #{ client_id } -> #{ channel }" end App.on(:unsubscribe) do |client_id, channel| puts "[UNSUBSCRIBE] #{ client_id } -> #{ channel }" end App.on(:disconnect) do |client_id| puts "[ DISCONNECT] #{ client_id }" end faye-1.4.0/examples/ruby/benchmark.rb000066400000000000000000000017021371104377200175230ustar00rootroot00000000000000require 'rubygems' require 'bundler/setup' require 'faye' port = ARGV[0] || 9292 path = ARGV[1] || 'bayeux' scheme = ARGV[2] == 'tls' ? 'https' : 'http' EM.run { A = Faye::Client.new("#{ scheme }://0.0.0.0:#{ port }/#{ path }") B = Faye::Client.new("#{ scheme }://0.0.0.0:#{ port }/#{ path }") A.connect do B.connect do time = Time.now.to_f * 1000 MAX = 1000 stop = lambda do puts Time.now.to_f * 1000 - time EM.stop end handle = lambda do |client, channel| lambda do |n| if n == MAX stop.call else client.publish(channel, n + 1) end end end sub_a = A.subscribe('/chat/a', &handle.call(A, '/chat/b')) sub_b = B.subscribe('/chat/b', &handle.call(B, '/chat/a')) sub_a.callback do sub_b.callback do puts 'START' A.publish('/chat/b', 0) end end end end } faye-1.4.0/examples/ruby/client.rb000066400000000000000000000026601371104377200170530ustar00rootroot00000000000000# This script demonstrates a logger for the chat app. First, start the chat # server in one terminal then run this in another: # # $ ruby examples/ruby/server.rb # $ ruby examples/ruby/client.rb # # The client connects to the chat server and logs all messages sent by all # connected users. require 'rubygems' require 'bundler/setup' require 'faye' require 'permessage_deflate' port = ARGV[0] || 9292 path = ARGV[1] || 'bayeux' scheme = ARGV[2] == 'tls' ? 'https' : 'http' endpoint = "#{ scheme }://user:pass@0.0.0.0:#{ port }/#{ path }" proxy = { :headers => { 'User-Agent' => 'Faye' }} EM.run { puts "Connecting to #{ endpoint }" client = Faye::Client.new(endpoint, :proxy => proxy) client.add_websocket_extension(PermessageDeflate) subscription = client.subscribe '/chat/*' do |message| user = message['user'] publication = client.publish("/members/#{ user }", { "user" => "ruby-logger", "message" => "Got your message, #{ user }!" }) publication.callback do puts "[PUBLISH SUCCEEDED]" end publication.errback do |error| puts "[PUBLISH FAILED] #{ error.inspect }" end end subscription.callback do puts "[SUBSCRIBE SUCCEEDED]" end subscription.errback do |error| puts "[SUBSCRIBE FAILED] #{ error.inspect }" end client.bind 'transport:down' do puts "[CONNECTION DOWN]" end client.bind 'transport:up' do puts "[CONNECTION UP]" end } faye-1.4.0/examples/ruby/config.ru000066400000000000000000000011451371104377200170620ustar00rootroot00000000000000# Run using your favourite async server: # # thin start -R examples/ruby/config.ru -p 9292 # rainbows -c examples/ruby/rainbows.conf -E production examples/ruby/config.ru -p 9292 # # If you run using one of these commands, the webserver is loaded before this # file, so Faye::WebSocket can figure out which adapter to load. If instead you # run using `rackup`, you need the `load_adapter` line below. # # rackup -E production -s thin examples/ruby/config.ru -p 9292 require 'rubygems' require 'bundler/setup' require File.expand_path('../app', __FILE__) Faye::WebSocket.load_adapter('thin') run App faye-1.4.0/examples/ruby/pingpong.rb000066400000000000000000000007321371104377200174140ustar00rootroot00000000000000require 'rubygems' require 'bundler/setup' require 'faye' EM.run { ENDPOINT = 'http://0.0.0.0:9292/bayeux' puts 'Connecting to ' + ENDPOINT ping = Faye::Client.new(ENDPOINT) ping.subscribe('/ping') do puts 'PING' EM.add_timer(1) { ping.publish('/pong', {}) } end pong = Faye::Client.new(ENDPOINT) pong.subscribe('/pong') do puts 'PONG' EM.add_timer(1) { ping.publish('/ping', {}) } end EM.add_timer(0.5) { ping.publish('/pong', {}) } } faye-1.4.0/examples/ruby/rainbows.conf000066400000000000000000000000401371104377200177310ustar00rootroot00000000000000Rainbows! { use :EventMachine } faye-1.4.0/examples/ruby/server.rb000066400000000000000000000023061371104377200171000ustar00rootroot00000000000000require 'rubygems' require 'bundler/setup' port = ARGV[0] || 9292 secure = ARGV[1] == 'tls' engine = ARGV[2] || 'thin' shared = File.expand_path('../..', __FILE__) require File.expand_path('../app', __FILE__) Faye::WebSocket.load_adapter(engine) case engine when 'goliath' class FayeServer < Goliath::API def response(env) App.call(env) end end when 'puma' require 'puma/events' events = Puma::Events.new($stdout, $stderr) require 'puma/binder' binder = Puma::Binder.new(events) binder.parse(["tcp://0.0.0.0:#{ port }"], App) server = Puma::Server.new(App, events) server.binder = binder server.run.join when 'rainbows' rackup = Unicorn::Configurator::RACKUP rackup[:port] = port rackup[:set_listener] = true options = rackup[:options] options[:config_file] = File.expand_path('../rainbows.conf', __FILE__) Rainbows::HttpServer.new(App, options).start.join when 'thin' thin = Rack::Handler.get('thin') thin.run(App, :Host => '0.0.0.0', :Port => port) do |server| if secure server.ssl_options = { :private_key_file => shared + '/server.key', :cert_chain_file => shared + '/server.crt' } server.ssl = true end end end faye-1.4.0/examples/ruby/ticker.rb000066400000000000000000000004171371104377200170540ustar00rootroot00000000000000require 'rubygems' require 'bundler/setup' require 'faye' EM.run { endpoint = ARGV.first || 'http://0.0.0.0:9292/bayeux' client = Faye::Client.new(endpoint) n = 0 EM.add_periodic_timer 1 do n += 1 client.publish('/chat/tick', 'n' => n) end } faye-1.4.0/examples/server.crt000066400000000000000000000012241371104377200163020ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIBuTCCASICCQDJW77Y8qm64zANBgkqhkiG9w0BAQUFADAhMQswCQYDVQQGEwJV SzESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIwMDIxMzE0MzI0NVoXDTIxMDIxMjE0 MzI0NVowITELMAkGA1UEBhMCVUsxEjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkq hkiG9w0BAQEFAAOBjQAwgYkCgYEAtEosNE85QVarsB19CH6Nnuh04zIA5IVRGlzI PYTeEAgCqK7oExLAoW10TScEcJvLtyrGe6Wl2op7Bu6KvP4XtvymasbH9zhlGDtK c790BmUq8dwj5P6BWKgKKEZrRq8g8zFeSzOA1iv2ZGA6CKCfl1phqV4Y1IpZ0aqU tSXJLmUCAwEAATANBgkqhkiG9w0BAQUFAAOBgQA6UX8LG3vjaEbszGrO3IXcYyo8 rFXUpifwbrkH95eS039MKoO8/Lpfi1VQCgS5EJEQl+dTwjLbM4zx3tr36UAkp8WC eLw1uAZSMd9FFBop4rQcj5K5KQSzQ5kENDqtcffmgHHiEXhRJ42tbqHqNtp0F9Pz 2KzLuz/s0o9KBBnkKQ== -----END CERTIFICATE----- faye-1.4.0/examples/server.key000066400000000000000000000015671371104377200163140ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIICXAIBAAKBgQC0Siw0TzlBVquwHX0Ifo2e6HTjMgDkhVEaXMg9hN4QCAKorugT EsChbXRNJwRwm8u3KsZ7paXainsG7oq8/he2/KZqxsf3OGUYO0pzv3QGZSrx3CPk /oFYqAooRmtGryDzMV5LM4DWK/ZkYDoIoJ+XWmGpXhjUilnRqpS1JckuZQIDAQAB AoGBAJE3CSHdCf3xxHonVlVoMcPj8nopBYULSMcWIodCpOZ+vT2HHBB400Vy9vFJ 0w6svko7nwFq7Ar57ZwouQD/HLN/2K7GQvdHIYD5fuqXSdZSk7yD5U7y9g4z3tv3 BIVALk64Zbg1Jx7Vfg0PpHwd+A9bQ/737aI6c7NtHpdn/pCFAkEA8AG4Ufyo3Fyv 0vVNXesMqQVoH3Gtlcti0wLT+EdhuAo/eavhz6H2YTsvqNhlED+UBgA7yF9cFaIm PfvqdQholwJBAMBNvUVysK0z9lkTeGXUbcB2amowfj38Ls54uG/pOL/rvnm/9XCU fCq2OjteHAmYxaZaXaTzFrwHCQOhW8y4pGMCQFhBGak0Ulet0Y2uXEOz4hTpAw/E +oLBplZXjOCYRwuo4Qx5svBp6zJ31B+vawJdjq3iOhmFT1Q5KvzR/LYKYIMCQHGF ctYsfl/A7QxokgqK/L9BKFDHj77HwFniqf5mjzm80RTOXPW4shQEvxuM0sBXj1ZE wkM9vY4CGTArcSXJWkECQHydmu3nu2ls7dpy/irjohO61qG9GMkHqMsSYOoklsgx +JjHyBZ0b5tzc0HWKEp0jlXgKK2zRScW71jgkU6zRcM= -----END RSA PRIVATE KEY----- faye-1.4.0/faye.gemspec000066400000000000000000000043171371104377200147430ustar00rootroot00000000000000Gem::Specification.new do |s| s.name = 'faye' s.version = '1.4.0' s.summary = 'Simple pub/sub messaging for the web' s.author = 'James Coglan' s.email = 'jcoglan@gmail.com' s.homepage = 'https://faye.jcoglan.com' s.license = 'Apache-2.0' s.extra_rdoc_files = %w[README.md] s.rdoc_options = %w[--main README.md --markup markdown] s.require_paths = %w[lib] # It is important that the JavaScript files listed here are not removed: they # contain the browser client and the gem should fail to build without them. # You should generate them by running `make` in the project root. client_suffix = %w[.js .js.map -min.js -min.js.map] client_files = client_suffix.map { |ext| "build/client/faye-browser#{ext}" } s.files = %w[CHANGELOG.md LICENSE.md README.md] + Dir.glob('lib/**/*.rb') + client_files s.add_dependency 'cookiejar', '>= 0.3.0' s.add_dependency 'em-http-request', '>= 1.1.6' s.add_dependency 'eventmachine', '>= 0.12.0' s.add_dependency 'faye-websocket', '>= 0.11.0' s.add_dependency 'multi_json', '>= 1.0.0' s.add_dependency 'rack', '>= 1.0.0' s.add_dependency 'websocket-driver', '>= 0.5.1' s.add_development_dependency 'compass', '~> 0.11.0' s.add_development_dependency 'haml', '~> 3.1.0' s.add_development_dependency 'permessage_deflate', '>= 0.1.0' s.add_development_dependency 'puma', '>= 2.0.0' s.add_development_dependency 'rack-proxy', '~> 0.4.0' s.add_development_dependency 'rack-test' s.add_development_dependency 'rake' s.add_development_dependency 'RedCloth', '~> 3.0.0' s.add_development_dependency 'rspec', '~> 2.99.0' s.add_development_dependency 'rspec-eventmachine', '>= 0.2.0' s.add_development_dependency 'sass', '~> 3.2.0' s.add_development_dependency 'sinatra' s.add_development_dependency 'staticmatic' jruby = RUBY_PLATFORM =~ /java/ rbx = defined?(RUBY_ENGINE) && RUBY_ENGINE =~ /rbx/ unless jruby s.add_development_dependency 'rainbows', '~> 4.4.0' s.add_development_dependency 'thin', '>= 1.2.0' end unless rbx or RUBY_VERSION < '1.9' s.add_development_dependency 'goliath' end unless jruby or rbx s.add_development_dependency 'passenger', '>= 4.0.0' end end faye-1.4.0/lib/000077500000000000000000000000001371104377200132135ustar00rootroot00000000000000faye-1.4.0/lib/faye.rb000066400000000000000000000064511371104377200144720ustar00rootroot00000000000000require 'cgi' require 'cookiejar' require 'digest/sha1' require 'em-http' require 'em-http/version' require 'eventmachine' require 'faye/websocket' require 'forwardable' require 'multi_json' require 'rack' require 'securerandom' require 'set' require 'time' require 'uri' module Faye VERSION = '1.4.0' ROOT = File.expand_path(File.dirname(__FILE__)) autoload :Deferrable, File.join(ROOT, 'faye', 'mixins', 'deferrable') autoload :Logging, File.join(ROOT, 'faye', 'mixins', 'logging') autoload :Publisher, File.join(ROOT, 'faye', 'mixins', 'publisher') autoload :Timeouts, File.join(ROOT, 'faye', 'mixins', 'timeouts') autoload :Namespace, File.join(ROOT, 'faye', 'util', 'namespace') autoload :Engine, File.join(ROOT, 'faye', 'engines', 'proxy') autoload :Channel, File.join(ROOT, 'faye', 'protocol', 'channel') autoload :Client, File.join(ROOT, 'faye', 'protocol', 'client') autoload :Dispatcher, File.join(ROOT, 'faye', 'protocol', 'dispatcher') autoload :Scheduler, File.join(ROOT, 'faye', 'protocol', 'scheduler') autoload :Extensible, File.join(ROOT, 'faye', 'protocol', 'extensible') autoload :Grammar, File.join(ROOT, 'faye', 'protocol', 'grammar') autoload :Publication, File.join(ROOT, 'faye', 'protocol', 'publication') autoload :Server, File.join(ROOT, 'faye', 'protocol', 'server') autoload :Subscription, File.join(ROOT, 'faye', 'protocol', 'subscription') autoload :Error, File.join(ROOT, 'faye', 'error') autoload :Transport, File.join(ROOT, 'faye', 'transport', 'transport') autoload :RackAdapter, File.join(ROOT, 'faye', 'adapters', 'rack_adapter') autoload :StaticServer, File.join(ROOT, 'faye', 'adapters', 'static_server') BAYEUX_VERSION = '1.0' JSONP_CALLBACK = 'jsonpcallback' CONNECTION_TYPES = %w[long-polling cross-origin-long-polling callback-polling websocket eventsource in-process] MANDATORY_CONNECTION_TYPES = %w[long-polling callback-polling in-process] class << self attr_accessor :logger end def self.ensure_reactor_running! Engine.ensure_reactor_running! end def self.random(*args) Engine.random(*args) end def self.client_id_from_messages(messages) first = [messages].flatten.find { |m| m['channel'] == '/meta/connect' } first && first['clientId'] end def self.copy_object(object) case object when Hash clone = {} object.each { |k,v| clone[k] = copy_object(v) } clone when Array clone = [] object.each { |v| clone << copy_object(v) } clone else object end end def self.to_json(value) case value when Hash, Array then MultiJson.dump(value) when String, NilClass then value.inspect else value.to_s end end def self.async_each(list, iterator, callback) n = list.size i = -1 calls = 0 looping = false loop, resume = nil, nil iterate = lambda do calls -= 1 i += 1 if i == n callback.call if callback else iterator.call(list[i], resume) end end loop = lambda do unless looping looping = true iterate.call while calls > 0 looping = false end end resume = lambda do calls += 1 loop.call end resume.call end end faye-1.4.0/lib/faye/000077500000000000000000000000001371104377200141375ustar00rootroot00000000000000faye-1.4.0/lib/faye/adapters/000077500000000000000000000000001371104377200157425ustar00rootroot00000000000000faye-1.4.0/lib/faye/adapters/rack_adapter.rb000066400000000000000000000207371371104377200207200ustar00rootroot00000000000000module Faye class RackAdapter include Logging extend Forwardable def_delegators '@server.engine', *Faye::Publisher.instance_methods ASYNC_RESPONSE = [-1, {}, []].freeze DEFAULT_ENDPOINT = '/bayeux' SCRIPT_PATH = 'faye-browser-min.js' TYPE_JSON = { 'Content-Type' => 'application/json; charset=utf-8' } TYPE_SCRIPT = { 'Content-Type' => 'text/javascript; charset=utf-8' } TYPE_TEXT = { 'Content-Type' => 'text/plain; charset=utf-8' } VALID_JSONP_CALLBACK = /^[a-z_\$][a-z0-9_\$]*(\.[a-z_\$][a-z0-9_\$]*)*$/i # This header is passed by Rack::Proxy during testing. Rack::Proxy seems to # set content-length for you, and setting it in here really slows the tests # down. Better suggestions welcome. HTTP_X_NO_CONTENT_LENGTH = 'HTTP_X_NO_CONTENT_LENGTH' def initialize(app = nil, options = nil, &block) @app = app if app.respond_to?(:call) @options = [app, options].grep(Hash).first || {} ::WebSocket::Driver.validate_options(@options, [:engine, :mount, :ping, :timeout, :extensions, :websocket_extensions]) @endpoint = @options[:mount] || DEFAULT_ENDPOINT @extensions = [] @endpoint_re = Regexp.new('^' + @endpoint.gsub(/\/$/, '') + '(/[^/]*)*(\\.[^\\.]+)?$') @server = Server.new(@options) @static = StaticServer.new(File.join(ROOT, '..', 'build', 'client'), /\.(?:js|map)$/) @static.map(File.basename(@endpoint) + '.js', SCRIPT_PATH) @static.map('client.js', SCRIPT_PATH) if extensions = @options[:extensions] [*extensions].each { |extension| add_extension(extension) } end if websocket_extensions = @options[:websocket_extensions] [*websocket_extensions].each { |ext| add_websocket_extension(ext) } end block.call(self) if block end def listen(*args) raise 'The listen() method is deprecated - see https://github.com/faye/faye-websocket-ruby#running-your-socket-application for information on running your Faye server' end def add_extension(extension) @server.add_extension(extension) end def remove_extension(extension) @server.remove_extension(extension) end def add_websocket_extension(extension) @extensions << extension end def close @server.close end def get_client @client ||= Client.new(@server) end def call(env) Faye.ensure_reactor_running! request = Rack::Request.new(env) unless request.path_info =~ @endpoint_re env['faye.client'] = get_client return @app ? @app.call(env) : [404, TYPE_TEXT, ["Sure you're not looking for #{ @endpoint } ?"]] end return @static.call(env) if @static =~ request.path_info # http://groups.google.com/group/faye-users/browse_thread/thread/4a01bb7d25d3636a if env['REQUEST_METHOD'] == 'OPTIONS' or env['HTTP_ACCESS_CONTROL_REQUEST_METHOD'] == 'POST' return handle_options(request) end return handle_websocket(request) if Faye::WebSocket.websocket?(env) return handle_eventsource(request) if Faye::EventSource.eventsource?(env) handle_request(request) end private def handle_request(request) unless json_msg = message_from_request(request) error 'Received request with no message: ?', format_request(request) return [400, TYPE_TEXT, ['Bad request']] end unless json_msg.force_encoding('UTF-8').valid_encoding? error 'Received request with invalid encoding: ?', format_request(request) return [400, TYPE_TEXT, ['Bad request']] end debug("Received message via HTTP #{ request.request_method }: ?", json_msg) message = parse_json(json_msg) jsonp = request.params['jsonp'] || JSONP_CALLBACK headers = request.get? ? TYPE_SCRIPT.dup : TYPE_JSON.dup origin = request.env['HTTP_ORIGIN'] callback = request.env['async.callback'] if jsonp !~ VALID_JSONP_CALLBACK error 'Invalid JSON-P callback: ?', jsonp return [400, TYPE_TEXT, ['Bad request']] end headers['Cache-Control'] = 'no-cache, no-store' headers['X-Content-Type-Options'] = 'nosniff' if origin headers['Access-Control-Allow-Credentials'] = 'true' headers['Access-Control-Allow-Origin'] = origin end request.env['rack.hijack'].call if request.env['rack.hijack'] hijack = request.env['rack.hijack_io'] EventMachine.next_tick do @server.process(message, request) do |replies| response = Faye.to_json(replies) if request.get? response = "/**/#{ jsonp }(#{ jsonp_escape(response) });" headers['Content-Disposition'] = 'attachment; filename=f.txt' end headers['Content-Length'] = response.bytesize.to_s unless request.env[HTTP_X_NO_CONTENT_LENGTH] debug('HTTP response: ?', response) send_response([200, headers, [response]], hijack, callback) end end ASYNC_RESPONSE rescue => e error "#{ e.message }\nBacktrace:\n#{ e.backtrace * "\n" }" [400, TYPE_TEXT, ['Bad request']] end def message_from_request(request) message = request.params['message'] return message if message # Some clients do not send a content-type, e.g. # Internet Explorer when using cross-origin-long-polling # Some use application/xml when using CORS content_type = request.env['CONTENT_TYPE'] || '' if content_type.split(';').first == 'application/json' request.body.read else CGI.parse(request.body.read)['message'][0] end end def jsonp_escape(json) json.gsub(/\u2028/, '\u2028').gsub(/\u2029/, '\u2029') end def send_response(response, hijack, callback) return callback.call(response) if callback buffer = "HTTP/1.1 #{ response[0] } OK\r\n" response[1].each do |name, value| buffer << "#{ name }: #{ value }\r\n" end buffer << "\r\n" response[2].each do |chunk| buffer << chunk end hijack.write(buffer) hijack.flush hijack.close_write end def handle_websocket(request) options = { :extensions => @extensions, :ping => @options[:ping] } ws = Faye::WebSocket.new(request.env, [], options) client_id = nil ws.onmessage = lambda do |event| begin debug("Received message via WebSocket[#{ ws.version }]: ?", event.data) message = parse_json(event.data) cid = Faye.client_id_from_messages(message) @server.close_socket(client_id, false) if client_id and cid and cid != client_id @server.open_socket(cid, ws, request) client_id = cid if cid @server.process(message, request) do |replies| ws.send(Faye.to_json(replies)) if ws end rescue => e error "#{ e.message }\nBacktrace:\n#{ e.backtrace * "\n" }" end end ws.onclose = lambda do |event| @server.close_socket(client_id) ws = nil end ws.rack_response end def handle_eventsource(request) es = Faye::EventSource.new(request.env, :ping => @options[:ping]) client_id = es.url.split('/').pop debug('Opened EventSource connection for ?', client_id) @server.open_socket(client_id, es, request) es.onclose = lambda do |event| @server.close_socket(client_id) es = nil end es.rack_response end def handle_options(request) origin = request.env['HTTP_ORIGIN'] || request.env['HTTP_REFERER'] headers = { 'Access-Control-Allow-Credentials' => 'true', 'Access-Control-Allow-Headers' => 'Accept, Authorization, Content-Type, Pragma, X-Requested-With', 'Access-Control-Allow-Methods' => 'POST, GET', 'Access-Control-Allow-Origin' => origin || '*', 'Access-Control-Max-Age' => '86400' } [200, headers, []] end def parse_json(json) data = MultiJson.load(json) return data if Array === data or Hash === data raise ArgumentError, 'JSON messages must contain an object or array' end def format_request(request) request.body.rewind string = "curl -X #{ request.request_method.upcase }" string << " '#{ request.url }'" if request.post? string << " -H 'Content-Type: #{ request.env['CONTENT_TYPE'] }'" string << " -d '#{ request.body.read }'" end string end end end faye-1.4.0/lib/faye/adapters/static_server.rb000066400000000000000000000026651371104377200211550ustar00rootroot00000000000000module Faye class StaticServer def initialize(directory, path_regex) @directory = directory @path_regex = path_regex @path_map = {} @index = {} end def map(request_path, filename) @path_map[request_path] = filename end def =~(pathname) @path_regex =~ pathname end def call(env) filename = File.basename(env['PATH_INFO']) filename = @path_map[filename] || filename cache = @index[filename] ||= {} fullpath = File.join(@directory, filename) begin cache[:content] ||= File.read(fullpath) cache[:digest] ||= Digest::SHA1.hexdigest(cache[:content]) cache[:mtime] ||= File.mtime(fullpath) rescue return [404, {}, []] end type = /\.js$/ =~ fullpath ? RackAdapter::TYPE_SCRIPT : RackAdapter::TYPE_JSON ims = env['HTTP_IF_MODIFIED_SINCE'] no_content_length = env[RackAdapter::HTTP_X_NO_CONTENT_LENGTH] headers = { 'ETag' => cache[:digest], 'Last-Modified' => cache[:mtime].httpdate } if env['HTTP_IF_NONE_MATCH'] == cache[:digest] [304, headers, ['']] elsif ims and cache[:mtime] <= Time.httpdate(ims) [304, headers, ['']] else headers['Content-Length'] = cache[:content].bytesize.to_s unless no_content_length headers.update(type) [200, headers, [cache[:content]]] end end end end faye-1.4.0/lib/faye/engines/000077500000000000000000000000001371104377200155675ustar00rootroot00000000000000faye-1.4.0/lib/faye/engines/connection.rb000066400000000000000000000023761371104377200202630ustar00rootroot00000000000000module Faye module Engine class Connection include Deferrable include Timeouts attr_accessor :socket def initialize(engine, id, options = {}) @engine = engine @id = id @options = options @inbox = Set.new end def deliver(message) message.delete('clientId') return @socket.send(message) if @socket return unless @inbox.add?(message) begin_delivery_timeout end def connect(options, &block) options = options || {} timeout = options['timeout'] ? options['timeout'] / 1000.0 : @engine.timeout set_deferred_status(:unknown) callback(&block) begin_delivery_timeout begin_connection_timeout(timeout) end def flush remove_timeout(:connection) remove_timeout(:delivery) set_deferred_status(:succeeded, @inbox.entries) @inbox = [] @engine.close_connection(@id) unless @socket end private def begin_delivery_timeout return if @inbox.empty? add_timeout(:delivery, MAX_DELAY) { flush } end def begin_connection_timeout(timeout) add_timeout(:connection, timeout) { flush } end end end end faye-1.4.0/lib/faye/engines/memory.rb000066400000000000000000000066731371104377200174400ustar00rootroot00000000000000module Faye module Engine class Memory include Timeouts def self.create(server, options) new(server, options) end def initialize(server, options) @server = server @options = options reset end def disconnect reset remove_all_timeouts end def reset @namespace = Namespace.new @clients = {} @channels = {} @messages = {} end def create_client(&callback) client_id = @namespace.generate @server.debug('Created new client ?', client_id) ping(client_id) @server.trigger(:handshake, client_id) callback.call(client_id) end def destroy_client(client_id, &callback) return unless @namespace.exists?(client_id) if @clients.has_key?(client_id) @clients[client_id].each { |channel| unsubscribe(client_id, channel) } end remove_timeout(client_id) @namespace.release(client_id) @messages.delete(client_id) @server.debug('Destroyed client ?', client_id) @server.trigger(:disconnect, client_id) @server.trigger(:close, client_id) callback.call if callback end def client_exists(client_id, &callback) callback.call(@namespace.exists?(client_id)) end def ping(client_id) timeout = @server.timeout return unless Numeric === timeout @server.debug('Ping ?, ?', client_id, timeout) remove_timeout(client_id) add_timeout(client_id, 2 * timeout) { destroy_client(client_id) } end def subscribe(client_id, channel, &callback) @clients[client_id] ||= Set.new should_trigger = @clients[client_id].add?(channel) @channels[channel] ||= Set.new @channels[channel].add(client_id) @server.debug('Subscribed client ? to channel ?', client_id, channel) @server.trigger(:subscribe, client_id, channel) if should_trigger callback.call(true) if callback end def unsubscribe(client_id, channel, &callback) if @clients.has_key?(client_id) should_trigger = @clients[client_id].delete?(channel) @clients.delete(client_id) if @clients[client_id].empty? end if @channels.has_key?(channel) @channels[channel].delete(client_id) @channels.delete(channel) if @channels[channel].empty? end @server.debug('Unsubscribed client ? from channel ?', client_id, channel) @server.trigger(:unsubscribe, client_id, channel) if should_trigger callback.call(true) if callback end def publish(message, channels) @server.debug('Publishing message ?', message) clients = Set.new channels.each do |channel| next unless subs = @channels[channel] subs.each(&clients.method(:add)) end clients.each do |client_id| @server.debug('Queueing for client ?: ?', client_id, message) @messages[client_id] ||= [] @messages[client_id] << Faye.copy_object(message) empty_queue(client_id) end @server.trigger(:publish, message['clientId'], message['channel'], message['data']) end def empty_queue(client_id) return unless @server.has_connection?(client_id) @server.deliver(client_id, @messages[client_id]) @messages.delete(client_id) end end end end faye-1.4.0/lib/faye/engines/proxy.rb000066400000000000000000000066741371104377200173120ustar00rootroot00000000000000module Faye module Engine METHODS = %w[create_client client_exists destroy_client ping subscribe unsubscribe] MAX_DELAY = 0.0 INTERVAL = 0.0 TIMEOUT = 60.0 ID_LENGTH = 160 autoload :Connection, File.expand_path('../connection', __FILE__) autoload :Memory, File.expand_path('../memory', __FILE__) def self.ensure_reactor_running! Thread.new { EventMachine.run } unless EventMachine.reactor_running? Thread.pass until EventMachine.reactor_running? end def self.get(options) Proxy.new(options) end def self.random(bitlength = ID_LENGTH) limit = 2 ** bitlength max_size = (bitlength * Math.log(2) / Math.log(36)).ceil string = SecureRandom.random_number(limit).to_s(36) string = '0' + string while string.size < max_size string end class Proxy include Publisher include Logging attr_reader :interval, :timeout extend Forwardable def_delegators :@engine, *METHODS def initialize(options) super() @options = options @connections = {} @interval = @options[:interval] || INTERVAL @timeout = @options[:timeout] || TIMEOUT engine_class = @options[:type] || Memory @engine = engine_class.create(self, @options) bind :close do |client_id| EventMachine.next_tick { flush_connection(client_id) } end debug('Created new engine: ?', @options) end def connect(client_id, options = {}, &callback) debug('Accepting connection from ?', client_id) @engine.ping(client_id) conn = connection(client_id, true) conn.connect(options, &callback) @engine.empty_queue(client_id) end def has_connection?(client_id) @connections.has_key?(client_id) end def connection(client_id, create) conn = @connections[client_id] return conn if conn or not create @connections[client_id] = Connection.new(self, client_id) trigger('connection:open', client_id) @connections[client_id] end def close_connection(client_id) debug('Closing connection for ?', client_id) return unless conn = @connections[client_id] conn.socket.close if conn.socket trigger('connection:close', client_id) @connections.delete(client_id) end def open_socket(client_id, socket) conn = connection(client_id, true) conn.socket = socket end def deliver(client_id, messages) return if !messages || messages.empty? return false unless conn = connection(client_id, false) messages.each(&conn.method(:deliver)) true end def generate_id Engine.random end def flush_connection(client_id, close = true) return unless client_id debug('Flushing connection for ?', client_id) return unless conn = connection(client_id, false) conn.socket = nil unless close conn.flush close_connection(client_id) end def close @connections.keys.each { |client_id| flush_connection(client_id) } @engine.disconnect end def disconnect @engine.disconnect if @engine.respond_to?(:disconnect) end def publish(message) channels = Channel.expand(message['channel']) @engine.publish(message, channels) end end end end faye-1.4.0/lib/faye/error.rb000066400000000000000000000025701371104377200156210ustar00rootroot00000000000000module Faye class Error def self.method_missing(type, *args) code = const_get(type.to_s.upcase) new(code[0], args, code[1]).to_s end def self.parse(message) message ||= '' return new(nil, [], message) unless Grammar::ERROR =~ message parts = message.split(':') code = parts[0].to_i params = parts[1].split(',') message = parts[2] new(code, params, message) end attr_reader :code, :params, :message def initialize(code, params, message) @code = code @params = params @message = message end def to_s "#{ @code }:#{ @params * ',' }:#{ @message }" end # http://code.google.com/p/cometd/wiki/BayeuxCodes VERSION_MISMATCH = [300, 'Version mismatch'] CONNTYPE_MISMATCH = [301, 'Connection types not supported'] EXT_MISMATCH = [302, 'Extension mismatch'] BAD_REQUEST = [400, 'Bad request'] CLIENT_UNKNOWN = [401, 'Unknown client'] PARAMETER_MISSING = [402, 'Missing required parameter'] CHANNEL_FORBIDDEN = [403, 'Forbidden channel'] CHANNEL_UNKNOWN = [404, 'Unknown channel'] CHANNEL_INVALID = [405, 'Invalid channel'] EXT_UNKNOWN = [406, 'Unknown extension'] PUBLISH_FAILED = [407, 'Failed to publish'] SERVER_ERROR = [500, 'Internal server error'] end end faye-1.4.0/lib/faye/mixins/000077500000000000000000000000001371104377200154465ustar00rootroot00000000000000faye-1.4.0/lib/faye/mixins/deferrable.rb000066400000000000000000000004001371104377200200600ustar00rootroot00000000000000module Faye module Deferrable include EventMachine::Deferrable def set_deferred_status(status, *args) if status == :unknown @deferred_status = @deferred_args = @callbacks = @errbacks = nil end super end end end faye-1.4.0/lib/faye/mixins/logging.rb000066400000000000000000000012731371104377200174240ustar00rootroot00000000000000module Faye module Logging LOG_LEVELS = { :fatal => 4, :error => 3, :warn => 2, :info => 1, :debug => 0 } LOG_LEVELS.each do |level, value| define_method(level) { |*args| write_log(args, level) } end private def write_log(message_args, level) return unless Faye.logger message = message_args.shift.gsub(/\?/) do Faye.to_json(message_args.shift) end banner = "[#{ self.class.name }] " if Faye.logger.respond_to?(level) Faye.logger.__send__(level, banner + message) elsif Faye.logger.respond_to?(:call) Faye.logger.call(banner + message) end end end end faye-1.4.0/lib/faye/mixins/publisher.rb000066400000000000000000000004631371104377200177730ustar00rootroot00000000000000module Faye module Publisher include ::WebSocket::Driver::EventEmitter alias :bind :add_listener alias :trigger :emit def unbind(event, &listener) if listener remove_listener(event, &listener) else remove_all_listeners(event) end end end end faye-1.4.0/lib/faye/mixins/timeouts.rb000066400000000000000000000011511371104377200176420ustar00rootroot00000000000000module Faye module Timeouts def add_timeout(name, delay, &block) Engine.ensure_reactor_running! @timeouts ||= {} return if @timeouts.has_key?(name) @timeouts[name] = EventMachine.add_timer(delay) do @timeouts.delete(name) block.call end end def remove_timeout(name) @timeouts ||= {} timeout = @timeouts[name] return if timeout.nil? EventMachine.cancel_timer(timeout) @timeouts.delete(name) end def remove_all_timeouts @timeouts ||= {} @timeouts.keys.each { |name| remove_timeout(name) } end end end faye-1.4.0/lib/faye/protocol/000077500000000000000000000000001371104377200160005ustar00rootroot00000000000000faye-1.4.0/lib/faye/protocol/channel.rb000066400000000000000000000047441371104377200177460ustar00rootroot00000000000000module Faye class Channel include Publisher attr_reader :name def initialize(name) super() @name = name end def <<(message) trigger(:message, message) end def unused? listener_count(:message).zero? end HANDSHAKE = '/meta/handshake' CONNECT = '/meta/connect' SUBSCRIBE = '/meta/subscribe' UNSUBSCRIBE = '/meta/unsubscribe' DISCONNECT = '/meta/disconnect' META = 'meta' SERVICE = 'service' class << self def expand(name) segments = parse(name) channels = ['/**', name] copy = segments.dup copy[copy.size - 1] = '*' channels << unparse(copy) 1.upto(segments.size - 1) do |i| copy = segments[0...i] copy << '**' channels << unparse(copy) end channels end def valid?(name) Grammar::CHANNEL_NAME =~ name or Grammar::CHANNEL_PATTERN =~ name end def parse(name) return nil unless valid?(name) name.split('/')[1..-1] end def unparse(segments) '/' + segments.join('/') end def meta?(name) segments = parse(name) segments ? (segments.first == META) : nil end def service?(name) segments = parse(name) segments ? (segments.first == SERVICE) : nil end def subscribable?(name) return nil unless valid?(name) not meta?(name) and not service?(name) end end class Set def initialize @channels = {} end def keys @channels.keys end def remove(name) @channels.delete(name) end def has_subscription?(name) @channels.has_key?(name) end def subscribe(names, subscription) names.each do |name| channel = @channels[name] ||= Channel.new(name) channel.bind(:message, &subscription) end end def unsubscribe(name, subscription) channel = @channels[name] return false unless channel channel.unbind(:message, &subscription) if channel.unused? remove(name) true else false end end def distribute_message(message) channels = Channel.expand(message['channel']) channels.each do |name| channel = @channels[name] channel.trigger(:message, message) if channel end end end end end faye-1.4.0/lib/faye/protocol/client.rb000066400000000000000000000274361371104377200176170ustar00rootroot00000000000000module Faye class Client include Deferrable include Publisher include Logging include Extensible UNCONNECTED = 1 CONNECTING = 2 CONNECTED = 3 DISCONNECTED = 4 HANDSHAKE = 'handshake' RETRY = 'retry' NONE = 'none' CONNECTION_TIMEOUT = 60.0 extend Forwardable def_delegators :@dispatcher, :add_websocket_extension, :disable, :set_header def initialize(endpoint = nil, options = {}) ::WebSocket::Driver.validate_options(options, [:interval, :timeout, :endpoints, :proxy, :retry, :scheduler, :websocket_extensions, :tls]) super() info('New client created for ?', endpoint) @channels = Channel::Set.new @dispatcher = Dispatcher.new(self, endpoint || RackAdapter::DEFAULT_ENDPOINT, options) @message_id = 0 @state = UNCONNECTED @response_callbacks = {} @advice = { 'reconnect' => RETRY, 'interval' => 1000.0 * (options[:interval] || Engine::INTERVAL), 'timeout' => 1000.0 * (options[:timeout] || CONNECTION_TIMEOUT) } @dispatcher.timeout = @advice['timeout'] / 1000.0 @dispatcher.bind(:message, &method(:receive_message)) end # Request # MUST include: * channel # * version # * supportedConnectionTypes # MAY include: * minimumVersion # * ext # * id # # Success Response Failed Response # MUST include: * channel MUST include: * channel # * version * successful # * supportedConnectionTypes * error # * clientId MAY include: * supportedConnectionTypes # * successful * advice # MAY include: * minimumVersion * version # * advice * minimumVersion # * ext * ext # * id * id # * authSuccessful def handshake(&block) return if @advice['reconnect'] == NONE return if @state != UNCONNECTED @state = CONNECTING info('Initiating handshake with ?', @dispatcher.endpoint.to_s) @dispatcher.select_transport(MANDATORY_CONNECTION_TYPES) send_message({ 'channel' => Channel::HANDSHAKE, 'version' => BAYEUX_VERSION, 'supportedConnectionTypes' => @dispatcher.connection_types }, {}) do |response| if response['successful'] @state = CONNECTED @dispatcher.client_id = response['clientId'] @dispatcher.select_transport(response['supportedConnectionTypes']) info('Handshake successful: ?', @dispatcher.client_id) subscribe(@channels.keys, true) block.call if block_given? else info('Handshake unsuccessful') EventMachine.add_timer(@dispatcher.retry) { handshake(&block) } @state = UNCONNECTED end end end # Request Response # MUST include: * channel MUST include: * channel # * clientId * successful # * connectionType * clientId # MAY include: * ext MAY include: * error # * id * advice # * ext # * id # * timestamp def connect(&block) return if @advice['reconnect'] == NONE or @state == DISCONNECTED return handshake { connect(&block) } if @state == UNCONNECTED callback(&block) return unless @state == CONNECTED info('Calling deferred actions for ?', @dispatcher.client_id) set_deferred_status(:succeeded) set_deferred_status(:unknown) return unless @connect_request.nil? @connect_request = true info('Initiating connection for ?', @dispatcher.client_id) send_message({ 'channel' => Channel::CONNECT, 'clientId' => @dispatcher.client_id, 'connectionType' => @dispatcher.connection_type }, {}) do cycle_connection end end # Request Response # MUST include: * channel MUST include: * channel # * clientId * successful # MAY include: * ext * clientId # * id MAY include: * error # * ext # * id def disconnect return unless @state == CONNECTED @state = DISCONNECTED info('Disconnecting ?', @dispatcher.client_id) promise = Publication.new send_message({ 'channel' => Channel::DISCONNECT, 'clientId' => @dispatcher.client_id }, {}) do |response| if response['successful'] @dispatcher.close promise.set_deferred_status(:succeeded) else promise.set_deferred_status(:failed, Error.parse(response['error'])) end end info('Clearing channel listeners for ?', @dispatcher.client_id) @channels = Channel::Set.new promise end # Request Response # MUST include: * channel MUST include: * channel # * clientId * successful # * subscription * clientId # MAY include: * ext * subscription # * id MAY include: * error # * advice # * ext # * id # * timestamp def subscribe(channel, force = false, &block) if Array === channel return channel.map { |c| subscribe(c, force, &block) } end subscription = Subscription.new(self, channel, block) has_subscribe = @channels.has_subscription?(channel) if has_subscribe and not force @channels.subscribe([channel], subscription) subscription.set_deferred_status(:succeeded) return subscription end connect { info('Client ? attempting to subscribe to ?', @dispatcher.client_id, channel) @channels.subscribe([channel], subscription) unless force send_message({ 'channel' => Channel::SUBSCRIBE, 'clientId' => @dispatcher.client_id, 'subscription' => channel }, {}) do |response| unless response['successful'] subscription.set_deferred_status(:failed, Error.parse(response['error'])) next @channels.unsubscribe(channel, subscription) end channels = [response['subscription']].flatten info('Subscription acknowledged for ? to ?', @dispatcher.client_id, channels) subscription.set_deferred_status(:succeeded) end } subscription end # Request Response # MUST include: * channel MUST include: * channel # * clientId * successful # * subscription * clientId # MAY include: * ext * subscription # * id MAY include: * error # * advice # * ext # * id # * timestamp def unsubscribe(channel, subscription = nil, &block) subscription ||= block if Array === channel return channel.map { |c| unsubscribe(c, subscription) } end dead = @channels.unsubscribe(channel, subscription) return unless dead connect { info('Client ? attempting to unsubscribe from ?', @dispatcher.client_id, channel) send_message({ 'channel' => Channel::UNSUBSCRIBE, 'clientId' => @dispatcher.client_id, 'subscription' => channel }, {}) do |response| next unless response['successful'] channels = [response['subscription']].flatten info('Unsubscription acknowledged for ? from ?', @dispatcher.client_id, channels) end } end # Request Response # MUST include: * channel MUST include: * channel # * data * successful # MAY include: * clientId MAY include: * id # * id * error # * ext * ext def publish(channel, data, options = {}) ::WebSocket::Driver.validate_options(options, [:attempts, :deadline]) publication = Publication.new connect { info('Client ? queueing published message to ?: ?', @dispatcher.client_id, channel, data) send_message({ 'channel' => channel, 'data' => data, 'clientId' => @dispatcher.client_id }, options) do |response| if response['successful'] publication.set_deferred_status(:succeeded) else publication.set_deferred_status(:failed, Error.parse(response['error'])) end end } publication end private def send_message(message, options, &callback) message['id'] = generate_message_id timeout = [nil, 0].include?(@advice['timeout']) ? 1.2 * @dispatcher.retry : 1.2 * @advice['timeout'] / 1000.0 pipe_through_extensions(:outgoing, message, nil) do |message| next unless message @response_callbacks[message['id']] = callback if callback @dispatcher.send_message(message, timeout, options) end end def generate_message_id @message_id += 1 @message_id = 0 if @message_id >= 2**32 @message_id.to_s(36) end def receive_message(message) id = message['id'] if message.has_key?('successful') callback = @response_callbacks.delete(id) end pipe_through_extensions(:incoming, message, nil) do |message| next unless message handle_advice(message['advice']) if message['advice'] deliver_message(message) callback.call(message) if callback end end def handle_advice(advice) @advice.update(advice) @dispatcher.timeout = @advice['timeout'] / 1000.0 if @advice['reconnect'] == HANDSHAKE and @state != DISCONNECTED @state = UNCONNECTED @dispatcher.client_id = nil cycle_connection end end def deliver_message(message) return unless message.has_key?('channel') and message.has_key?('data') info('Client ? calling listeners for ? with ?', @dispatcher.client_id, message['channel'], message['data']) @channels.distribute_message(message) end def cycle_connection if @connect_request @connect_request = nil info('Closed connection for ?', @dispatcher.client_id) end EventMachine.add_timer(@advice['interval'] / 1000.0) { connect } end end end faye-1.4.0/lib/faye/protocol/dispatcher.rb000066400000000000000000000104421371104377200204540ustar00rootroot00000000000000module Faye class Dispatcher class Envelope < Struct.new(:message, :scheduler, :request, :timer) end MAX_REQUEST_SIZE = 2048 DEFAULT_RETRY = 5.0 UP = 1 DOWN = 2 include Publisher include Logging extend Forwardable def_delegators :@transport, :connection_type attr_accessor :client_id, :timeout attr_reader :endpoint, :tls, :headers, :cookies, :proxy, :retry attr_reader :max_request_size, :transports, :ws_extensions def initialize(client, endpoint, options) super() @client = client @endpoint = String === endpoint ? URI(endpoint) : endpoint @alternates = options[:endpoints] || {} @cookies = CookieJar::Jar.new @disabled = [] @envelopes = {} @headers = {} @retry = options[:retry] || DEFAULT_RETRY @scheduler = options[:scheduler] || Faye::Scheduler @state = 0 @transports = {} @ws_extensions = [] @proxy = options[:proxy] || {} @proxy = { :origin => @proxy } if String === @proxy [*options[:websocket_extensions]].each do |extension| add_websocket_extension(extension) end @tls = { :verify_peer => true }.merge(options[:tls] || {}) @alternates.each do |type, url| @alternates[type] = URI(url) end @max_request_size = MAX_REQUEST_SIZE end def endpoint_for(connection_type) @alternates[connection_type] || @endpoint end def add_websocket_extension(extension) @ws_extensions << extension end def disable(feature) @disabled << feature end def set_header(name, value) @headers[name.to_s] = value.to_s end def close transport = @transport @transport = nil transport.close if transport end def connection_types Transport.connection_types end def select_transport(transport_types) Transport.get(self, transport_types, @disabled) do |transport| debug('Selected ? transport for ?', transport.connection_type, transport.endpoint) next if transport == @transport @transport.close if @transport @transport = transport end end def send_message(message, timeout, options = {}) id = message['id'] attempts = options[:attempts] deadline = options[:deadline] && Time.now.to_f + options[:deadline] envelope = @envelopes[id] unless envelope scheduler = @scheduler.new(message, :timeout => timeout, :interval => @retry, :attempts => attempts, :deadline => deadline) envelope = @envelopes[id] = Envelope.new(message, scheduler, nil, nil) end send_envelope(envelope) end def send_envelope(envelope) return unless @transport return if envelope.request or envelope.timer message = envelope.message scheduler = envelope.scheduler unless scheduler.deliverable? scheduler.abort! @envelopes.delete(message['id']) return end envelope.timer = EventMachine.add_timer(scheduler.timeout) do handle_error(message) end scheduler.send! envelope.request = @transport.send_message(message) end private :send_envelope def handle_response(reply) if reply.has_key?('successful') and envelope = @envelopes.delete(reply['id']) envelope.scheduler.succeed! EventMachine.cancel_timer(envelope.timer) if envelope.timer end trigger(:message, reply) return if @state == UP @state = UP @client.trigger('transport:up') end def handle_error(message, immediate = false) return unless envelope = @envelopes[message['id']] return unless request = envelope.request request.callback do |req| req.close if req.respond_to?(:close) end scheduler = envelope.scheduler scheduler.fail! EventMachine.cancel_timer(envelope.timer) envelope.request = envelope.timer = nil if immediate send_envelope(envelope) else envelope.timer = EventMachine.add_timer(scheduler.interval) do envelope.timer = nil send_envelope(envelope) end end return if @state == DOWN @state = DOWN @client.trigger('transport:down') end end end faye-1.4.0/lib/faye/protocol/extensible.rb000066400000000000000000000022311371104377200204650ustar00rootroot00000000000000module Faye module Extensible include Logging def add_extension(extension) @extensions ||= [] @extensions << extension extension.added(self) if extension.respond_to?(:added) end def remove_extension(extension) return unless @extensions @extensions.delete_if do |ext| next false unless ext == extension extension.removed(self) if extension.respond_to?(:removed) true end end def pipe_through_extensions(stage, message, env, &callback) debug('Passing through ? extensions: ?', stage, message) return callback.call(message) unless @extensions extensions = @extensions.dup pipe = lambda do |message| next callback.call(message) unless message extension = extensions.shift next callback.call(message) unless extension next pipe.call(message) unless extension.respond_to?(stage) arity = extension.method(stage).arity if arity >= 3 extension.__send__(stage, message, env, pipe) else extension.__send__(stage, message, pipe) end end pipe.call(message) end end end faye-1.4.0/lib/faye/protocol/grammar.rb000066400000000000000000000037541371104377200177640ustar00rootroot00000000000000module Faye module Grammar def self.rule(&block) source = instance_eval(&block) %r{^#{string(source)}$} end def self.choice(*list) '(' + list.map(&method(:string)) * '|' + ')' end def self.repeat(*pattern) '(' + string(pattern) + ')*' end def self.oneormore(*pattern) '(' + string(pattern) + ')+' end def self.string(item) return item.map(&method(:string)) * '' if Array === item String === item ? item : item.source.gsub(/^\^/, '').gsub(/\$$/, '') end LOWALPHA = rule {[ '[a-z]' ]} UPALPHA = rule {[ '[A-Z]' ]} ALPHA = rule {[ choice(LOWALPHA, UPALPHA) ]} DIGIT = rule {[ '[0-9]' ]} ALPHANUM = rule {[ choice(ALPHA, DIGIT) ]} MARK = rule {[ choice(*%w[\\- \\_ \\! \\~ \\( \\) \\$ \\@]) ]} STRING = rule {[ repeat(choice(ALPHANUM, MARK, ' ', '\\/', '\\*', '\\.')) ]} TOKEN = rule {[ oneormore(choice(ALPHANUM, MARK)) ]} INTEGER = rule {[ oneormore(DIGIT) ]} CHANNEL_SEGMENT = rule {[ TOKEN ]} CHANNEL_SEGMENTS = rule {[ CHANNEL_SEGMENT, repeat('\\/', CHANNEL_SEGMENT) ]} CHANNEL_NAME = rule {[ '\\/', CHANNEL_SEGMENTS ]} WILD_CARD = rule {[ '\\*{1,2}' ]} CHANNEL_PATTERN = rule {[ repeat('\\/', CHANNEL_SEGMENT), '\\/', WILD_CARD ]} VERSION_ELEMENT = rule {[ ALPHANUM, repeat(choice(ALPHANUM, '\\-', '\\_')) ]} VERSION = rule {[ INTEGER, repeat('\\.', VERSION_ELEMENT) ]} CLIENT_ID = rule {[ oneormore(ALPHANUM) ]} ID = rule {[ oneormore(ALPHANUM) ]} ERROR_MESSAGE = rule {[ STRING ]} ERROR_ARGS = rule {[ STRING, repeat(',', STRING) ]} ERROR_CODE = rule {[ DIGIT, DIGIT, DIGIT ]} ERROR = rule {[ choice(string([ERROR_CODE, ':', ERROR_ARGS, ':', ERROR_MESSAGE]), string([ERROR_CODE, ':', ':', ERROR_MESSAGE])) ]} end end faye-1.4.0/lib/faye/protocol/publication.rb000066400000000000000000000001001371104377200206250ustar00rootroot00000000000000module Faye class Publication include Deferrable end endfaye-1.4.0/lib/faye/protocol/scheduler.rb000066400000000000000000000011711371104377200203030ustar00rootroot00000000000000module Faye class Scheduler def initialize(message, options) @message = message @options = options @attempts = 0 end def interval @options[:interval] end def timeout @options[:timeout] end def deliverable? attempts = @options[:attempts] deadline = @options[:deadline] now = Time.now.to_f return false if attempts and @attempts >= attempts return false if deadline and now > deadline true end def send! @attempts += 1 end def succeed! end def fail! end def abort! end end end faye-1.4.0/lib/faye/protocol/server.rb000066400000000000000000000231741371104377200176420ustar00rootroot00000000000000module Faye class Server autoload :Socket, File.join(ROOT, 'faye', 'protocol', 'socket') include Logging include Extensible attr_reader :engine def initialize(options = {}) @options = options || {} engine_opts = @options[:engine] || {} engine_opts[:timeout] = @options[:timeout] @engine = Faye::Engine.get(engine_opts) info('Created new server: ?', @options) end def close @engine.close end def open_socket(client_id, socket, env) return unless client_id and socket @engine.open_socket(client_id, Socket.new(self, socket, env)) end def close_socket(client_id, close = true) @engine.flush_connection(client_id, close) end def process(messages, env, &callback) local = env.nil? messages = [messages].flatten info('Processing messages: ? (local: ?)', messages, local) return callback.call([]) if messages.size == 0 processed, responses = 0, [] gather_replies = lambda do |replies| responses.concat(replies) processed += 1 responses.compact! info('Returning replies: ?', responses) callback.call(responses) if processed == messages.size end handle_reply = lambda do |replies| extended, expected = 0, replies.size gather_replies.call(replies) if expected == 0 replies.each_with_index do |reply, i| debug('Processing reply: ?', reply) pipe_through_extensions(:outgoing, reply, env) do |message| replies[i] = message extended += 1 gather_replies.call(replies) if extended == expected end end end messages.each do |message| pipe_through_extensions(:incoming, message, env) do |piped_message| handle(piped_message, local, &handle_reply) end end end def make_response(message) response = {} response['id'] = message['id'] if message['id'] response['clientId'] = message['clientId'] if message['clientId'] response['channel'] = message['channel'] if message['channel'] response['error'] = message['error'] if message['error'] response['successful'] = !response['error'] response end def handle(message, local = false, &callback) return callback.call([]) if !message info('Handling message: ? (local: ?)', message, local) channel_name = message['channel'] error = message['error'] return handle_meta(message, local, &callback) if Channel.meta?(channel_name) if Grammar::CHANNEL_NAME !~ channel_name error = Faye::Error.channel_invalid(channel_name) end if message['data'].nil? error = Faye::Error.parameter_missing('data') end @engine.publish(message) unless error response = make_response(message) response['error'] = error if error response['successful'] = !response['error'] callback.call([response]) end def handle_meta(message, local, &callback) method = method_for(message) unless method response = make_response(message) response['error'] = Faye::Error.channel_forbidden(message['channel']) response['successful'] = false return callback.call([response]) end __send__(method, message, local) do |responses| responses = [responses].flatten responses.each { |r| advize(r, message['connectionType']) } callback.call(responses) end end def method_for(message) case message['channel'] when Channel::HANDSHAKE then :handshake when Channel::CONNECT then :connect when Channel::SUBSCRIBE then :subscribe when Channel::UNSUBSCRIBE then :unsubscribe when Channel::DISCONNECT then :disconnect end end def advize(response, connection_type) return unless [Channel::HANDSHAKE, Channel::CONNECT].include?(response['channel']) if connection_type == 'eventsource' interval = (@engine.timeout * 1000).floor timeout = 0 else interval = (@engine.interval * 1000).floor timeout = (@engine.timeout * 1000).floor end advice = response['advice'] ||= {} if response['error'] advice['reconnect'] ||= 'handshake' else advice['reconnect'] ||= 'retry' advice['interval'] ||= interval advice['timeout'] ||= timeout end end # MUST contain * version # * supportedConnectionTypes # MAY contain * minimumVersion # * ext # * id def handshake(message, local = false, &callback) response = make_response(message) response['version'] = BAYEUX_VERSION response['error'] = Error.parameter_missing('version') if message['version'].nil? client_conns = message['supportedConnectionTypes'] response['supportedConnectionTypes'] = CONNECTION_TYPES if client_conns common_conns = client_conns.select { |c| CONNECTION_TYPES.include?(c) } response['error'] = Error.conntype_mismatch(*client_conns) if common_conns.empty? else response['error'] = Error.parameter_missing('supportedConnectionTypes') end response['successful'] = response['error'].nil? return callback.call(response) unless response['successful'] @engine.create_client do |client_id| response['clientId'] = client_id callback.call(response) end end # MUST contain * clientId # * connectionType # MAY contain * ext # * id def connect(message, local = false, &callback) response = make_response(message) client_id = message['clientId'] connection_type = message['connectionType'] @engine.client_exists(client_id) do |exists| response['error'] = Error.client_unknown(client_id) unless exists response['error'] = Error.parameter_missing('clientId') if client_id.nil? unless CONNECTION_TYPES.include?(connection_type) response['error'] = Error.conntype_mismatch(connection_type) end response['error'] = Error.parameter_missing('connectionType') if connection_type.nil? response['successful'] = response['error'].nil? if !response['successful'] response.delete('clientId') next callback.call(response) end if message['connectionType'] == 'eventsource' message['advice'] ||= {} message['advice']['timeout'] = 0 end @engine.connect(response['clientId'], message['advice']) do |events| callback.call([response] + events) end end end # MUST contain * clientId # MAY contain * ext # * id def disconnect(message, local = false, &callback) response = make_response(message) client_id = message['clientId'] @engine.client_exists(client_id) do |exists| response['error'] = Error.client_unknown(client_id) unless exists response['error'] = Error.parameter_missing('clientId') if client_id.nil? response['successful'] = response['error'].nil? response.delete('clientId') unless response['successful'] @engine.destroy_client(client_id) if response['successful'] callback.call(response) end end # MUST contain * clientId # * subscription # MAY contain * ext # * id def subscribe(message, local = false, &callback) response = make_response(message) client_id = message['clientId'] subscription = [message['subscription']].flatten @engine.client_exists(client_id) do |exists| response['error'] = Error.client_unknown(client_id) unless exists response['error'] = Error.parameter_missing('clientId') if client_id.nil? response['error'] = Error.parameter_missing('subscription') if message['subscription'].nil? response['subscription'] = message['subscription'] || [] subscription.each do |channel| next if response['error'] response['error'] = Error.channel_forbidden(channel) unless local or Channel.subscribable?(channel) response['error'] = Error.channel_invalid(channel) unless Channel.valid?(channel) next if response['error'] @engine.subscribe(client_id, channel) end response['successful'] = response['error'].nil? callback.call(response) end end # MUST contain * clientId # * subscription # MAY contain * ext # * id def unsubscribe(message, local = false, &callback) response = make_response(message) client_id = message['clientId'] subscription = [message['subscription']].flatten @engine.client_exists(client_id) do |exists| response['error'] = Error.client_unknown(client_id) unless exists response['error'] = Error.parameter_missing('clientId') if client_id.nil? response['error'] = Error.parameter_missing('subscription') if message['subscription'].nil? response['subscription'] = message['subscription'] || [] subscription.each do |channel| next if response['error'] response['error'] = Error.channel_forbidden(channel) unless local or Channel.subscribable?(channel) response['error'] = Error.channel_invalid(channel) unless Channel.valid?(channel) next if response['error'] @engine.unsubscribe(client_id, channel) end response['successful'] = response['error'].nil? callback.call(response) end end end end faye-1.4.0/lib/faye/protocol/socket.rb000066400000000000000000000007231371104377200176170ustar00rootroot00000000000000module Faye class Server class Socket def initialize(server, socket, env) @server = server @socket = socket @env = env end def send(message) @server.pipe_through_extensions(:outgoing, message, @env) do |piped_message| @socket.send(Faye.to_json([piped_message])) if @socket end end def close @socket.close if @socket @socket = nil end end end end faye-1.4.0/lib/faye/protocol/subscription.rb000066400000000000000000000013311371104377200210470ustar00rootroot00000000000000module Faye class Subscription include Deferrable def initialize(client, channels, callback) @client = client @channels = channels @callback = callback @cancelled = false end def with_channel(&callback) @with_channel = callback self end def call(*args) message = args.first @callback.call(message['data']) if @callback @with_channel.call(message['channel'], message['data']) if @with_channel end def to_proc @to_proc ||= lambda { |*a| call(*a) } end def cancel return if @cancelled @client.unsubscribe(@channels, self) @cancelled = true end def unsubscribe cancel end end end faye-1.4.0/lib/faye/transport/000077500000000000000000000000001371104377200161735ustar00rootroot00000000000000faye-1.4.0/lib/faye/transport/http.rb000066400000000000000000000033741371104377200175060ustar00rootroot00000000000000module Faye class Transport::Http < Transport def self.usable?(dispatcher, endpoint, &callback) callback.call(URI === endpoint) end def encode(messages) Faye.to_json(messages) end def request(messages) content = encode(messages) params = build_params(content) request = create_request(params) request.callback do handle_response(messages, request.response) store_cookies(request.response_header['SET_COOKIE']) end request.errback do handle_error(messages) end request end private def build_params(content) headers = { 'Content-Length' => content.bytesize, 'Content-Type' => 'application/json', 'Host' => @endpoint.host + (@endpoint.port ? ":#{ @endpoint.port }" : '') } params = { :head => headers.merge(@dispatcher.headers), :body => content } cookie = get_cookies params[:head]['Cookie'] = cookie unless cookie == '' params end def create_request(params) options = { :inactivity_timeout => 0, :tls => @dispatcher.tls } if @proxy[:origin] uri = URI(@proxy[:origin]) options[:proxy] = { :host => uri.host, :port => uri.port } if uri.user options[:proxy][:authorization] = [uri.user, uri.password] end end client = EventMachine::HttpRequest.new(@endpoint.to_s, options) client.post(params) end def handle_response(messages, response) replies = MultiJson.load(response) rescue nil if replies receive(replies) else handle_error(messages) end end end Transport.register 'long-polling', Transport::Http end faye-1.4.0/lib/faye/transport/local.rb000066400000000000000000000006741371104377200176210ustar00rootroot00000000000000module Faye class Transport::Local < Transport def self.usable?(dispatcher, endpoint, &callback) callback.call(Server === endpoint) end def batching? false end def request(messages) EventMachine.next_tick do @endpoint.process(messages, nil) do |replies| receive(Faye.copy_object(replies)) end end end end Transport.register 'in-process', Transport::Local end faye-1.4.0/lib/faye/transport/transport.rb000066400000000000000000000100641371104377200205550ustar00rootroot00000000000000module Faye class Transport include Logging include Publisher include Timeouts DEFAULT_PORTS = { 'http' => 80, 'https' => 433, 'ws' => 80, 'wss' => 443 } attr_reader :endpoint def initialize(dispatcher, endpoint) super() @dispatcher = dispatcher @endpoint = endpoint @outbox = [] @proxy = @dispatcher.proxy.dup @proxy[:origin] ||= @endpoint.respond_to?(:find_proxy) ? @endpoint.find_proxy : nil end def batching? true end def close end def encode(messages) '' end def connection_type self.class.connection_type end def send_message(message) debug('Client ? sending message to ? via ?: ?', @dispatcher.client_id, @endpoint, connection_type, message) unless batching? promise = EventMachine::DefaultDeferrable.new promise.succeed(request([message])) return promise end @outbox << message flush_large_batch if message['channel'] == Channel::HANDSHAKE return publish(0.01) end if message['channel'] == Channel::CONNECT @connection_message = message end publish(Engine::MAX_DELAY) end private def publish(delay) @promise ||= EventMachine::DefaultDeferrable.new add_timeout(:publish, delay) do flush @promise = nil end @promise end def flush remove_timeout(:publish) if @outbox.size > 1 and @connection_message @connection_message['advice'] = { 'timeout' => 0 } end @promise.succeed(request(@outbox)) @connection_message = nil @outbox = [] end def flush_large_batch string = encode(@outbox) return if string.size < @dispatcher.max_request_size last = @outbox.pop @promise ||= EventMachine::DefaultDeferrable.new flush @outbox.push(last) if last end def receive(replies) return unless replies replies = [replies].flatten debug('Client ? received from ? via ?: ?', @dispatcher.client_id, @endpoint, connection_type, replies) replies.each do |reply| @dispatcher.handle_response(reply) end end def handle_error(messages, immediate = false) debug('Client ? failed to send to ? via ?: ?', @dispatcher.client_id, @endpoint, connection_type, messages) messages.each do |message| @dispatcher.handle_error(message, immediate) end end def get_cookies @dispatcher.cookies.get_cookies(@endpoint.to_s) * ';' end def store_cookies(set_cookie) [*set_cookie].compact.each do |cookie| @dispatcher.cookies.set_cookie(@endpoint.to_s, cookie) end end @transports = [] class << self attr_accessor :connection_type def get(dispatcher, allowed, disabled, &callback) endpoint = dispatcher.endpoint select = lambda do |(conn_type, klass), resume| conn_endpoint = dispatcher.endpoint_for(conn_type) if disabled.include?(conn_type) next resume.call end unless allowed.include?(conn_type) klass.usable?(dispatcher, conn_endpoint) { |u| } next resume.call end klass.usable?(dispatcher, conn_endpoint) do |is_usable| next resume.call unless is_usable transport = klass.respond_to?(:create) ? klass.create(dispatcher, conn_endpoint) : klass.new(dispatcher, conn_endpoint) callback.call(transport) end end error = lambda do raise "Could not find a usable connection type for #{ endpoint }" end Faye.async_each(@transports, select, error) end def register(type, klass) @transports << [type, klass] klass.connection_type = type end def connection_types @transports.map { |t| t[0] } end end %w[local web_socket http].each do |type| require File.join(ROOT, 'faye', 'transport', type) end end end faye-1.4.0/lib/faye/transport/web_socket.rb000066400000000000000000000060361371104377200206520ustar00rootroot00000000000000module Faye class Transport::WebSocket < Transport UNCONNECTED = 1 CONNECTING = 2 CONNECTED = 3 PROTOCOLS = { 'http' => 'ws', 'https' => 'wss' } include Deferrable class Request include Deferrable def close callback { |socket| socket.close } end end def self.usable?(dispatcher, endpoint, &callback) create(dispatcher, endpoint).usable?(&callback) end def self.create(dispatcher, endpoint) sockets = dispatcher.transports[:websocket] ||= {} sockets[endpoint.to_s] ||= new(dispatcher, endpoint) end def batching? false end def usable?(&callback) self.callback { callback.call(true) } self.errback { callback.call(false) } connect end def request(messages) @pending ||= Set.new messages.each { |message| @pending.add(message) } promise = Request.new callback do |socket| next unless socket and socket.ready_state == 1 socket.send(Faye.to_json(messages)) promise.succeed(socket) end connect promise end def connect @state ||= UNCONNECTED return unless @state == UNCONNECTED @state = CONNECTING url = @endpoint.dup headers = @dispatcher.headers.dup extensions = @dispatcher.ws_extensions cookie = get_cookies url.scheme = PROTOCOLS[url.scheme] headers['Cookie'] = cookie unless cookie == '' options = { :extensions => extensions, :headers => headers, :proxy => @proxy, :tls => @dispatcher.tls } socket = Faye::WebSocket::Client.new(url.to_s, [], options) socket.onopen = lambda do |*args| store_cookies(socket.headers['Set-Cookie']) @socket = socket @state = CONNECTED @ever_connected = true set_deferred_status(:succeeded, socket) end closed = false socket.onclose = socket.onerror = lambda do |*args| next if closed closed = true was_connected = (@state == CONNECTED) socket.onopen = socket.onclose = socket.onerror = socket.onmessage = nil @socket = nil @state = UNCONNECTED pending = @pending ? @pending.to_a : [] @pending = nil if was_connected or @ever_connected set_deferred_status(:unknown) handle_error(pending, was_connected) else set_deferred_status(:failed) end end socket.onmessage = lambda do |event| replies = MultiJson.load(event.data) rescue nil next if replies.nil? replies = [replies].flatten replies.each do |reply| next unless reply.has_key?('successful') next unless message = @pending.find { |m| m['id'] == reply['id'] } @pending.delete(message) end receive(replies) end end def close return unless @socket @socket.close end end Transport.register 'websocket', Transport::WebSocket end faye-1.4.0/lib/faye/util/000077500000000000000000000000001371104377200151145ustar00rootroot00000000000000faye-1.4.0/lib/faye/util/namespace.rb000066400000000000000000000005141371104377200173750ustar00rootroot00000000000000module Faye class Namespace extend Forwardable def_delegator :@used, :delete, :release def_delegator :@used, :has_key?, :exists? def initialize @used = {} end def generate name = Engine.random name = Engine.random while @used.has_key?(name) @used[name] = name end end end faye-1.4.0/package.json000066400000000000000000000032511371104377200147340ustar00rootroot00000000000000{ "name": "faye", "description": "Simple pub/sub messaging for the web", "homepage": "https://faye.jcoglan.com", "author": "James Coglan (http://jcoglan.com/)", "keywords": [ "comet", "websocket", "pubsub", "bayeux", "ajax", "http" ], "license": "Apache-2.0", "version": "1.4.0", "engines": { "node": ">=0.8.0" }, "main": "src/faye_node", "browser": "src/faye_browser", "dependencies": { "asap": "*", "csprng": "*", "faye-websocket": ">=0.9.1", "safe-buffer": "*", "tough-cookie": "*", "tunnel-agent": "*" }, "devDependencies": { "jstest": "~1.0.0", "mime": "~1.2.0", "permessage-deflate": ">=0.1.0", "promises-aplus-tests": "~2.1.0", "webpack": "*", "webpack-cli": "*", "imports-loader": "<1.0.0" }, "scripts": { "start": "webpack --watch", "test": "find spec -name '*_spec.js' | xargs jstest", "promise": "promises-aplus-tests src/util/promise.js" }, "repository": { "type": "git", "url": "git://github.com/faye/faye.git" }, "bugs": "https://github.com/faye/faye/issues" } faye-1.4.0/site/000077500000000000000000000000001371104377200134115ustar00rootroot00000000000000faye-1.4.0/site/config/000077500000000000000000000000001371104377200146565ustar00rootroot00000000000000faye-1.4.0/site/config/compass.rb000066400000000000000000000000721371104377200166470ustar00rootroot00000000000000require "staticmatic/compass" project_type = :staticmaticfaye-1.4.0/site/config/site.rb000066400000000000000000000007221371104377200161500ustar00rootroot00000000000000# Default is 3000 # configuration.preview_server_port = 3000 # Default is localhost # configuration.preview_server_host = "localhost" # Default is true # When false .html & index.html get stripped off generated urls # configuration.use_extensions_for_page_links = true # Default is an empty hash # configuration.sass_options = {} # Default is an empty hash # http://haml-lang.com/docs/yardoc/file.HAML_REFERENCE.html#options # configuration.haml_options = {}faye-1.4.0/site/site/000077500000000000000000000000001371104377200143555ustar00rootroot00000000000000faye-1.4.0/site/site/images/000077500000000000000000000000001371104377200156225ustar00rootroot00000000000000faye-1.4.0/site/site/images/aha.png000066400000000000000000000041701371104377200170630ustar00rootroot00000000000000PNG  IHDR< ?Y$iCCPICC Profile8UoT>oR? XGůUS[IJ*$:7鶪O{7@Hkk?<kktq݋m6nƶد-mR;`zv x#=\% oYRڱ#&?>ҹЪn_;j;$}*}+(}'}/LtY"$].9⦅%{_a݊]hk5'SN{<_ t jM{-4%TńtY۟R6#v\喊x:'HO3^&0::m,L%3:qVE t]~Iv6Wٯ) |ʸ2]G4(6w‹$"AEv m[D;Vh[}چN|3HS:KtxU'D;77;_"e?Yqxl+ pHYs  IDATX qHEVIGl;QaLG`+[HʀAq ,}.fí z*M9yJc)%\\¿l{GGG]]{uuՎٯd2Y]^^cRppػd3M9ːY2wwx琯MDV0{9Ux{p uD ޸ΠU?4;QiٿG_$OaKo g׽Gwwm)WBBowk$ƃ3ٰcNx 4'Pjc"F_HGt< ѦUċ-𭟟g/Я"ݟ ݒQoi#xX>a0M-2%u?f3% M1sAN N'x~yg g#ul_s|_#&:7Frq'~W|y:U(IENDB`faye-1.4.0/site/site/images/buster.png000066400000000000000000000117621371104377200176430ustar00rootroot00000000000000PNG  IHDRM pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_F IDATxXYLS>BQRR ES2I(@b"qh0$* Q*pB"XA`i SzN_nzkZP> |9sD"ќ9sh4 ~cJqӧOGGG(0h4 rd'0rD6lxiiin6L6)I&VVS@lnn={vBBׯ#Ht>;߽{񲲲/_dIDDĹssN7sL8!paǎg桡Ǐ{xxʼnbޞ˗/xxxؘFqtt@&NlRX- IaARSSݻdyǏ3fxzz8f LT jZ .t2;{zz\Fb>}zBBsڴi4-++o8'CCCׯ_OZAPLpX,$I2Lx'?hdWWׁ߿t]'NxW$X` (: ]g# ϟa3jmmmK.Ԡ(:U#66V"3)H!}}:LNjwꚗGZZZ***t:+b+W ҥKi4n޼eXbM8NYYY___^^޷oߎ9陟 : baX>}BQ79tХK"##]p?~T*]zd-0333''  '++;v ---L&k׮E$99y߾}EEE!..ٳ}u֭j](JRR ۾d @(*++rGq8\>aX,>=STXڔM6ًJJJBBBRRR>l?Q{ 6d2TTw޵jA*++_~- ĎƖz]|dgΜp8 g@@$8U{P(tuuuP$lŋ\nlll``D"RJ$ѣ/NJJ}+Y,X, ̓} *www[,^kġhe˖ VCrlt:AO]tw D2lΝӦMitm۶ +>\.HqUrZ6 thh۲eԏzjA,=ð3ڡ>mmm?.((tN+AL&zEQ.yX*!PeZ###ݍ$I5kٳM&b===̗/_"W[[;F`ZzD"H$k֬ (ޞ|$ߟ,VBL=pmAVT*>S|ʕMMMS(..v(lٲfcaaann@ 7o^CC=y򤲲`&%%ݽ{vF8߯ٯ^3 6uՑ$i6aT >zz 0Ffcbb<==f36>c?_vᅅC{nFٹg[nU* QQQ&I$痖n޼"s>|͚5:;;Çs΅kL&KOO̜>}zyyEÿ~"H`<{GGG3̎@hh(9tҔ8*h{]|`0dggoܸIӉF&J!hClnjj2BA|>_׷ °0xؘ`q3g(a؛7o<< $ϟ?'&&J]v=zh||ܹHq\.  z g|"IRP :DAQD,4+YIENDB`faye-1.4.0/site/site/images/chaxpert.png000066400000000000000000000114251371104377200201510ustar00rootroot00000000000000PNG  IHDRH pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_F@IDATxܙ[LTW̙ 00 IE 156MRmh/>5m҇:4J1@S 30sp|bVWr2/k^ZGPUuFh4RPP\ӥqnn={QҖH$drr`0(A0%%%$I^/b jnR4]zAfggZtuu( ",c4b!"IF,ϧ:A;m~~?VUU1  DQ$H I,#I& EQeh4~AFFFx71 /@gEDQ~322X,i11N*XLs+EQ/Ljm L&`Ϳs"lܸVPUd2UUYf fh4|gH$^<EbB}}=^˗,8,SSSٸq#k׮D"FGG1C8HeDQ͛|>Bl߾Zs'@{{;555:tB[[ݨ/@ܿ~X,FGGm^Q`6€z:B! 'cZ1jnn';;ǃ$I=z\2eX,(AX L&vpAmۖΥ+Z2c aΝV|(f]H9&~DFܸq)L&Nr$I,,,p8Rl-|Tq8lEwa~~^ /_Nqq' q=])". ł(=33˥DEh$( V)p9 Ge~?WF$~gnݺEkknn:xpN>͖-[tmwRSSx0PYYɪUB\pA;nذݻws5֭[cXXXX̙3+dff*SSS҂d"ׇ$IAٴidbNٿ?#ׯ_gΝx^fffX![,|>'NV344֭[5 9rDg1'OF,c455=116L477hz1|>7oH122?|ATTT,Yrxw p8u떮t駟z~'Z[[q:1x\o"hkXt=S4F|%8}6XVz)}ٱc/_fjjY._Lmmf=K+7ܟL5f3>SNiQ_ٱco?7o^cbb"*ʕ+|OQU۷oF55x<BV^b^ V P<_tIgh)0M&EEE ب;::J<ra2x1LuE".^ȺuhllDzzz/udL&O>A`tWʑ#GٳK( BJ***صk >|9ݻqLOOGi\ra0nQtff#fξ}/ܳ244 @1,cKx\#i~?eeej*JJJxYf /߿m+W̏?|nrr;wqs !4 1;"VDRyh hu"̐0Myttbk >D%n%((H0CĎ8^߳W^->ϸ`Y@=⁶:~5P5( "E|>?ْF_4^m6XroivgLsP daaaZ+Ҁ;wڵ)-2!>O޽kmiƌ Ν;KNθc*}ҡCVCu/OH!CdԩR__/999"UUUi&CY$11e1cdffŋ%77(br)**2@ny Y7rykhqcYl9sFwnHBeĈpB9tL>Xwn aQQQjʳgJzz9Rƌ#os-\I \NN>-ׯ_7DUЍTWWK.]LyE)..6~ $ts=9\v(M]GQ&Y|&Qj684q?~\Ν+0_[[k$-Z$C۷KIIK,)((aÆɀ[w%ڵF>2nT$W Z$$)) "ZÇƐDk&znw^ѣ!s59rĸ 6ȉ'n޼)eeeb 4֯+ 㱱t7&{|n[s3O8QfΜmN#,iPI,^^ۈm$ݰזp?:8 ĔӁ kh(m=1& 4'8q+T%;}Ox}6w {{J"n|gl0o˼!utfz+@cZBAɼ^nMEW P$?9g=?| ٦U%P1_BVGcO"9:Mr>8T}HTYD忩g >}g^۷o_gkv#WZKeU{Pi,2J E /]-~U3OW1 \+mժB 9=w ,w GEs`5p;K?UDnz;v4.cp{u%Lѧs/ɏ@c~np* @R*+cNIW1Zh yPQNz"n;zl" ~=$Z.#ÇK^L7-[M,cǎxgggd-Z4^lwrS)-/rM-9e7_-{=UEgt5//i;L]=ZFeˍ_tɐ `K˗/˩S::UgCjג;UFg5z]1A-P%ThYwPK&,[@!q9! F6olbxZ0ݻLl̦gH cM"JO]!mk)ǽD%JO&9iq n|NU'%=ZIn8~ s%--ͤ}5nIFfѲ`7oqG6Hk]%b J>M4Bx^$S[= ZݩDdhT2W5_:&VYIwPb 』hx_9$ },>O?R 9 7Fʭ[Liʕ2e Ka}`rIlc<pŨ;ֱ>+# ܻӆ0l 9|>vJ~gJw~?G#FOW []Uj)ӫV*<j}*-R<([n5(kf!Sd&0^tKtGoC,?k>^_4X]^ZͶxOQ4\]пtYQ&\')!?p~]qk >J^}1l>bm_/J(R]6ыi-@?19,Ņ F,Sx@ƴtf]ĦUɄOeðm=7 Z"PY+2:J5jҧ`4bAL#XJa&I%PY -9YWVZpseA7)n<'a2C E;v̐̏ &X^-}'fk&wܸq6JX`A###lܸOr9cTW?^7Kla3+l4%ܓ;~2H{ ~VMOmtC7Dr}W^5-Ko"nD]M a2*οђ|S!Wmg?!I|s*Ⱦ@rq`L=uB']4-y,ZmϠӵLҢ$W,߂U85tεo[`e IENDB`faye-1.4.0/site/site/images/faye-cluster.png000066400000000000000000000410261371104377200207360ustar00rootroot00000000000000PNG  IHDRS; pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_F7AIDATxOlgz?J!!"Ǘv rE(e[JEIvXVc---g61§c{!\z c3 ]EPؿû;7y%|69y>3O>%L-@ yA@d??+vܸq "=ww X'|n޹s "y>`QkD/EE1Md'OܺuKug}vvv1$ظIdN>@RR<3/K&_BCzbq/>}qffcZp!FNx!믿NU>2`%[X%tc{ N0Xđx%e/`)/Ȋ\ I#r+q+2l!H'M V)uVsKŰ֗+ZƉN4 uaȊ@n)YFɋȱ\ I܋X(: h,/"" B\:*BnqR?Fv, dFKt,$Xu?@K(R ۱$ba@ XȱCa (EzϧzOXI%$@#rdF>Zhzpx7t +nMѣ .[pnU |oo#%~bX<, J#q`0N8tM)(d!L?g˿ˋ/ nEƲC *F=zhffOE'qeKCհmjbo1~Ĩ>(Vߢ'ȦݻoF1tJO'W%?Uaܶ|-..pףZCՒ2 oG޽gzHIN>iH88zS4gffj ^X,Jj*L_+ ˲vwwCX*J18Z4VPfB'MLo'I'IDv*Zc<; ;KΝcSuKKK챦irV~F p̙W_p@?2@H/qaIɈŸPTE9NbsD[kRn7n囊 i;%!wW\j";7#?~8/-6u@%{rg 􃆤7sj~*R_/\?f.֋ɓiDX#G,bٮ%,BYH70Yhz:i]Yְn[BrYKt &:T&-,A\G2h840$@8"$ a#K'yÂX`jBl6nݺev r労7 ͊W\Y[[ i:P+Wlmm_MzIܖ>XD R+++CጢL2:u@J`bz-9:Ieomm+6{ST*$ 4Rn%8';K&h?`p~)yӤ0Kɓ #wi\0` ms;AvݮF4+1Qդ ]vLF'(P///0 ֩cz墕8!:`AFW& &ߡeY֖ D%qT$l4b ;Zi [[[˜^&c?}}6}b.Nl7:UUtW¨DjzzIz.//g.[02@ \ YNb7pD\w ϲ[nʟiV*'O>~X jfp";t^Jp֭P 4EU*Vv zB0%;g aj+ȒIC`}qXE~h&=iRAgYgeeOB=,뗿%̴m۲dL4!._'['NXӅ!x__o BجO`_sxI׿͛7={ofnnѣ][~ǩvݟO?4tZꫯΟ?Ͽ:z(8NVt,Di~H$J%|d0tJclw%ɍf"FGdIS"X5z|w2[pvG\KH! q$)oC%99vx$(B)¨!PCH 1ldF)ħO4W%kQ*A⃞QX * ~b=ASJ3"㧧 Qݥy~C#TK/_kl86~ b2G?D(, yJC<$ )҉#XTFhV^BŠ$9L,ؙX_Рψ"#.8( RTaAȄ˘rnG`FDdA qkBqE|C0Ӊ( KZ42^5=ְTDט !h#=)20.T*L@$!ؚ9QԃC˰X RkDzrtY b|(3l;;; 0ԉz:B>{Xk ()b=96t# BAlȀ l!F1wE2F# CNDFfiJDSJ_6"ryf2]a G:t*1CΊpnRvER 8 "&`n? Yȭ4!y9k:A_0{K#rUkQq1*Y^DEdAt*UJʯ)ٷ? , LC5~ҬVa:Y HwtȀK(ޣ{ EJAt;c3:ԀQJ0֐R|TRKBo @uR]YpR񹦓www濬8!=$ \qxkzzzaa?OXVbXXW[TKjwwWiP_GzR"D'[RB2bV;X:BVVV(^z) 2 &\dG@&`U7{ȰJrזyNV hLը4MS:GME6D@e=ϫFVB^rβ,6+eM(ø2PE/QxG2$A:W>"A( ! I2 4E2)z^)?~JJeXѰm;lnh4г!:e Lz~i`?m;9ÒihI99?t=)5jm|hMhz/ΒHF/ 8zױtՊ*/ _:R3&YJ-sAv3 #l08LqͨN^o $K3jfؠiʜʫQd_^9a&~ꩿãrMm?d,kkkKkKϜɊW浵5`ZgN&vӧOg'nqI6Nty*EzXޑ8 M\nn&I˲lۮT*R&Y_y^ ?yd&zQT*Jj_|Aap#^!,Y2|k:q>&>b?"|'|[o8qbrr/bM"@جO`_sxI׿͛7={ofnnѣ][~ǩN"TQ_}pIBGӅlEAhRM$&jHRT^GVѧS+gf3ZYȭ:ҝ}P4Y(za XV50tN XHT=PDcI8<.O ̮C XȱCa (EzϧzOXI%$@#rdF{c&d@ю. oa=8 VpfTcwq 1#^@^Hc0ܾ}SSSx&%{l^k\ 6tʪVXZXt|V{Pw۷oߦTW4/lT*5a!rJd<|{}g}vĉ~ңad őN«FC TȰ)f6 @Y b%SƌN7G&0 p6;;onjaNj$ QH?I$. Uec0Mرcð94>|8555;;[,džU" t'Q=I#N9}3Ǥ4U~vvvnnnZ\.|ZR2Ui?餪"I'g(b!+q>^+DNq ?_+¾ذ,kww{giRIշ= \<B,*筮{~Ǫo,J^']}J%4gffRbTQ- zXJ{ūjXTF=Ë9(:Mە%LT}]ץ$eYҝ˗ |.\vt:ׯ_wAݽ{Wݩ޽{5,..s;{~}JfY*.\`Ɛy-I͆N]~ِNV過X~vpڤE>K*FM*UjY8\qO?~h.lnnj5۶kڇ~I7sV _W*0 _߶zEC=ӄ yXO0 xD# 8KKKv1O'}ۥ셸| e X}k"ibT~Μ99'ɳ5Nx -8Ibѡ(Z۹V> Ts t"Q֦d|.8+BzrozzrqWÐXwc0ɑoQ %!$#'Xt2xeYKKQ%.tO&Urbへd4hkt +-3tқ\¸ >Fms[\ \Ge5@&[6nLs4ݶm65 ۶ٖ)S#U!d!B&&1ܹsG;[[[iYTXW_}?t:C/jFpt+++,Oi>0,} N\ Apiq h_9SHa2bq$Fq ?VjZNǏs%bth4$!;V|;Ēb8AvݮHģfi1W5ΝC: 0qF߼SyyyYOѐ8ښk0 cmmM}9O>eMU'IhRhZpI"t=pIsZVTܹ eU*q>}-+JRiZn/ceYta a,n/%8j1˶Vn)LӴm{aa4+W'ND IVk seSO?jS)Zxf;_Tm[uOܘa4Q=e.4Kzoa èj f!4Ly1L'N^bHdُ ؊#_OY0MsWCHs%|d0 hZI.6:H~Yxͼ 㘦yKdCƀ[Rzqqw1z|wQOMANE&z%:nsrKnjN`$RBB\&|;]h8jXeY333Z%#mXZ ^9UXVǯUPd 0j2AԐ;EM {3GĂ7hB|1NsUa/6PT *2Ds<͊i^vM3в~X,k>T-pyq4#:X,{׮]_o].-ʠgTAowi@_>"m/ۿ]ǝ~X4 ]\LTY%53G!;z^~"}Za$%ԐIH QHyCDrA,)Cyht8!6_3Msnnn}xPt(IrKH"YܪPC6Ȍ$X."eAj>*,@cTNdK̈H\X,aT!n 9U(#0zXzm"t" 4’0VG2W y5!2!5fB+Z*HOJ/F 0 Isf )4 2,V/!D£o:h%]#_'A#|,ʌc:Yl~o0 òriN CPIaRC/`Z5"VVIac@d+nJ`c@'QnoooooF ~S> co D @ 8l87o'1jx.^yxkĨxEo;vi!_DBfQ/ϒ08gƆ*.fIJC/$Ti~i%˰qJZ(D$ć59Na,Ma @b?Me,H@0S&&`EYC[KU BUB <~%۬jCqCԃ  XDzy)@%!$xAD/p +DO,SġV 4JT=4VJb+i¾BYq@QEF;,gMJB X#`p~t~^xĉ/&.H,dN}{~_ vG=ztcǎ'N{iK| Rzw5;}7a+ ~/mD쿪>nFmE+,FYC~ٰ ˟;P_ BMwxU6+$ũ~O?3 C VWWهŅZ[[ eKyyRZF >XpIoCp'W¾ewh4 1KYD* @J0x Ǐ?~8MvhpTW fNT*Vͭ,,,p^Q7۹j5nN^9%9m SmgΜ.!W$9z[]#twW6־ ZV={طɁ!rEifTqAGz.!Xj/>4G:h4DG/"?痞 b} \G,z )y^^o4Zq0"!̶vͺ?'OdKӪ_mfҕ+WǶmj4ms&=P]}]9D.`%4*9h ^ӿi..BN>]T, @f= LOOKϯ XA>}raܱz}ee}짶mQ, xsnu7pzDq6wvE˱.q;wp[P|F~x[FⓊ00=ɓ'W_}u>\yz|VhZi".<4P,<` dZ #n`[ZZ6iʢ1<%YWBKk t j*(aFZEBaJcuMjbtF!`|uE0"qJTIC*ĕW@Ed#z{Ojaov\FfaT#5#P ǎA7bHG*L+LSjWⶣH 44OB,QdiFcE= + bC $,]~$]>@z0׆! RwؚD=B,U[ K噉qCķc(H݄ @ X C8,bxK4'Q*,1(00. #t?/@`,5sYY7#°-n'0f!"0"`mooF #?,4@ s=mh#`o@ "FIENDB`faye-1.4.0/site/site/images/faye-internals.png000066400000000000000000001226461371104377200212640ustar00rootroot00000000000000PNG  IHDRno pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_FIDATx}y|Si*)(MrAIEiTzUhSTJ@.*e"ʢ.mR(MZ l'^oAg3I6d9|EQQPXa n۹lflhPW^2FVsRFՌ7\?@[k޽{]vڵÇBr~6U&ZLB%$RB}}~2 Zjnn~w+`9EĀR)PidBmW>l=--ɓ{ٱc?~Pмr]K(uAŅBEW/?~w>o199!fBy$\@*dfaᣰ /~u!x ^JHH8zC WY2.!5 \B@bŊzwkf<ѣGlْ4`VHYi'tyJO/$ 3֭Y"3~xWK(},#vZjU^^^ЭסOT K8 KT7+ II_@ ʕ+Bɷz+!!; ?ñc*++|#,)k?>.~ƍÇwiiiRff۝Nɓ'}G?p݃+Yx 4͛7#~|Ǐ_hPB+W䥺’I%,Ln!*-}d1=zI^Ӊ2Lw?Aɢ#9Y2.hQH*a544Z{'BHVs'n Zm>m۶###_[o:*[nݺuڵ+B0={ _˸(;Rѻ lwuJtzBaѵk׆ wCa#HIIѣh.@xvQ/kHz[1D!b, ԩӕ+W&NRu&܅+ry}/sΗ.]Zz5IOvnܳgOHdO)R`+M.ϟ׋r1&w-22{/_믡߿DXǏx"ƍ7N0ApEEog0/? !*",VBվKw2d} w/ b׮]d2dСCx&B^pr5"TW\FM2 0dddۗa?Y 6\x %%Z -m2ЩS'q˖-Fw޽SNs΅yau0SO!X_|EY",!N:/$`  "as?~v3HEXd"+F&4G!1x0`%?HE90;iW4vF ,YahK#B(~[o[BYC CӖzʻ1cƄ=PQ7xOf?>#lQKRJu(#xEJ%չ_/_7LKK[KDEEeffT*QQQ WTT`[LIIIHHp:%%%w}E =!!a3oRRR<OII 6vk4`DqP`ZKvvZ.))p89\~l!3f\_|0̼yBgΜ5t-I/9<2ȐBv;p,Xa&tj؟B3f?9B`c #նt<*LBlATg^LT cff&[BVR5t-IYATH IOj;3 UUU 8_|?xV^i4DJOOgɱVQQT*U*w8F}w_5 }Lz뭔*QRrAꖅ&^&g)8XDEEBJ -k]aI ".\d,\2h4 .Wb0fHJJb zmN}DFF*A4߽rƍ9s&kE~~lVT7nlQm|ڵk❟G1yd!5;AeK$Xjiiy饗I;?S,2mc2==]єq0ŸtXѸ9@QƑ2|\[5 !ZBB`BrTdIns]qqq~?..v3 cY6DeddGh,rH2,D&P(!lzfzR4vvk4^_g077l6[VaJN3 9*J JF)٤!Q`%$[ͫlE[!ca:Nӵ]Jh4SP~_ AϫQ1ej3^6H-/ i$SiiiYY٬Yz}pMrd Na58k֬DWJƘ}Qd4EBFvyé,MYY!vʬVˀRSSfE\.^7Wh4\0ʽJE79Wu0|ln?hfqlF>ql~DB TVV5ӽ$ LwEbX4æ5y5޶|Ma:{lݮT*sss)So4EgQI܁7v;>K0{첲2SK,qAՊ͎[NS*mV\=G!rT/.d[ g6QQQx***dd0pPa)S`R*/}.R8B74!'0 v#\.WYYٳWʚE +%.[,fj9Bܟ^ܫu:~p kv: `Y$U(,,,,,dM]xp+ Cnn.n[<\ JwOt3aW8p pb6Ն`jJr=k׮SIIIvea̙ܚfΜj<0*ۢRi끑0*iBDd HMn;sjjj⎂\3pV"|P&lB='?w5cst8 V0 P3kP;%љRQT!ϖмdS}Gc0Kg171М9s=)x< f̳F|Eij1nnau;A/^i[4Y[qoܸQRuybfΜ9la͖/ p56lp\SN}]*hXۖ5 \ϷBCm0o"_&$(s q=ѲjuAAVk4̙v̙KHPI@&{DzoZZSX"sa(:Ch/Tt#|hS[JII0uTZt:q;;;ĉsXNNNYY4++wzOa^į]/ge` ijj;+, cM;/j;$$$dgg[,\Tզ$&&Gh,X9vϝ;w{׾U. [JJJff&/_^QQa[BA(ܚE8}^Ò ؓ(Ӝ^|rłws;dff655͛7UVVccc͛!#v'**1'[3.F_3@lbK8 Zq &+ tzݺu8_RRe?{bN &'W}Dce¢H\&mdKB KG { 1|,AJWSd~ )D7% S+u--?DDX(%49 t@ݑ\T^nhd,+E9C"XXhxFF3с|8dW\㏱3أG$66nܸaKy'F٩S'>a_aɊ,;vرc{oJJTzn䢢pn{L0 ]0LSSaF[Rx4 !tm ՘Dmwuu5BhҤIP@v Dn:& QA!4q455O>yDmkÆ .\x衇{>+`an] _^5^{^6lXll,϶۷o߾.]+디ʒ&Lx砇 W<,)>{M6UVVfee~ݲe B襗^ߛKZ}z7nΝGLMMe_{ C2,nSx<E;@JTj2A4%>:w<}WWW:)avܩP(^{뮻.go?>֭[ۇmd"ӓTdk $œ/ Bl6#ƌMr33g\.ԯJ=j&,^&)7 LZ08kڴi{=xf_? /6/_~Xvԯ0?VK^=HXL4n"h"::z/\5kV[ ܭx_kkkCIaj+IMR)u! {l۶mv!4j(^7o].WEEp\Z\.bnfBBBbbbBB+^R;N0T&!!Aj)^GEEi4VRph4ujZV&.p8|DRD'ĉGbun߾W{"U 6* (%I$"iaYԩStb2 aw:s2۫V8K8EUUӉ+333==W{",[oqobX,XhnwEEEyy(QZ,JE̛7az%C~!rY^yȩʅaB{7nJjU8dDҥK]-c6ylb⸼ d+J0C,5o޼ln7hDϚ;wnVV`*SRR{΄?9$Gt$B%++Brl@eE4"K!+DX." @%˝;wUyf6 9MRX+`+7>ll.!lYY/C ,!!mBtPXdgYÇ~ba|#G"##zꩨ+WAMXd<]h0!,(,zj~~>B())IR=󍍍'N08aa8Wy[Bzg0X0 -6nSO#III֭裏 ֽ{$,JS{!<,N:g!Fv}ժUƷaBX}JAS_]^I ZZZV^}Շ~XVs2}jkkzh'OE_oI=Ѵ98C%,{ /~ڵ#***x8 \Iz7"K5k֬Y O<ϐpǎ;tиq㠕x&1u݀VbРA=ϖXz駃zfCn:hQ|WBb?gee/z%Kt-##gINN޺uk^>N:AӁ/^\z5B(99oDbor#O?t\\\cc M*C(,,p€z=~? f+ʃX, ;w/J)˗/=zٳ<ȕ+W54YWpʕlիW7n܈W^|%KΝ;9rHee%I|Y ^ѣZVղG{ .\vܹs㍍--- hs<<믿~FaEDD8N3<3tPh: 73K M& HZll.][NRAc(يs@ayرc,^W^Hw2dHfffll, ޲?~|]]]ccO>i0/ 2x`'|RWWh{9hCV;K.YYY۷_pfl;w.{ JJJjnnf l6#y!@W_}5::dS~^z? >}:Bʕ+Rtܹ۷#f͚չsgh7<YA6l˗/'''s^ @VVVΝw}!x?[փZ au$TUU˗/WWWvA[@Xɓ| $h%o kچ:={3p݁ vw|d֭V XC &l#Glݺܹs=D8_}˖-Ͱ za:uZgϟ?onEJxP3sH%De^v-??ҥK.!FNz-$,Oi *YBB))%/)L:+f?a.-@Y\biKx\&$2 GR3(YRŠ^^yAXzgPx%W^hCSa!N {?{ ^Ar yT(a8^ҺuC|E KT^y "\pˊa+Qy%9JeCROYdzjuEE0CRi4V-; cĈ!Vt8avZ-W!.9w\;v\rrp;]0\=ZOUHXFz0BA^!P`fҥnu\.fl6o*((p8<@Y, h4̛7_p/ ЦçBbEK"%AaQUm ,+**D pTTTH%̙3ӦMQ;͛t: e"Q}${> oA')n',QNC)FlE ,ۭR&Op.T3gFZd N%p`. ت=-2.j-!F> a@(JPYYYqq13\.Ja+>R*vnsa͚5NNRYJ2AzJ`x\T\R%(hJ+?6rLL SttU0 xqq1OR(._F o3F!BF㦒Ox@/PŅ^KJGH0v7QDC wiD.Kh&//Oo>8l Z(u"-aM;z= n/--e3LaxId͂l@ 550LFF~vy$@!sJ_0)PBjcd=. {:n@/+=K((y56pV <"J-ZGx=g2F#eO. mk? ї "h4BnZaiOLMM K 'JNg8qbCCCNNN߾}#"""##i你HCKK 㛛-K׮]e\BBb@D+@x C'(b*-&zM0*aJ4EaѦHFtRҬa k"ڵR'T(, $ b .A^ >,q[BEVw$ x!"9 dE dJJ~;99yʔ)q*YdnE3A6qDw}? O;|vG}TV?Ga!B$*BL[ԘfBӧq>p|ם;w|rAAU"##CNM|GDݵkܼzjPRRѣGO_~q׏=X@*(W$nɰ@a_|Я_ `NFQZZZ\\}-BsvVXl2hI ,uLPBTY{dժU>y$/t:BӧO0`+Wt  W ~=2-,!ԻwoFM(,q Fcc#N!667Zl;v쨯2a„nݺAc3C^Fի׮]2dȖ-[>I #)) j'̚5륗^r?e!eŋNgSSBhÆ ӧO6tY&L@޽;!!e+);l0gڴi7|3!~3fLlllCCÆ h7n<{V9r$e:u4k֬ӧOMûwܹY@^@X{gA}d{X,<-O@X~Izqȑ-[8'444ƦC@X.]̞=!T]]믿rC￯;wnh0a1co٤B=,ӧOwX,}Js 9"Y@XEnݦLڶm.]@+ZY@U@XDJJJ7n܈577'%% <F #^N:޽ȑ#~WeVҢW^/ڵk- Ƕe>c   m'"@zÇk;w.))y ɳJ@a._ܩS'a$@&1SN" F`p 'N ;o C8$~9~K./R^01{[JȀ`"W_W7Q[O.`a~WR +@&,)\)GFCF(9 C SB")WXB"x Y^ acrEykk <[%JU>TEV[ A^RY^BXR˫xPz%D ҺdRdp-ow1,)Β", |WXRSS_.]B| ,VlcXd]Foi 3%:x7x׮]۳gO ,@r @X택;w nݺu„ U@pQ,PX퉺:у'N xN'ᡇ"SKȥ'$Bt EUU?śҷ;ϟCG8;%J6ŁK;aAУ-QzJ’u ^!YrJZ[[;cƌ6bRzeVXc;k FXx<PX*ߋd|Vh"bw@[bp&$$8Nq8r\@DO1b˱Op8͛iWRT )))*[W!V]|9NdhZJV/z1.p\.^̀V3/3’ i! Y@[ma܄t111\(JQ+))qµ{{O@UUUUUWr  h45:yq,^"/矏 K\vxG].pt^I#)#,N>zJn7>n0( rM65B18o޼ltR^ `~q g&%,E؊RIMymjJFyUbi*(()G\Rrrrv,YB/0'R"0$K> Y2a!0 1BO%O!aUBR)my%(|.!_0Wʂ ͞=R:nEEE/~㏈DsweeesyhPp"//O4E:]?#K%1t hSy9> 322p@n|:ORf3ߤ|=\*,,ccv5..8?(ӗb(UR… :@nݖ-[W¥ Rn (//{nc2JxGFjre剢8Ri4ijHMM-++ca Kn%XlAuk!I^ek@\r~#xn^z_YYɽf|R5khc2|8+L&aLJ2//o֬YlnQLMM s}Ѭ,$ m[n;EvvvG:Ñ#|0zR·TƬYB&///770L&n,jժ/~WIIInD7I/ aN@ޤG-[ks ^",-Ѕ݄% j=W_!z)=޽;''gŊzPp2LJUXR_oqÇqآFcLLÇ˰Qd &bE Y^IL^+T#H1hEyyyCCw}yu׍1bC ѣGH*,)5#kQX4B)%{Dcc5j/0:tPNv? W Kq4J4v#\HA"dbXZ0^:lذ_s=wq:bĈ!C¢Y S+ 04AtB) 8p@XH &|Fssヒ1b7LXƪq566ycRa۝Nɓ'umܸqw+=EpCXUTHHXǎÓ|Ǐ?^XX=_r;JE*,r%{1,_н{wEEEG9ydll,9NdҺrJD| ,! Q),K} уoD?RTw}wBQ>zA/^,LVyk׺u[__Xx=tQ1BGe}C?DH,\5uW^yҥKknjq ~ڵ~Æ ~Wsg hb",7Ƃ!9k׮.]۷ثWy]vo6B5kF,Md xƍiL'SN1vذa?OS/~'h:@+}C k…;B|kkku#Gt7ܹ3V:|0+;V߾}i04ɓ'KKKf3y .lٲ!4w\0/WԫW/;,HhQ z:z>zU777'%% : 0Uuusv{;wܿttӡ)a JNN|ryyм\R]]6m7 h%g2/ bnaÆ3ghZ:G}pp8 ƾC%t\-fTzR 70d׿5#/aP2 ˷|PUX^+aG>QB:ԺWUQa !Q%!Є@d-I|>oHâ36wBl02()YZVSjv 2'|@/bX1t՚R@޼[ Rm("޾.To#XX0aOTz[ vB>}aQXΝ;{g}6v' OI% x͛7g_c J lgbdx[| x<ׯ3fLhG+O,,u̙͛78p̙3={|{={5_U|||>l*љvƨQnjҀgs | 2t98pwx<,ymݺuΝ_sY >QFؤ)/\BY Cbˤ,g '$$$$$wc̘@s:s V={TT.̙3wYjO׭[*cǎ;6m|6Wxeaކ56 Li&VFzW+Wnٲlڴ=h"/QXdmTǎxT*ըQtnڴiJrĉ8@t:lR[[xT*U||رc6mڄG7xcӦMNSVgݺuWZ͖ܺu+B| ǧT1cƬ\!4q@QK$:Jz؃ځ\.W]]k{ܯɲQ'=zӦMUUU'>>^Rmڴ=WXVuֹ\.\'; (s"[#ebaӦMjKui4߿_<ϙ3g"""N_W6fr0-[mŊ* [^;Ε+W^Z]Pry[/jLR&6Gjl`(sbAٳx<؜~yMo߿ˆOÜ%+VjLgٲefN4f&p z#qn>{cUDDڵklٲ?\FDDXͤI-[6zhV탫?y<3fl۶Mղ5>f̘l2[ " o7Ӊ-GYZvҤImT*fe˖&˗/_͜DTd+$h l,k4Rɽrlv)))8W j!NWUUcJBİBÁ/ً!\CzzzJJ w5$T9ak$JwTdVkX<ڵkg̘]3  ̙3 ؠ~II O( Xb[I ^CIG#@"ohX,Zh4ᨨh4lzrlRUSW!VTT(JJ0L7pa=Kx-^[[v-raHOO^AKYfy뭷RRRnwmm-E OʢٜD< ~ .+PMPNKK[t6mB(33S̘1c޼yn}Ѥ}̥K@x#%!+DE-$Rf̘n0[͘1;tTΝ˞)))aݧ`xnNrH!3ʦ:srrCRbbb"""ⲳ U*Uzzzvvvtt46-.\M_R-\# JXf|kJ`0|zV h4ɓ'O}p?{F@˅7b-#YHz /ZZZkTG^ ѿ Us*((0*jÆ !iNRq"K/577[,]R0mQ5Myy9>!Aw$b[/pU!abӱo(R93ʾ![[I&eaR^@  !dك{Fi! F&kaHtH*,Fvc3'- "#ǹ4lb^T*fnwF WC"yM$0\lZQ*:`0Ņ9? @,!Hct:] l&yH$['YN!ssR*Fя*0Ij Ovt~)aZ|I }W#i0'ZkN><^OEt{=ʆf.** OM2"lT>hЙШ;`ZiX Jd2H(E L&REdahxz L&ꌌ`H]Ghmy>OMAjKn}jr%Nsݳgg͚R§L&(ZBFSSS=Y 2\yMM BvYV˥T*SSSE\.^7Wh4\vl;XɦQ~-29rYm61yyy샰lܯo&>F>ql~DB TVV5ӽT̆'exS)--}Go2 ZٳgvRUM2|(FAdY&**J8=fYY^7)N8YjfǭY)v`+(M6GqcR% 5{<|.46^ IVh.~.Sӭd9G9sFg9sn)"C8~*t0#k.i-¢R.oZ'h[aSbjV)//ݞΔ}^KgΜ/g}`PXs۹dM," NI4(*> 'j .LIIaJ,X@feeќ9sN8*zS*f`^t߲yfFpBV (* hB`Ce]ҵkײ'ORvJe,V`6ʙ/|nv7{Ĺejh?bCmSs"!Q/;;O>l5kț<܀`M?W6u۶m~40_2/߄ JqZ{- Eqȃ*ABіh dSDEQ&aI",!RYUFF" ~H.R(H(Q z?*̍g%SV!cNY_㡔t2)B֛իW{BBq]w3FF?Qbվ}Ν;R`w'; Y##*؇O>PnC/SU Kd<%SPENNO?}-;JJtJpӴr~!4Zg#0DY ڳgO]](iazq؆{c/$,ѸWiDjnn^z5B觟~*++û a! ˏVC7nFw  jc`%)[wѣG7mw.dC|>ycP=ay50I-{j[ѣGlقzoVZlYdddZзyG---Rla˄R.!lhdTxW^]jUKK{5nܸӧOc 5jTS5a?<|q;mm T ի!VH"Ɍ,s ēBdW )^ڶmۏ?Vqʽ>?nPc h*Zp!xh-7|7d9e>|Ȇ!}+ն P( m>)R?css?|r9΋/pSDŽ3=h+Ξ=F(н{w6?@ &&&##7*#F|g{AAV p9<ȍ9?-A#|+~A=CR~iPeeÇŀZ+5.:::--MS޽{Ƿ]rڍ1,vEh.k[͕P\|>B%''թR/s]6nܸg2 hѢaÆs9SN|Z~=~#xd?))ItoQٽ{ݻù{1ˆWʟ.]ڳgYú  _G 0`Ν*J]vϟ6l?ni{=J۷ҥK.!KfzݿDj^;=Ο? K%ڵI7ȸz7RW^ߗ,Yr9.G j"}]o>^ PBh֠:U@;{/w>|…ݻzxMM͹sxOsK_CBԹ#o'~xiws\766+""t" JKACOa+ûv!V%zХKjjj{u=@^x?BVy]BzPKJJx̻C=o߾?ZSN"[ -: fIE#BLa1bΝǏG"##vV  zS&X;wرc!7ty ]?"8H,a?9zEOŠ|g͚k׮3gs=Rw[sذaC^WW׳gϴ4Wh%fݔQvEȺ;yM6UUUD~j/[NaB]1c??Ÿ~+W{19 )HV^* gКGz׿_m6lذ)kZxqZ-^!Bh~\ؚ7npC=롳ZV쭵ݎ˴ QA2=::d2;[n:t(/Yn6mLvE(< uaPcC ߍ7L&O|ordzuֺ:v|0666>>>66|^z BM>СC{ qÆ }llO?7e˖?+bql޼gϞ&LtKؚ2bLvK=ѣGI&^z˖-xN}}}mmmdddVV@@ժUΜ9sYpcStLwnStx5'UBh555G_|Eٌ3fwhr֭[G.5zh !}Ν7|ѣGcbbƍ D,%N0g N$#SBIgoرxΌ3nN6Ί˗q,KJJ ȫ@#Q& $ERR,&Za4WXb /X֦@άp8_yyRRRRRR,[bJ6ȒʇE)|q ik 5''af۹.w`ar XV^ R:hlLC\4͉«bՓfW0 2V@vQ’@,$K*{=Bsu 07Yֲ2^T#yO.Ki 0\J( ܟ0hyFwR |b\Fua QE&,؛'ʬV|@İ|h"ݣ/.a4B~z=Kz>P,(|RRZ 0EEEeeeF155 )L7111L&+|3 ΃9$Z@[{EEEEEE̦&^A)J^P55Շ2Cs 0//]e޸8^VE@*B:>M ت=ayN_eddpTVVv<GsJe^AJd ߥr0 /Ut=A #eJM1.t: 555a2$+%+q%)|=B?+leI/}f{ɓU*Uii)aN[hRd`/̙3zԣfD^ĝe4ERչyyy7&It?y Gxϋ& v `VrKt:^/NzlO8NTx7}FFFKX4tQh4B| ð̅=_ n,J#h$>c8qbCCCnnn>}LQ|`+Q1uM---ǏonnX,&{ ?#]M 'm|vAoD'oT"LU4.!O[QBR밅 7DRY^4 aWf.!sqي_WP&z“ K(yaLH:/ʗrܹ)Q +,QExT Դj*ݾ`WRDEET͒.!LF$I$"//իw6lXhsyȒ ؇W0 [n=tPϞ=Ϝ9jժxK.YR"6(AQ[=/툒sέ[.J*-Vy";t*5WsssMM @.]}QxPXXv8lذ;ѣfG{+DSIIe+qŕ+W vmSӥKgy!ԻwÇܹsժU+VP(xSsPjr"LUp-r} ڵ ^}ݻ }رcCq <3l;F_vڅe('|?'x{|ԨQ ydL dyP^aI]Š(Z_8ˀրa0'VgϞ+V,[,xZзJyG[ZZ ;–hpիW >O>Oo㏇BCXnv|}qwH/_}}=tB=놆~7Nt߰aCqqqݣBDUдo .\&% _fΜ F"6:|% o߾A&M{CC~AC(O?6lXqرcO8ܫW/6Apx"22R˅8{lZZAau޽ @[.[o@rw-99jiiYbEȬi-yA_G%''wԉhxԨQ @-!@իW_x!'|'ƍիmٲ͛7'''B3{ٱcH}}Uy: {_|sKK˲e` (V,CZ#={ 33ӉZlСC@aKZdO?-իWΟ?o2ޢ\`ժUAˏ$N:Es"K˴޲>1s_ ;`a+//tMfx 6N<&orwUA:'??ҥK.!FNz-$,*B)^ v?^ƣp7 3xM yE4ۗP! KBRaD`ZRWPF"H3 =ϸU-G~{y萀V"22.HEx;2ԼSX< %>}l޼{Mknaذa?kng j;8hH!dWiAi +L!C ~뭷BoxEwQ~$|)ɷR>ӧOƎ=ێ!3(S cX1xqݻEEE[ly衇d量~Ν bԩ]wtE-.>.!|d;'?\;B' ,"{ڢMaX$+22rڴiYYY;w2d=#U/<~JgK(Z H-j~.! i!4!;|g+**, &,fB?mBӧOԩtB;d£Ж GE"ʊ,*s)vy?lM\_TIȸd?k&o($fca\ܹsl2d^rHzn)33A_dOs]xo[\ԣߟbX>W^y=ܼedG͛7oٲ9r̘1Aݞq)Qt @>P-aoK/ٳСC;vxDZamذ… ~G:?S٭ &L0f̘:6o1aPXW-^Ar eŠ:uo]]]=tЮ]{v/K bڎ;wrnٲeĉLX111U) u .!4WB(>>~С{)--pGb h+^ g+ܷo 7p鸸z*n-ѣ_!-OX)&ْGPXڊWFdz葙jժ^{- 8PetŨիWWUUy<ޡ_ڨ?z"+4RSSwߍ;6(\|y@]F EY %*zWzuu͘1cҥ?|lD@WT>DHT!Y}}wocܹ#;v`b8Na4ZNLLj5n\pnVhRRR\.f?$%%qgggꊊ aJh4Z6%%p;aӉ/E8vTeD!d[*1,] A 2332RPP~eWfffzz:f{wn72,KZZZ+/aK\.el6[vv6*((p8E*QPXA 0sLh|7L寲2t*arZy%&''eee\.s\* !dv;֬Yt**??%Yl0)3HWDX; @p^9Vg.G srr N37nLLLl=uB!455Y+55s^~KW&o ׫/~X2e xÊaf`͏s À) eCrwqo ITD@:tu1Q0v&z+B%$pˇ-VyH#qqqyyy/fvQʹd¯+Wu'EނPkԉ~lՁ5k^OMMmMzjrԔ)Sz}TT㩬$̊/z= Xqqq& LHʌ}~ۗ/,+ zg \B555qݐʝc0LFFn^4o.]ݒ9R#Ёt"UJ%WRa“lśhx%Kd3\?KJʻ@+5&BmܟFg)JOu|%j:77^d2͜93ի/^yfbcGIO; [GN,,CA0zjF4< k+h4vj۬h4‰ n۶sΗ/_^|yaaaRH$0qĆ}=i你QHPl6q]\h)\|yҤI)))GL"LTŕW_ZZZxǏlXvJRXP4!aE\ ɫT*0֯_x=5wޅy/s Fd_$ҭM** ̠x=ax&an0`ݻjժe˖ȒR[`qV+EB!/w{#!!C|;}z_ ȭޚ)**ܹs0^Q,Z%2 `p>BhĈREDD644;vlÆ 'N RmS"Vaɞ(G[(,,׿5`KW{JJʱc6oޜpw?G렻C'TO ۶mԩӘ1c\Gyd׮]+V(((:qwKI ʕ+Bɷz+! '; ?'*++|#,DL a?~<޺~zYlܸ;<|w}+KvDDnw:'OG>裇~{,-m+,qY444l޼!?#O>~En/,,ܼrŋ$aIu%Z~&c^o[nn/,X @hvcWG4>Qի?%T({xg677 ~]t swٳjqz饗z]}][CXv3fM7$L"//Hz>xAҹbY ͫʫV!*xT%+V!&#h  ۨ^d? KBHDDD%i=xA=G*,YgPw",cA.]L2w9wܢEnf_vm޽%%%7pd<^+PJJ!xnZWWN{cccyKnKH}%ԩ+2k֬}۶m;tP_z-[|{ؼysϞ='L=DET.!e$ b{'/^{ !vBӦM"gw;sٳgGxN'JU)%d!wA9rd֭#G,//x?,;E-r֭[G.5zhU KJD]’\HnN(q֍78cƌ TWWr-ڵ_ry N')--MT?mғTJ,_cH Cc 2$11fam2q[oC bjl>*ŒBny ,@zeVXsY7Mp8?=uUh4kJJJJJ,=aڈ/~f!!]!"&}%tm+W9sf\ᨨHKK*0bZv㵵p\.|Djڄa/^˗/h4f_=b#T*VMKKs\= b8N|UjZ$$$hZhZs].ሊh4j "[’ i! Y!I[GJŲ FIIbILLLHH-,pjGIIIzz:/4ƃ7ow+=ra.]ʭrY,aw}[t:%33l;x+Qfh~Y),BRZLdXpS. FQra2 3gdEn3gl#D3t^5kְ>g_x,̽BW|%ܯVWJ#[ޫ YO4zE Ad07Yֲ2Zq:R/m#%;)SDGG_ d0=agj0xd2pբd={h{0##{UHyy9qns:uTB3\.0va!R/#< WPQh4\oga6RIQSSz.㽵+¢hkaɳf"u:ƍU*DC1 jFcnnnTTe,jTJd2Iգj-Z$EvB\-Zd02{𤌔z_BUvA60 n[Vj.F5o?4WZZjZa#:WۚuiU*NȐ E&vjb=QRtDG4 f1@/^-'OƋV+W^f.!#1ql WcTP8R4 h)a,ouBV^"(,> ***;;{ܹ4+"333{  'x$aNG j/((V3fHLL{ :R,򊠰/#h T*Uvv60.Kh4”r EJv*|pAB ~ZjZtg WHlfyV^[KHp[DENupsE9EaL )o@jaK[By%;ٽU K%$x 3a!Hn";@F^!yjd1е۷xIԬݻy|_sK6]]xeL͖Ƙ3 `#KC6sEH!QѰ񂺶&Dxl(oesƫȓ+Ob˟)ltQlz] cD @P~~ˇf aϿP4)׊vsLbC2 w?9 BQ @2߈kTт_9Y~(./J;SJ$M/2tH LjQZF("q ,N*5DRc,(eBJ` LΧP9DXc*,t @DLSЏvF&D\&7IqʇEhTF̙,05\C)ݡ2C6O+g–BR#.0$/%CJ@lr|a\iP7vCBR! ~2 )lZXې/226@fPB!K@gR4/)BR,+*5 _&#y  n A7 .5R3Bi Y~H#,55"AphXAH׳{-Z-9k*4O/c9:OuY={SfG,2,>48+</ޢB8gbsCII.dGd:cg ѣJdŐ7@(/rBXcК˷$DghHV)\ѐi)t3JaW2r/(ǃgU+a%158Az :W!\\ѧ#՚ᵲe;iD!WN QFŘ-x W{I1_WT."1ÊCL0䶇CgKDȟ>J$coܘ:D<Jb8r"JQ6 !Adfč,>S2J2 X_+;. PQ "vFɯ D#'K/`A^(|F,Ib"DN@p>O~1~(q^hG,ŋg-REDMs>QFĩ %*NAҒ\?Lx!S aDADqh~N1ғJH!$]L:CH (.EH7SdD/QݠN~DE@76,u^hH1 J^(Qzճ«`XzN0 gWZBr酢*vpIJD. D{%@. &!TTHz3BD SY+B4a"`R@+"Ð6 ".BV!k,"mmgAT P-#z2Ep(l+܄T`&n R`7k5RTZLCxmDQiڋ !fw:JpR\)r"\Z + .8ABƯB$ j*#/6# N2^" Q 'L1W8B$H14d 8t<ȡYk*Bat@Y~H d/D̀Ebo[xhP ZyuƽG".4QRԧN  0t\< VѨQkSCHHAAs=^!Gj[0ke::Eֈ?@P!A(`1ԍZwG`P[ I@ ش562+RқAȸ7Kp p@җ :;B P9_Aq^R&DRz0_Wq"r<'RCSa"(p SGp@)r";jl'擽2.Lɓa+?MHav <-2fc HjSIDATx{LQ͒X ah(I˝<(mR,Gy?,xE&RٍE4VZl^|m̝k{9ts3;swf3LctZp8IOOph9%&ǺQJRc;m١~8]YRn+NyAO^ִ9Z]S|or|cjx9.wPF))tL}"mܭ ~ɜy(M}P N(}اT1gs LRkbNak܎QQI6g6~] e,ph,QKgx0VDmm ܱ5ڮ9I)N>pV'BT¤0/s +.[5.*NWgwQc{oVF`Jʝ` W@0 :rtei/jn9#Y>~X 6َa>Apðد3lV-A;}q)R9/$\9z PD:ndEI8I =fKI<R.M\4P&}d9 4W$٫\r֤g@Pd鮃 H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_F IDATxY{LT5a 9Q@ P Z1*F4mmlb[ڪW ZA#XJ""-[&@A3ǙonnrlgoopX,>0 KLL={6xdn8رc.eYAr0A֭h4 8  `e)ݹsgXX#4V󮀲,˲wD"X,---K__ eY߿_5kڵK$P~wx<|-,,L.tV{֭$C ^otn.))H$8WUU9ΣGQQQt:嗉 R͛rJT*tsjpĊ ]4M7v(%% Ô,O?MLLDFFfgg&8h4eee===.]rIIIsMJJ\vbHҔ d2\rYJDZZܹsjijj:pT*{SUVVzjۢ(  mܸ(iZw^!ׯ?< ð,P(F# ۷oZ7o999Fp( L&۾}{WWW{{;EQ0`"T*_y啲2%Knܸgucʕ7oެ={uiii$I( Wmmm8+ʨ(0 a]vrr"oFRSSv8+T555%%%w-q\QQQrj\d-߾}bqVaX,c&1 ř ÌVVV>|ð`0hXnW_}]TT$D"A!η#"83Lcv`0844w^zGFF4Z}T g餤$}399e˖{8ǏcjSSS=hvjwT$ǎ޺u k0Iyـg5kJKKOB0Bx뭷2lƍTwy'77pFСJ8Djժ wɲlmmmjjj?fzw%뭫?8rŊ:bZϝ;{I 6m ̙3_~6mڄ\02˓O>YXXR60 gee7ϟl6$Y-))3339~byH 333!yze].xJJ q2V"RUU%i-Z ˲VI믿39s `ddf3T0,..^l|\r%_~j}7Uܸq# rCywFQ4..Nƍϟ(b }ľ>}>_XXq "&&aلpXvIt:IR ɢ0 (j||ky悂2Z}ȑ{E8EQB!lwiT*U* Xu8AVDGG#B4I .a^z< uB˲^x!(h$"##ĉ/^WtbZw3t^,{.l 9{yen{gW`8ȄI"ZI*z^ 0 J}}}fI$ttt`Ha̚c=x yn?>֭[O<N$0ATfBX9(Jgrr8#""&''&áۼ s?А a"bttn(z3g4(0bx;vP(!7::q /$yxLbʽ!ahj)66ޒ7o=SAL&p\.'H8頛zL&۰aCJJ%MKuHNDzԆ ::::;;{=͆V9uTWWk׮qj5 qNs. 0::0sFСC~z*ׯ_P \q\KK ={kf0yyyuuu[lp0`FR-[EҶm`%KL&g2L&(JH{v4M;b/ ;w.L__ߟ)HnH$0555uvvB8Yf8>66끷hryiii[[IuuuZV"ܺukjj޽{۶m8~֭PiǧOƜN'Ӹ8^h"#}̂Nr`PTwB42vҥEuttx<Պ jzP ZV?==qH$fvDq\rr4g(SO^ȑ#6 >SGB EA8ʕ+9y1P8^PPj*vO?|饗I^y-i6wew%x`0Vk P*mmm,b ˛˗/?s&''52[xqaaaOOIR0;;ĉ#//:M$ID<_XAB!#HTUUeqzbcc_}U222`Kmm l6˲999z_H = X-))ˋpwww?TT!a&:|1 㠨Yx}[BBBBB}:tb…Goo/O :d,ͤ`>Bx~xzzH$il6L&BR v 8$ AB@IJJ mmm* N`:r SIEhB_ "[[[b?MիWx|{7?aXC 'GhHT]]ɔJ%IpBX]payyzK PLΟ? +id2P-++ HBZ]]]f3S駟~zΜ95Le}xXX͛C#{p5jjj LJKKSSS/}?jCǏ>3Ր+IENDB`faye-1.4.0/site/site/images/ineda.png000066400000000000000000000062601371104377200174140ustar00rootroot00000000000000PNG  IHDRG3+ pHYsgRiCCPPhotoshop ICC profilexڍkYܠ ^ YH$4">IlmD Hvw[qƥ0{p…+(|!{EܨtWyV_ιs-.t ) s~d\VNNMH.2t0,A&>>xOs` ] *S 4>s@@z&@ڌ;C nD nXNV53 u9@' uV#MJ@?XJjxz7 fDF/9u&g򇚞"Β8xdI?Q_2`9R{OWǏlա|3k.ų0F8GFG2JM!/yo($GYm9H~l_ٯ/f=̂9Q@aZlsߥ M\k01ib1vr/S[D>fPbG Q &[ڽv=jlZ[-n%+$Fŀ؁{^[ SNLq!r[%~ksWsʂ=Oߌɩitnvk[`֚Ŷ]oA[Kps[7Mk˺'WFrmoA^YZRBiT̼*l]V_m4>]i4\p[Eiݎ cHRMms{n3'I RIDATxTTe?s q" 0&fFe).i*HV\kez2]c9mV:Jkmi GEŁAa`f,-2q篙|y{ B3@vKR]S+LX&LN7A=ozZNl@1P}>&5}y}'Wc#ـCTU@ùsHYA 3G{Y1)Fd,+LF꽶Nɓ̭oCdI \n'S&Ofٰa#Y4re˞7hlX;lz/x<ʺ");ws\%^U5G}yra:ڵ9_cC!1,|rS^!l6.}b  6 f4U}ؒGMM 7 疓ʬYLB4!YU̞T\n~Xx9KTWt]/--<$Z"5669saEffشisG%KEBEMo cyyyynKt.\ Ʀ)DnGo3Ee oۅGŌlĶm;O IbM7'NzB%r - Hѣ">!IO[rO !4Mݕ!,aĉSb MHӦ躖@M/wLdFi=pV$,bIL\l,~_0"ルq68))) < :vaa1V' 9.ܘ1ԩӸ8N0FK*${?̙Q5C${f֮]kΈ‘#¯5j$ ;V:u̝u,I48Tz;uuu38cI-୷p҂CSZZlbEƧ)q?]qDR[ӱ8ۅƑ#0MflQ! L}0uD-))].TC'v|ïSkQ^^ΨQ.S!ҏ'ұ'MJgV"|5I+_~"99sy~]r5"u+N\UU;E 6.bpW)HV]@d4Fc-kB! ~ *(.|kW%+r&gw%z=nHNN&*z0N}=]:je ]S톻.$#A ?9Vy1֯6'ʙ6CY̾[V="""xzəf~<̳h42kp9Աxcǣ <nt=α$hJMdEZ;誆`Ŋ̟?Lccc\=(--@:2 :B4^xgL&*26&7tF99]v18Z/؈Œ̆ז2fhVxӧORVVNzd  {.{\|ŀ%O-yyENJ'OIh2r`|Nv領ӽ{=͞={ѣ>|{t>0:OaH`NJJ(ؾ="0ed"""z} =_}5^{Mzm#)!W_]% eɱlmy1Fh[J>`7%~c>O.~Aee%/Yh0 K2r֭o}ź}u?G PKOE2Є@mWz[ ?>x?YY3`)0b21$:Z;vlw0ǿBt#IENDB`faye-1.4.0/site/site/images/medeo.png000066400000000000000000000032771371104377200174320ustar00rootroot00000000000000PNG  IHDR\FsRGBbKGD̿ pHYs  tIME 6 BݮCIDATX՘p?{+(D# ڪС"`)T{ ܅C˯V]H.0Ik)-m*C۩(HA:tWL.w\rgjyoo>~},^$G=ᲜGYgiyG^yM1=ByTAQq5ȨKM݌Cv>3fU_oPomƠ<~~șkY[?~ԃxAt2Gɳ {!2yPN*53vƎ^nrRd/ &i.Bi"j(]g(y SgB4&J!-De5Jph Bvb4^_EwsF ]YM.~lRhC|z`~PDגS%cpb.#7~*+9jj,)653woBd\bm6]jgP/Y,mڭb:vosX+{VYYl}ΰ^bW۱ k1g#& KqAUAziJP06/blSlo+@IjSG/=BFkmmvBCpe$I;ԭZd$ԽyIf\ n{8(`vb2p, @ownY%ܠ}eU jZۇ+=/zǧO%vT!xnMYvԼ(pJ59_S#n`iaCtCw8ЋVfLtǻ.fz$BsbnY!F{kL`bY\UljZ2_&d—:ONtW8}Riߤ(s=zts~?:v斍ezDfНz}G9١zpF ^ףR-ܥx8N-flrk2L.%ݜvhL%ɇ^_՜'7;$)[2em@ypPU}|U \.X5h!IENDB`faye-1.4.0/site/site/images/myspace.png000066400000000000000000000201601371104377200177700ustar00rootroot00000000000000PNG  IHDR9< pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_FIDATx\yTS%H% (@dj}ӾʫS}:h[ֱ}֑Z_ Z *2F ! CI{o]_{Z'瞜{g2 3gŋhkk13fӳgёLqQ@tz= p=T yg'l6FȨ(Ņ4RDkY&F`"<<vvvZ<:, zHHLf|Ǯ.޺V &M2>QVR6H$Bhh(Bƍ3D^رt8", }}}ynZXIppp^jܖJQTT(Jvvp'$P]U:| iӈzCрp Ȩ(؜\.ǭ)/L&C_?;;x LXVU*zzz@\]]p(}J%oۆb1"wdj3 ذnLmBܵ \:x.U \0D#AB۟b!.!/_777h4[RS;;;lظsHmS# =ɓ'ŦC&cmJطw/ E=y[YCAZV&%K4u*݋C0rHs7_<1΁ "2 .D@@sVsg?qGGGLMNK 헽ܴiӦG=ӑ?;.^Dii)\\\K~40z=b9y$Hmy5 gϜݻz=Ɂ?232R~`sع};߷:B@vv6\?$7}!Zd2ܵ<`ϟIbV-2\ 'NgJ|12kuVj@,fޡ%Ũ$-G8r0hT*Q\TM7ӦzMO$CKs39z!v}]:xp \RR3gbcc)SAA::: XƆ I6Eo?K_~JRb6E;QY%Ξ=KyQQ>3(z_ƍ׵۶nRdؽkLq-/җ$権;LA}Pp&f0,!tƚ bBxh4{Fb3V?\6111 q5;ԿĹ!iTBBӃ|HJOqqq$>}:ҒF */̛)IIAMu5`ܹxkaҸ"' .ׯS#G1cƐb}{ 8"ӧO7z=*++q!+ ---@%XV|'C"99 B2\&i8ufgq9HLL= FR|{8r_ݻ8wXx1>cN;oǺ.`0@"4s99^+5a⍂70cLS[rr26֯ٳqqoH}ﵴ@&! Ӧݨ nnFzJ?pq䠤ĪDpzMxyyB$MWLДJ%Ξ>W_w,\6K`sϑ'3p0- /_9cGRѣz2OϞ=w?8^_, I]\\q&Gj kqŻps*@oo/6(mdw0ȲfƄ '''L,E͆z$>jYn`$4\mm-6n؀Wt,t($ӦO'QӃ;][[K pa3X Z,]~0OϞMR[s3 VI|1`u:Zχ eX 3-ʒ%KL-X&AazH: Hm$&M6enwcvHۿ7_QȑC~%c&?KaS:~<:caN&(8r9σ0BV@ <=M1|Hh4iNp>ۇV0.]lHp*Áe1DC3OIǏPq@9===Mt-..WPVdff"33?<FaӅTh6 ŠK`Ho!O@` .q-Pd+WŢ< bJRΞ9C 3g`^Ѕ cb6q" QSS`L0.bĭ` mFq ˗/C*3b @FcBjp*! Ǐ o\ZS?b%;6A\&#Cr@ s0 u.)fT(c)JF*朝ocߞ=4-͟)II;:_(}DϸXvvvZ^@`ս ((QPP`j++)Jݪ*FD 氷T$OܜH$Ți#\m[bƍec:::jMւuTFm%}H1C_ENtC3^tv')+Aprs1wyOa[69|7l,ܼq{MHpVV>HV4mlCSގiBݽ ,:QZ{q8<m``6ǷǏ\!-@ P((D[[*;v7sE:jݘ"-d5-ֵgRVIq1i?_'N>PYQ{6 W]D8YI$G]m-kq!{wr>>!Fdfd4|9Bs)LKNljj–>BeECEq_?v؁|";s`ۖ-HOǎqg zlx[DNYdىv4S,B,b|TWWKЇtUP9s碪rdkh8JGRk ' H$/zU*޺ׯѣmfRq:#"09&NNNhp#?555內f&ɸEQPPx Vb*(2KJ$W*ؾu+&!.>P ֭[(-)A` R7oƆ1.4SS'EZ 1sLcoN%%%OFww7筷@Y67}Cu1&:͂ tw3̚5Qj++*p5JYp& nd,R0Vƍ6+ Kihxx}r({SShdz@xOsxcJHhP\TD[gFԐzi-9`^h4 T(&8>FfwV&osPB̜5n X,p!BԞ|sHNarlU̮9E"v-%[l-˲eRaDXu?rB? 6o&%薾 "mA0 zrr2ּ|>~DV#k\N{ejHƹV;{6/ﰳ3]...vQQ4Dz3fs HEY, pb٨zTՈ֑#q.+ bcPl۱;Xt)"} `0`` "ضc~ĥKHhtk6NݝT WS]nSA$1*FAee%4j50xwݝj’dNEHWWWgΟgvږXYMeTT(++C]]P(xې#"hkyظy3^T*EYI d2tz=\ A d2; ̶pŦCI&`0B\BԠS.7= HȨ(nbYYO&NX,'뷾p߄w$չaӖWð \iYŬ>aaRne? cXcaYN}X۠QjL ydTJIG0'5ل_~F#:)鷂ÁZzs4jtɸIENDB`faye-1.4.0/site/site/images/nokia_mix_party.png000066400000000000000000000054361371104377200215350ustar00rootroot00000000000000PNG  IHDR*sRGBbKGD pHYs  tIME ,D6 IDATx{UU?Ӏ&we8V.45L+Dhyoc)hu/c_1FC fi -| w"K"Xw>|gZ{}DkUjΩǁT\Qax'Uau @n٪,`%Fj >Џ87/ '$@`Wx`>3^9몺r'8G7{}[\^J/O׊HKD>OVixv vnLM oVf/[.RݏC4U}44s-y;]YKifg 5Hf`s!6r8fak5_XC }7$z+m'", n,Ps"r>GK"$2UU.U}5lk&iN. 8wq;0VD*0LǓb`ud>Ys|Ĉ~0&>5TC~ S^O Ӈ [=R,W Y^U""gy U#"ӝ8^HU)jS`P~n oURj,lmi ֦`HkbSCUI&"76?醔)g[00SUƔʑu+ayf~KU5S_*'"8ǹC",(p\ݡ+ {9`4V`@DVUJ0-@'Wi^^^DqHХ sŴzr1׶Qny ޏsIѣ " ӗpCƎ_e74dw;yC1Kd99 =}"! "2*$"0m1`|8t$$Lqnpﰪvj>_ 1SUnWURKzH}YRmET`̺(j>4/Qj]fB6qq-%j?UunKi܇_}3 "2 Ed,psj-z'x]xel0gR$3:e%?j͜*nBmÝ,@'b${`s|JvE2* ?L|K?⃎2[*bQaJJ]Qbވdj2")]dWjLFSUNd,w6"7dыEd.p RzС NnjvκDgn61"J6cڨd|F3&b$w:9`9a67xѸJM<>k8~AUkX[.Ox7QDPoL˾6k>-Sse~R\)6SvfRI<""KC6s}fwuGE䂄UCSwZd+[Fx=J@3#>EC4O6b6&1.LF H6puT+}_{I"̅0 A?v:eZ{Sdi$wol=)M!f3ܗ 8kjQbh/1!qU)`T(Dxí"# w!KD _$=LZĴ>HԒ9d&VfhfL'ن嘊hW &>aEx\%6&n?sNқk o}ߵ:9fYnQv/RVR]VrDQFӄ9 5 y>^5pI$B>!" |[d?<ꋞ9Ox/̯ w "r>:wgtfzeYD|S.EbkDH&|l2F(bd"= Lvɮ FzSu+ײBS:!؁_xBvZXT,W挌&'Gݏ)"v6+Հd>FB5IENDB`faye-1.4.0/site/site/images/pathient.png000066400000000000000000000125301371104377200201450ustar00rootroot00000000000000PNG  IHDR<2K pHYs,I,IU OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_F IDATxWKlǙb;vibHiM"U)iJ"$JB;X]B,P xX6ШRb7M8NR{o{yJ ̜;:;;@!l~Em(oq{W=z躞A,$I}7mq0 e1 m[Q˲PUնmiǩ( 4 ^ap}=a EQ|W^,%m6˲Ahv5 1 fSUZaa4MC,K4㚦z{hVUUu((_$ 1 CQ,8β,ta`z(JٜfZ-L8p㸝b)*J4L$vM(i>"=Jݏ4wNִv].ͦ ir@ 8IJAx<@ժx<.Iq à(*bv!L?|C EQH˥ifYΝ;4M[UQj(\.IFFF?.,ˍFðH$ra˲Jit]w8ey^ ezqw8pvKR `fppyYIj,OOOgٽ=EQ(P"W~rNgBr O$MN^<_* Jae_ViZ^WŞKRp8 rZ6<_4?r{|p…O>D噙x?_|qnnb[Áa4BN}ŏ?8J]v{, o߻wllllmm;AضfF4H^x?K._}եK&''ݻw/^O~ȑg,'O/c,ᇉDBuXt/loovU=өTaA)z=ZB84l, 84MÎGL&s9<_,a p: >}z˲,y?~u?d2y98XZZ?xEqmmmyy9ꫡP(J%s#GrQnw^wǎ7EqlllhhH^xavvvkkK$I'\. NOOC___O$gΜq:n. X Nsssv[u0Lܧ_c߼aCaa.0~;zϟ8qzkٿ!ȿ0 @Bſ IENDB`faye-1.4.0/site/site/images/podio.png000066400000000000000000000101171371104377200174420ustar00rootroot00000000000000PNG  IHDRqv pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_FzIDATxZAH*Q>Fd1@I0Bƅ…A"Qi.BUR0H( I!"$W >J޼w}{Lt]쿌U&n0l4zT*U µgٷjn:aJR*J$Zh, jvkkkv]*_.sk9aJ0L&Y44h$Ɔpp7n[RLKRDl(p8:==F,-l+++}Ȋt V+ EQ NqCaEQ>o8LONN2Jn.dt:H$JG@@,P(4p ðz~||j*VUd2 = H$INǍb1Lvu:]* ll5JDVh Xt a(JrA?Bw[[[ufJeX~ND">H@c'b488OzH$z}gDQ(4MsYQT33LVwv;EQxRtihcAl6٬2<<<wR\.0C\]]n:W ef_膽q(CwјT*$Og,CD&FQ |(z=taKj4:Dʆ9᪠3RX3\W$//[p`ta8LPXVc a6v{\6q4L&cɿӟD<y<m;j[((cpSSSC-|fp8z[P+M(.yB_zĪ4L (L_onffFr>^!^7 rI]\\&%7C5wktjWjr Y7ht:Qyha|zTGL&gSLa),//ConnW(%XfJ/V|t_qjR{E=bxxxwvv%6ZM*RS@UQp\\\q3,--􆽪SQqbaf\w-k)r) s(bt߃͔Jy{zybJA/ǽ^ 4C I4MDN;88J$R=F(AGGGX,Nr>v HDh68Q.OOOZP(7+ ÔJ=V4*RMg{+ثT)\;\IENDB`faye-1.4.0/site/site/images/xydo.png000066400000000000000000000141221371104377200173130ustar00rootroot00000000000000PNG  IHDRf] pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_F }IDATxY[LT?=3gngBkErK@/)D ZҴ/MKM&5AQ@DSS)a93s?GL÷ffg^Z $ȲWqY[DQ( EQ0$Wx0LYh4_ M<ϲ,t: CAE+ 2<<<>>pDFFyy bAt^^^zz:\r:,t:ݦ<ϓ$4??RdYq<88844444EaxH)aT*nr,WX$ J'$$hU8sn6$\. V˲l{{$IKQ_5veZ1 CQ˖-~*h/.+33333sՓGDD A}NȲ(bMMǏf$I<ϗw"##9SYO\. $I|weee+mmllٳv'NDEE\.ӧO[[[#RRR&ʔ[nɲ }Z 1!LpuBDDDv͆8cwwwCC)˕gϞvs:iiiڲY]XXhiix<A$YWWe;?fL&Ya[zI/rrrrzzzyy^7ׯ_ tM6%&&Ba0\^EQw}7((Kq<66 QQ$ihheYPQQan{k/hO:e2X0L۷o$IB=zUZVٳO> `qFsͶ dرc͒$A(jdd͛"(:|Ph4+ ѣGPq AP%%%&nKJILL,,,8N$Ftww+v鴴wyg-%22>HT{{8BtOe;::]3 0qqq@7 Pcǎ}:.Az{{eh4*'''[ANw]%hnX{ʲRZ-fyDGGFTTT4M'%%# ^dddn%\.h]]]p{_J477!z8nn;??W_}!MzΝ;q흜T<ϛLҵ$** Rnʙǥà8e!]2Eq8 hTooJ$iqq(<<,ZR t:=d{Z 0eYݾ&d<ӳGS*GEEɲ"I̒{Z* \a(2c(bdY^ׯ )0q)cn߾ KJJ^IAXFVy~ FaEQT*PIV4AQT]vÇ/V+++ MeK`0Xl,C6~W -@fZ/^Y;EQ111GR<_^^%G~;w@+aH$Ѩ1LZVE,GQj5L)2+**aaPRRr TUU)222RYY "0QQQEEEiell ~A|||`kXqZ ===0: U w^ss3. (((&&&77?,=(F#7bY3%Iڼy3 oCJ׹gss3EQ^qddr/]DLܷo\ʊ[\\BĩBtnnnBB¿Ԍw(%$IRuuu/(O|pPPL꯾a:gϞTRѣG2 (*ONNV\r ô}娨 Inܸ1:: Snɓ$vͶ0l1jQ].ב#GRSSRVVÐ*8iXuuuvv(JOOV@+**tǙLUB&>IwaaҥK~_~ihhr޽СCjoo{ FVCx%%%?~|޽{Dž Aj$Ivtttvvn۶l6kZ1;;kZF#l%8)Na\.u+WhZ ~駨|e;w=h޴iSiiz3 3 1/,jzH}&''EQ `6EQ>;;xYVIozWȑ#E8.Iғ'O& رvxOOOߺu`ifg(Ρ\JJJppp}}}kkcVM$I۩+:v6Ȕ~(9ۣ_3>>C$UCk.x0t:am5ggl¹IENDB`faye-1.4.0/site/site/javascripts/000077500000000000000000000000001371104377200167065ustar00rootroot00000000000000faye-1.4.0/site/site/javascripts/analytics.js000066400000000000000000000006351371104377200212370ustar00rootroot00000000000000var _gaq = _gaq || []; _gaq.push(['_setAccount', 'UA-873493-8']); _gaq.push(['_trackPageview']); (function() { var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); })(); faye-1.4.0/site/site/javascripts/prettify.js000066400000000000000000001520221371104377200211140ustar00rootroot00000000000000// Copyright (C) 2006 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /** * @fileoverview * some functions for browser-side pretty printing of code contained in html. * *

    * For a fairly comprehensive set of languages see the * README * file that came with this source. At a minimum, the lexer should work on a * number of languages including C and friends, Java, Python, Bash, SQL, HTML, * XML, CSS, Javascript, and Makefiles. It works passably on Ruby, PHP and Awk * and a subset of Perl, but, because of commenting conventions, doesn't work on * Smalltalk, Lisp-like, or CAML-like languages without an explicit lang class. *

    * Usage:

      *
    1. include this source file in an html page via * {@code } *
    2. define style rules. See the example page for examples. *
    3. mark the {@code
      } and {@code } tags in your source with
       *    {@code class=prettyprint.}
       *    You can also use the (html deprecated) {@code } tag, but the pretty
       *    printer needs to do more substantial DOM manipulations to support that, so
       *    some css styles may not be preserved.
       * </ol>
       * That's it.  I wanted to keep the API as simple as possible, so there's no
       * need to specify which language the code is in, but if you wish, you can add
       * another class to the {@code <pre>} or {@code <code>} element to specify the
       * language, as in {@code <pre class="prettyprint lang-java">}.  Any class that
       * starts with "lang-" followed by a file extension, specifies the file type.
       * See the "lang-*.js" files in this directory for code that implements
       * per-language file handlers.
       * <p>
       * Change log:<br>
       * cbeust, 2006/08/22
       * <blockquote>
       *   Java annotations (start with "@") are now captured as literals ("lit")
       * </blockquote>
       * @requires console
       */
      
      // JSLint declarations
      /*global console, document, navigator, setTimeout, window */
      
      /**
       * Split {@code prettyPrint} into multiple timeouts so as not to interfere with
       * UI events.
       * If set to {@code false}, {@code prettyPrint()} is synchronous.
       */
      window['PR_SHOULD_USE_CONTINUATION'] = true;
      
      /** the number of characters between tab columns */
      window['PR_TAB_WIDTH'] = 8;
      
      /** Contains functions for creating and registering new language handlers.
        * @type {Object}
        */
      window['PR']
      
      /** Pretty print a chunk of code.
        *
        * @param {string} sourceCodeHtml code as html
        * @return {string} code as html, but prettier
        */
        = window['prettyPrintOne']
      /** Find all the {@code <pre>} and {@code <code>} tags in the DOM with
        * {@code class=prettyprint} and prettify them.
        * @param {Function?} opt_whenDone if specified, called when the last entry
        *     has been finished.
        */
        = window['prettyPrint'] = void 0;
      
      
      (function () {
        // Keyword lists for various languages.
        var FLOW_CONTROL_KEYWORDS =
            "break continue do else for if return while ";
        var C_KEYWORDS = FLOW_CONTROL_KEYWORDS + "auto case char const default " +
            "double enum extern float goto int long register short signed sizeof " +
            "static struct switch typedef union unsigned void volatile ";
        var COMMON_KEYWORDS = C_KEYWORDS + "catch class delete false import " +
            "new operator private protected public this throw true try typeof ";
        var CPP_KEYWORDS = COMMON_KEYWORDS + "alignof align_union asm axiom bool " +
            "concept concept_map const_cast constexpr decltype " +
            "dynamic_cast explicit export friend inline late_check " +
            "mutable namespace nullptr reinterpret_cast static_assert static_cast " +
            "template typeid typename using virtual where ";
        var JAVA_KEYWORDS = COMMON_KEYWORDS +
            "abstract boolean byte extends final finally implements import " +
            "instanceof null native package strictfp super synchronized throws " +
            "transient ";
        var CSHARP_KEYWORDS = JAVA_KEYWORDS +
            "as base by checked decimal delegate descending dynamic event " +
            "fixed foreach from group implicit in interface internal into is lock " +
            "object out override orderby params partial readonly ref sbyte sealed " +
            "stackalloc string select uint ulong unchecked unsafe ushort var ";
        var COFFEE_KEYWORDS = "all and by catch class else extends false finally " +
            "for if in is isnt loop new no not null of off on or return super then " +
            "true try unless until when while yes ";
        var JSCRIPT_KEYWORDS = COMMON_KEYWORDS +
            "debugger eval export function get null set undefined var with " +
            "Infinity NaN ";
        var PERL_KEYWORDS = "caller delete die do dump elsif eval exit foreach for " +
            "goto if import last local my next no our print package redo require " +
            "sub undef unless until use wantarray while BEGIN END ";
        var PYTHON_KEYWORDS = FLOW_CONTROL_KEYWORDS + "and as assert class def del " +
            "elif except exec finally from global import in is lambda " +
            "nonlocal not or pass print raise try with yield " +
            "False True None ";
        var RUBY_KEYWORDS = FLOW_CONTROL_KEYWORDS + "alias and begin case class def" +
            " defined elsif end ensure false in module next nil not or redo rescue " +
            "retry self super then true undef unless until when yield BEGIN END ";
        var SH_KEYWORDS = FLOW_CONTROL_KEYWORDS + "case done elif esac eval fi " +
            "function in local set then until ";
        var ALL_KEYWORDS = (
            CPP_KEYWORDS + CSHARP_KEYWORDS + JSCRIPT_KEYWORDS + PERL_KEYWORDS +
            PYTHON_KEYWORDS + RUBY_KEYWORDS + SH_KEYWORDS);
        var C_TYPES = /^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;
      
        // token style names.  correspond to css classes
        /** token style for a string literal */
        var PR_STRING = 'str';
        /** token style for a keyword */
        var PR_KEYWORD = 'kwd';
        /** token style for a comment */
        var PR_COMMENT = 'com';
        /** token style for a type */
        var PR_TYPE = 'typ';
        /** token style for a literal value.  e.g. 1, null, true. */
        var PR_LITERAL = 'lit';
        /** token style for a punctuation string. */
        var PR_PUNCTUATION = 'pun';
        /** token style for a punctuation string. */
        var PR_PLAIN = 'pln';
      
        /** token style for an sgml tag. */
        var PR_TAG = 'tag';
        /** token style for a markup declaration such as a DOCTYPE. */
        var PR_DECLARATION = 'dec';
        /** token style for embedded source. */
        var PR_SOURCE = 'src';
        /** token style for an sgml attribute name. */
        var PR_ATTRIB_NAME = 'atn';
        /** token style for an sgml attribute value. */
        var PR_ATTRIB_VALUE = 'atv';
      
        /**
         * A class that indicates a section of markup that is not code, e.g. to allow
         * embedding of line numbers within code listings.
         */
        var PR_NOCODE = 'nocode';
      
        /** A set of tokens that can precede a regular expression literal in
          * javascript.
          * http://www.mozilla.org/js/language/js20/rationale/syntax.html has the full
          * list, but I've removed ones that might be problematic when seen in
          * languages that don't support regular expression literals.
          *
          * <p>Specifically, I've removed any keywords that can't precede a regexp
          * literal in a syntactically legal javascript program, and I've removed the
          * "in" keyword since it's not a keyword in many languages, and might be used
          * as a count of inches.
          *
          * <p>The link a above does not accurately describe EcmaScript rules since
          * it fails to distinguish between (a=++/b/i) and (a++/b/i) but it works
          * very well in practice.
          *
          * @private
          */
        var REGEXP_PRECEDER_PATTERN = function () {
            var preceders = [
                "!", "!=", "!==", "#", "%", "%=", "&", "&&", "&&=",
                "&=", "(", "*", "*=", /* "+", */ "+=", ",", /* "-", */ "-=",
                "->", /*".", "..", "...", handled below */ "/", "/=", ":", "::", ";",
                "<", "<<", "<<=", "<=", "=", "==", "===", ">",
                ">=", ">>", ">>=", ">>>", ">>>=", "?", "@", "[",
                "^", "^=", "^^", "^^=", "{", "|", "|=", "||",
                "||=", "~" /* handles =~ and !~ */,
                "break", "case", "continue", "delete",
                "do", "else", "finally", "instanceof",
                "return", "throw", "try", "typeof"
                ];
            var pattern = '(?:^^|[+-]';
            for (var i = 0; i < preceders.length; ++i) {
              pattern += '|' + preceders[i].replace(/([^=<>:&a-z])/g, '\\$1');
            }
            pattern += ')\\s*';  // matches at end, and matches empty string
            return pattern;
            // CAVEAT: this does not properly handle the case where a regular
            // expression immediately follows another since a regular expression may
            // have flags for case-sensitivity and the like.  Having regexp tokens
            // adjacent is not valid in any language I'm aware of, so I'm punting.
            // TODO: maybe style special characters inside a regexp as punctuation.
          }();
      
        
        /**
         * Given a group of {@link RegExp}s, returns a {@code RegExp} that globally
         * matches the union of the sets of strings matched by the input RegExp.
         * Since it matches globally, if the input strings have a start-of-input
         * anchor (/^.../), it is ignored for the purposes of unioning.
         * @param {Array.<RegExp>} regexs non multiline, non-global regexs.
         * @return {RegExp} a global regex.
         */
        function combinePrefixPatterns(regexs) {
          var capturedGroupIndex = 0;
        
          var needToFoldCase = false;
          var ignoreCase = false;
          for (var i = 0, n = regexs.length; i < n; ++i) {
            var regex = regexs[i];
            if (regex.ignoreCase) {
              ignoreCase = true;
            } else if (/[a-z]/i.test(regex.source.replace(
                           /\\u[0-9a-f]{4}|\\x[0-9a-f]{2}|\\[^ux]/gi, ''))) {
              needToFoldCase = true;
              ignoreCase = false;
              break;
            }
          }
        
          function decodeEscape(charsetPart) {
            if (charsetPart.charAt(0) !== '\\') { return charsetPart.charCodeAt(0); }
            switch (charsetPart.charAt(1)) {
              case 'b': return 8;
              case 't': return 9;
              case 'n': return 0xa;
              case 'v': return 0xb;
              case 'f': return 0xc;
              case 'r': return 0xd;
              case 'u': case 'x':
                return parseInt(charsetPart.substring(2), 16)
                    || charsetPart.charCodeAt(1);
              case '0': case '1': case '2': case '3': case '4':
              case '5': case '6': case '7':
                return parseInt(charsetPart.substring(1), 8);
              default: return charsetPart.charCodeAt(1);
            }
          }
        
          function encodeEscape(charCode) {
            if (charCode < 0x20) {
              return (charCode < 0x10 ? '\\x0' : '\\x') + charCode.toString(16);
            }
            var ch = String.fromCharCode(charCode);
            if (ch === '\\' || ch === '-' || ch === '[' || ch === ']') {
              ch = '\\' + ch;
            }
            return ch;
          }
        
          function caseFoldCharset(charSet) {
            var charsetParts = charSet.substring(1, charSet.length - 1).match(
                new RegExp(
                    '\\\\u[0-9A-Fa-f]{4}'
                    + '|\\\\x[0-9A-Fa-f]{2}'
                    + '|\\\\[0-3][0-7]{0,2}'
                    + '|\\\\[0-7]{1,2}'
                    + '|\\\\[\\s\\S]'
                    + '|-'
                    + '|[^-\\\\]',
                    'g'));
            var groups = [];
            var ranges = [];
            var inverse = charsetParts[0] === '^';
            for (var i = inverse ? 1 : 0, n = charsetParts.length; i < n; ++i) {
              var p = charsetParts[i];
              switch (p) {
                case '\\B': case '\\b':
                case '\\D': case '\\d':
                case '\\S': case '\\s':
                case '\\W': case '\\w':
                  groups.push(p);
                  continue;
              }
              var start = decodeEscape(p);
              var end;
              if (i + 2 < n && '-' === charsetParts[i + 1]) {
                end = decodeEscape(charsetParts[i + 2]);
                i += 2;
              } else {
                end = start;
              }
              ranges.push([start, end]);
              // If the range might intersect letters, then expand it.
              if (!(end < 65 || start > 122)) {
                if (!(end < 65 || start > 90)) {
                  ranges.push([Math.max(65, start) | 32, Math.min(end, 90) | 32]);
                }
                if (!(end < 97 || start > 122)) {
                  ranges.push([Math.max(97, start) & ~32, Math.min(end, 122) & ~32]);
                }
              }
            }
        
            // [[1, 10], [3, 4], [8, 12], [14, 14], [16, 16], [17, 17]]
            // -> [[1, 12], [14, 14], [16, 17]]
            ranges.sort(function (a, b) { return (a[0] - b[0]) || (b[1]  - a[1]); });
            var consolidatedRanges = [];
            var lastRange = [NaN, NaN];
            for (var i = 0; i < ranges.length; ++i) {
              var range = ranges[i];
              if (range[0] <= lastRange[1] + 1) {
                lastRange[1] = Math.max(lastRange[1], range[1]);
              } else {
                consolidatedRanges.push(lastRange = range);
              }
            }
        
            var out = ['['];
            if (inverse) { out.push('^'); }
            out.push.apply(out, groups);
            for (var i = 0; i < consolidatedRanges.length; ++i) {
              var range = consolidatedRanges[i];
              out.push(encodeEscape(range[0]));
              if (range[1] > range[0]) {
                if (range[1] + 1 > range[0]) { out.push('-'); }
                out.push(encodeEscape(range[1]));
              }
            }
            out.push(']');
            return out.join('');
          }
        
          function allowAnywhereFoldCaseAndRenumberGroups(regex) {
            // Split into character sets, escape sequences, punctuation strings
            // like ('(', '(?:', ')', '^'), and runs of characters that do not
            // include any of the above.
            var parts = regex.source.match(
                new RegExp(
                    '(?:'
                    + '\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]'  // a character set
                    + '|\\\\u[A-Fa-f0-9]{4}'  // a unicode escape
                    + '|\\\\x[A-Fa-f0-9]{2}'  // a hex escape
                    + '|\\\\[0-9]+'  // a back-reference or octal escape
                    + '|\\\\[^ux0-9]'  // other escape sequence
                    + '|\\(\\?[:!=]'  // start of a non-capturing group
                    + '|[\\(\\)\\^]'  // start/emd of a group, or line start
                    + '|[^\\x5B\\x5C\\(\\)\\^]+'  // run of other characters
                    + ')',
                    'g'));
            var n = parts.length;
        
            // Maps captured group numbers to the number they will occupy in
            // the output or to -1 if that has not been determined, or to
            // undefined if they need not be capturing in the output.
            var capturedGroups = [];
        
            // Walk over and identify back references to build the capturedGroups
            // mapping.
            for (var i = 0, groupIndex = 0; i < n; ++i) {
              var p = parts[i];
              if (p === '(') {
                // groups are 1-indexed, so max group index is count of '('
                ++groupIndex;
              } else if ('\\' === p.charAt(0)) {
                var decimalValue = +p.substring(1);
                if (decimalValue && decimalValue <= groupIndex) {
                  capturedGroups[decimalValue] = -1;
                }
              }
            }
        
            // Renumber groups and reduce capturing groups to non-capturing groups
            // where possible.
            for (var i = 1; i < capturedGroups.length; ++i) {
              if (-1 === capturedGroups[i]) {
                capturedGroups[i] = ++capturedGroupIndex;
              }
            }
            for (var i = 0, groupIndex = 0; i < n; ++i) {
              var p = parts[i];
              if (p === '(') {
                ++groupIndex;
                if (capturedGroups[groupIndex] === undefined) {
                  parts[i] = '(?:';
                }
              } else if ('\\' === p.charAt(0)) {
                var decimalValue = +p.substring(1);
                if (decimalValue && decimalValue <= groupIndex) {
                  parts[i] = '\\' + capturedGroups[groupIndex];
                }
              }
            }
        
            // Remove any prefix anchors so that the output will match anywhere.
            // ^^ really does mean an anchored match though.
            for (var i = 0, groupIndex = 0; i < n; ++i) {
              if ('^' === parts[i] && '^' !== parts[i + 1]) { parts[i] = ''; }
            }
        
            // Expand letters to groups to handle mixing of case-sensitive and
            // case-insensitive patterns if necessary.
            if (regex.ignoreCase && needToFoldCase) {
              for (var i = 0; i < n; ++i) {
                var p = parts[i];
                var ch0 = p.charAt(0);
                if (p.length >= 2 && ch0 === '[') {
                  parts[i] = caseFoldCharset(p);
                } else if (ch0 !== '\\') {
                  // TODO: handle letters in numeric escapes.
                  parts[i] = p.replace(
                      /[a-zA-Z]/g,
                      function (ch) {
                        var cc = ch.charCodeAt(0);
                        return '[' + String.fromCharCode(cc & ~32, cc | 32) + ']';
                      });
                }
              }
            }
        
            return parts.join('');
          }
        
          var rewritten = [];
          for (var i = 0, n = regexs.length; i < n; ++i) {
            var regex = regexs[i];
            if (regex.global || regex.multiline) { throw new Error('' + regex); }
            rewritten.push(
                '(?:' + allowAnywhereFoldCaseAndRenumberGroups(regex) + ')');
          }
        
          return new RegExp(rewritten.join('|'), ignoreCase ? 'gi' : 'g');
        }
      
      
        /**
         * Split markup into a string of source code and an array mapping ranges in
         * that string to the text nodes in which they appear.
         *
         * <p>
         * The HTML DOM structure:</p>
         * <pre>
         * (Element   "p"
         *   (Element "b"
         *     (Text  "print "))       ; #1
         *   (Text    "'Hello '")      ; #2
         *   (Element "br")            ; #3
         *   (Text    "  + 'World';")) ; #4
         * </pre>
         * <p>
         * corresponds to the HTML
         * {@code <p><b>print </b>'Hello '<br>  + 'World';</p>}.</p>
         *
         * <p>
         * It will produce the output:</p>
         * <pre>
         * {
         *   source: "print 'Hello '\n  + 'World';",
         *   //                 1         2
         *   //       012345678901234 5678901234567
         *   spans: [0, #1, 6, #2, 14, #3, 15, #4]
         * }
         * </pre>
         * <p>
         * where #1 is a reference to the {@code "print "} text node above, and so
         * on for the other text nodes.
         * </p>
         *
         * <p>
         * The {@code} spans array is an array of pairs.  Even elements are the start
         * indices of substrings, and odd elements are the text nodes (or BR elements)
         * that contain the text for those substrings.
         * Substrings continue until the next index or the end of the source.
         * </p>
         *
         * @param {Node} node an HTML DOM subtree containing source-code.
         * @return {Object} source code and the text nodes in which they occur.
         */
        function extractSourceSpans(node) {
          var nocode = /(?:^|\s)nocode(?:\s|$)/;
        
          var chunks = [];
          var length = 0;
          var spans = [];
          var k = 0;
        
          var whitespace;
          if (node.currentStyle) {
            whitespace = node.currentStyle.whiteSpace;
          } else if (window.getComputedStyle) {
            whitespace = document.defaultView.getComputedStyle(node, null)
                .getPropertyValue('white-space');
          }
          var isPreformatted = whitespace && 'pre' === whitespace.substring(0, 3);
        
          function walk(node) {
            switch (node.nodeType) {
              case 1:  // Element
                if (nocode.test(node.className)) { return; }
                for (var child = node.firstChild; child; child = child.nextSibling) {
                  walk(child);
                }
                var nodeName = node.nodeName;
                if ('BR' === nodeName || 'LI' === nodeName) {
                  chunks[k] = '\n';
                  spans[k << 1] = length++;
                  spans[(k++ << 1) | 1] = node;
                }
                break;
              case 3: case 4:  // Text
                var text = node.nodeValue;
                if (text.length) {
                  if (!isPreformatted) {
                    text = text.replace(/[ \t\r\n]+/g, ' ');
                  } else {
                    text = text.replace(/\r\n?/g, '\n');  // Normalize newlines.
                  }
                  // TODO: handle tabs here?
                  chunks[k] = text;
                  spans[k << 1] = length;
                  length += text.length;
                  spans[(k++ << 1) | 1] = node;
                }
                break;
            }
          }
        
          walk(node);
        
          return {
            source: chunks.join('').replace(/\n$/, ''),
            spans: spans
          };
        }
      
      
        /**
         * Apply the given language handler to sourceCode and add the resulting
         * decorations to out.
         * @param {number} basePos the index of sourceCode within the chunk of source
         *    whose decorations are already present on out.
         */
        function appendDecorations(basePos, sourceCode, langHandler, out) {
          if (!sourceCode) { return; }
          var job = {
            source: sourceCode,
            basePos: basePos
          };
          langHandler(job);
          out.push.apply(out, job.decorations);
        }
      
        /** Given triples of [style, pattern, context] returns a lexing function,
          * The lexing function interprets the patterns to find token boundaries and
          * returns a decoration list of the form
          * [index_0, style_0, index_1, style_1, ..., index_n, style_n]
          * where index_n is an index into the sourceCode, and style_n is a style
          * constant like PR_PLAIN.  index_n-1 <= index_n, and style_n-1 applies to
          * all characters in sourceCode[index_n-1:index_n].
          *
          * The stylePatterns is a list whose elements have the form
          * [style : string, pattern : RegExp, DEPRECATED, shortcut : string].
          *
          * Style is a style constant like PR_PLAIN, or can be a string of the
          * form 'lang-FOO', where FOO is a language extension describing the
          * language of the portion of the token in $1 after pattern executes.
          * E.g., if style is 'lang-lisp', and group 1 contains the text
          * '(hello (world))', then that portion of the token will be passed to the
          * registered lisp handler for formatting.
          * The text before and after group 1 will be restyled using this decorator
          * so decorators should take care that this doesn't result in infinite
          * recursion.  For example, the HTML lexer rule for SCRIPT elements looks
          * something like ['lang-js', /<[s]cript>(.+?)<\/script>/].  This may match
          * '<script>foo()<\/script>', which would cause the current decorator to
          * be called with '<script>' which would not match the same rule since
          * group 1 must not be empty, so it would be instead styled as PR_TAG by
          * the generic tag rule.  The handler registered for the 'js' extension would
          * then be called with 'foo()', and finally, the current decorator would
          * be called with '<\/script>' which would not match the original rule and
          * so the generic tag rule would identify it as a tag.
          *
          * Pattern must only match prefixes, and if it matches a prefix, then that
          * match is considered a token with the same style.
          *
          * Context is applied to the last non-whitespace, non-comment token
          * recognized.
          *
          * Shortcut is an optional string of characters, any of which, if the first
          * character, gurantee that this pattern and only this pattern matches.
          *
          * @param {Array} shortcutStylePatterns patterns that always start with
          *   a known character.  Must have a shortcut string.
          * @param {Array} fallthroughStylePatterns patterns that will be tried in
          *   order if the shortcut ones fail.  May have shortcuts.
          *
          * @return {function (Object)} a
          *   function that takes source code and returns a list of decorations.
          */
        function createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns) {
          var shortcuts = {};
          var tokenizer;
          (function () {
            var allPatterns = shortcutStylePatterns.concat(fallthroughStylePatterns);
            var allRegexs = [];
            var regexKeys = {};
            for (var i = 0, n = allPatterns.length; i < n; ++i) {
              var patternParts = allPatterns[i];
              var shortcutChars = patternParts[3];
              if (shortcutChars) {
                for (var c = shortcutChars.length; --c >= 0;) {
                  shortcuts[shortcutChars.charAt(c)] = patternParts;
                }
              }
              var regex = patternParts[1];
              var k = '' + regex;
              if (!regexKeys.hasOwnProperty(k)) {
                allRegexs.push(regex);
                regexKeys[k] = null;
              }
            }
            allRegexs.push(/[\0-\uffff]/);
            tokenizer = combinePrefixPatterns(allRegexs);
          })();
      
          var nPatterns = fallthroughStylePatterns.length;
          var notWs = /\S/;
      
          /**
           * Lexes job.source and produces an output array job.decorations of style
           * classes preceded by the position at which they start in job.source in
           * order.
           *
           * @param {Object} job an object like {@code
           *    source: {string} sourceText plain text,
           *    basePos: {int} position of job.source in the larger chunk of
           *        sourceCode.
           * }
           */
          var decorate = function (job) {
            var sourceCode = job.source, basePos = job.basePos;
            /** Even entries are positions in source in ascending order.  Odd enties
              * are style markers (e.g., PR_COMMENT) that run from that position until
              * the end.
              * @type {Array.<number|string>}
              */
            var decorations = [basePos, PR_PLAIN];
            var pos = 0;  // index into sourceCode
            var tokens = sourceCode.match(tokenizer) || [];
            var styleCache = {};
      
            for (var ti = 0, nTokens = tokens.length; ti < nTokens; ++ti) {
              var token = tokens[ti];
              var style = styleCache[token];
              var match = void 0;
      
              var isEmbedded;
              if (typeof style === 'string') {
                isEmbedded = false;
              } else {
                var patternParts = shortcuts[token.charAt(0)];
                if (patternParts) {
                  match = token.match(patternParts[1]);
                  style = patternParts[0];
                } else {
                  for (var i = 0; i < nPatterns; ++i) {
                    patternParts = fallthroughStylePatterns[i];
                    match = token.match(patternParts[1]);
                    if (match) {
                      style = patternParts[0];
                      break;
                    }
                  }
      
                  if (!match) {  // make sure that we make progress
                    style = PR_PLAIN;
                  }
                }
      
                isEmbedded = style.length >= 5 && 'lang-' === style.substring(0, 5);
                if (isEmbedded && !(match && typeof match[1] === 'string')) {
                  isEmbedded = false;
                  style = PR_SOURCE;
                }
      
                if (!isEmbedded) { styleCache[token] = style; }
              }
      
              var tokenStart = pos;
              pos += token.length;
      
              if (!isEmbedded) {
                decorations.push(basePos + tokenStart, style);
              } else {  // Treat group 1 as an embedded block of source code.
                var embeddedSource = match[1];
                var embeddedSourceStart = token.indexOf(embeddedSource);
                var embeddedSourceEnd = embeddedSourceStart + embeddedSource.length;
                if (match[2]) {
                  // If embeddedSource can be blank, then it would match at the
                  // beginning which would cause us to infinitely recurse on the
                  // entire token, so we catch the right context in match[2].
                  embeddedSourceEnd = token.length - match[2].length;
                  embeddedSourceStart = embeddedSourceEnd - embeddedSource.length;
                }
                var lang = style.substring(5);
                // Decorate the left of the embedded source
                appendDecorations(
                    basePos + tokenStart,
                    token.substring(0, embeddedSourceStart),
                    decorate, decorations);
                // Decorate the embedded source
                appendDecorations(
                    basePos + tokenStart + embeddedSourceStart,
                    embeddedSource,
                    langHandlerForExtension(lang, embeddedSource),
                    decorations);
                // Decorate the right of the embedded section
                appendDecorations(
                    basePos + tokenStart + embeddedSourceEnd,
                    token.substring(embeddedSourceEnd),
                    decorate, decorations);
              }
            }
            job.decorations = decorations;
          };
          return decorate;
        }
      
        /** returns a function that produces a list of decorations from source text.
          *
          * This code treats ", ', and ` as string delimiters, and \ as a string
          * escape.  It does not recognize perl's qq() style strings.
          * It has no special handling for double delimiter escapes as in basic, or
          * the tripled delimiters used in python, but should work on those regardless
          * although in those cases a single string literal may be broken up into
          * multiple adjacent string literals.
          *
          * It recognizes C, C++, and shell style comments.
          *
          * @param {Object} options a set of optional parameters.
          * @return {function (Object)} a function that examines the source code
          *     in the input job and builds the decoration list.
          */
        function sourceDecorator(options) {
          var shortcutStylePatterns = [], fallthroughStylePatterns = [];
          if (options['tripleQuotedStrings']) {
            // '''multi-line-string''', 'single-line-string', and double-quoted
            shortcutStylePatterns.push(
                [PR_STRING,  /^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,
                 null, '\'"']);
          } else if (options['multiLineStrings']) {
            // 'multi-line-string', "multi-line-string"
            shortcutStylePatterns.push(
                [PR_STRING,  /^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,
                 null, '\'"`']);
          } else {
            // 'single-line-string', "single-line-string"
            shortcutStylePatterns.push(
                [PR_STRING,
                 /^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,
                 null, '"\'']);
          }
          if (options['verbatimStrings']) {
            // verbatim-string-literal production from the C# grammar.  See issue 93.
            fallthroughStylePatterns.push(
                [PR_STRING, /^@\"(?:[^\"]|\"\")*(?:\"|$)/, null]);
          }
          var hc = options['hashComments'];
          if (hc) {
            if (options['cStyleComments']) {
              if (hc > 1) {  // multiline hash comments
                shortcutStylePatterns.push(
                    [PR_COMMENT, /^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/, null, '#']);
              } else {
                // Stop C preprocessor declarations at an unclosed open comment
                shortcutStylePatterns.push(
                    [PR_COMMENT, /^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,
                     null, '#']);
              }
              fallthroughStylePatterns.push(
                  [PR_STRING,
                   /^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,
                   null]);
            } else {
              shortcutStylePatterns.push([PR_COMMENT, /^#[^\r\n]*/, null, '#']);
            }
          }
          if (options['cStyleComments']) {
            fallthroughStylePatterns.push([PR_COMMENT, /^\/\/[^\r\n]*/, null]);
            fallthroughStylePatterns.push(
                [PR_COMMENT, /^\/\*[\s\S]*?(?:\*\/|$)/, null]);
          }
          if (options['regexLiterals']) {
            var REGEX_LITERAL = (
                // A regular expression literal starts with a slash that is
                // not followed by * or / so that it is not confused with
                // comments.
                '/(?=[^/*])'
                // and then contains any number of raw characters,
                + '(?:[^/\\x5B\\x5C]'
                // escape sequences (\x5C),
                +    '|\\x5C[\\s\\S]'
                // or non-nesting character sets (\x5B\x5D);
                +    '|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+'
                // finally closed by a /.
                + '/');
            fallthroughStylePatterns.push(
                ['lang-regex',
                 new RegExp('^' + REGEXP_PRECEDER_PATTERN + '(' + REGEX_LITERAL + ')')
                 ]);
          }
      
          if (options['types']) {
            fallthroughStylePatterns.push([PR_TYPE, options['types']]);
          }
      
          var keywords = options['keywords'].replace(/^\s+|\s+$/g, '');
          if (keywords.length) {
            fallthroughStylePatterns.push(
                [PR_KEYWORD,
                 new RegExp('^(?:' + keywords.replace(/\s+/g, '|') + ')\\b'), null]);
          }
      
          shortcutStylePatterns.push([PR_PLAIN,       /^\s+/, null, ' \r\n\t\xA0']);
          fallthroughStylePatterns.push(
              // TODO(mikesamuel): recognize non-latin letters and numerals in idents
              [PR_LITERAL,     /^@[a-z_$][a-z_$@0-9]*/i, null],
              [PR_TYPE,        /^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/, null],
              [PR_PLAIN,       /^[a-z_$][a-z_$@0-9]*/i, null],
              [PR_LITERAL,
               new RegExp(
                   '^(?:'
                   // A hex number
                   + '0x[a-f0-9]+'
                   // or an octal or decimal number,
                   + '|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)'
                   // possibly in scientific notation
                   + '(?:e[+\\-]?\\d+)?'
                   + ')'
                   // with an optional modifier like UL for unsigned long
                   + '[a-z]*', 'i'),
               null, '0123456789'],
              // Don't treat escaped quotes in bash as starting strings.  See issue 144.
              [PR_PLAIN,       /^\\[\s\S]?/, null],
              [PR_PUNCTUATION, /^.[^\s\w\.$@\'\"\`\/\#\\]*/, null]);
      
          return createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns);
        }
      
        var decorateSource = sourceDecorator({
              'keywords': ALL_KEYWORDS,
              'hashComments': true,
              'cStyleComments': true,
              'multiLineStrings': true,
              'regexLiterals': true
            });
      
        /**
         * Given a DOM subtree, wraps it in a list, and puts each line into its own
         * list item.
         *
         * @param {Node} node modified in place.  Its content is pulled into an
         *     HTMLOListElement, and each line is moved into a separate list item.
         *     This requires cloning elements, so the input might not have unique
         *     IDs after numbering.
         */
        function numberLines(node, opt_startLineNum) {
          var nocode = /(?:^|\s)nocode(?:\s|$)/;
          var lineBreak = /\r\n?|\n/;
        
          var document = node.ownerDocument;
        
          var whitespace;
          if (node.currentStyle) {
            whitespace = node.currentStyle.whiteSpace;
          } else if (window.getComputedStyle) {
            whitespace = document.defaultView.getComputedStyle(node, null)
                .getPropertyValue('white-space');
          }
          // If it's preformatted, then we need to split lines on line breaks
          // in addition to <BR>s.
          var isPreformatted = whitespace && 'pre' === whitespace.substring(0, 3);
        
          var li = document.createElement('LI');
          while (node.firstChild) {
            li.appendChild(node.firstChild);
          }
          // An array of lines.  We split below, so this is initialized to one
          // un-split line.
          var listItems = [li];
        
          function walk(node) {
            switch (node.nodeType) {
              case 1:  // Element
                if (nocode.test(node.className)) { break; }
                if ('BR' === node.nodeName) {
                  breakAfter(node);
                  // Discard the <BR> since it is now flush against a </LI>.
                  if (node.parentNode) {
                    node.parentNode.removeChild(node);
                  }
                } else {
                  for (var child = node.firstChild; child; child = child.nextSibling) {
                    walk(child);
                  }
                }
                break;
              case 3: case 4:  // Text
                if (isPreformatted) {
                  var text = node.nodeValue;
                  var match = text.match(lineBreak);
                  if (match) {
                    var firstLine = text.substring(0, match.index);
                    node.nodeValue = firstLine;
                    var tail = text.substring(match.index + match[0].length);
                    if (tail) {
                      var parent = node.parentNode;
                      parent.insertBefore(
                          document.createTextNode(tail), node.nextSibling);
                    }
                    breakAfter(node);
                    if (!firstLine) {
                      // Don't leave blank text nodes in the DOM.
                      node.parentNode.removeChild(node);
                    }
                  }
                }
                break;
            }
          }
        
          // Split a line after the given node.
          function breakAfter(lineEndNode) {
            // If there's nothing to the right, then we can skip ending the line
            // here, and move root-wards since splitting just before an end-tag
            // would require us to create a bunch of empty copies.
            while (!lineEndNode.nextSibling) {
              lineEndNode = lineEndNode.parentNode;
              if (!lineEndNode) { return; }
            }
        
            function breakLeftOf(limit, copy) {
              // Clone shallowly if this node needs to be on both sides of the break.
              var rightSide = copy ? limit.cloneNode(false) : limit;
              var parent = limit.parentNode;
              if (parent) {
                // We clone the parent chain.
                // This helps us resurrect important styling elements that cross lines.
                // E.g. in <i>Foo<br>Bar</i>
                // should be rewritten to <li><i>Foo</i></li><li><i>Bar</i></li>.
                var parentClone = breakLeftOf(parent, 1);
                // Move the clone and everything to the right of the original
                // onto the cloned parent.
                var next = limit.nextSibling;
                parentClone.appendChild(rightSide);
                for (var sibling = next; sibling; sibling = next) {
                  next = sibling.nextSibling;
                  parentClone.appendChild(sibling);
                }
              }
              return rightSide;
            }
        
            var copiedListItem = breakLeftOf(lineEndNode.nextSibling, 0);
        
            // Walk the parent chain until we reach an unattached LI.
            for (var parent;
                 // Check nodeType since IE invents document fragments.
                 (parent = copiedListItem.parentNode) && parent.nodeType === 1;) {
              copiedListItem = parent;
            }
            // Put it on the list of lines for later processing.
            listItems.push(copiedListItem);
          }
        
          // Split lines while there are lines left to split.
          for (var i = 0;  // Number of lines that have been split so far.
               i < listItems.length;  // length updated by breakAfter calls.
               ++i) {
            walk(listItems[i]);
          }
        
          // Make sure numeric indices show correctly.
          if (opt_startLineNum === (opt_startLineNum|0)) {
            listItems[0].setAttribute('value', opt_startLineNum);
          }
        
          var ol = document.createElement('OL');
          ol.className = 'linenums';
          var offset = Math.max(0, ((opt_startLineNum - 1 /* zero index */)) | 0) || 0;
          for (var i = 0, n = listItems.length; i < n; ++i) {
            li = listItems[i];
            // Stick a class on the LIs so that stylesheets can
            // color odd/even rows, or any other row pattern that
            // is co-prime with 10.
            li.className = 'L' + ((i + offset) % 10);
            if (!li.firstChild) {
              li.appendChild(document.createTextNode('\xA0'));
            }
            ol.appendChild(li);
          }
        
          node.appendChild(ol);
        }
      
        /**
         * Breaks {@code job.source} around style boundaries in {@code job.decorations}
         * and modifies {@code job.sourceNode} in place.
         * @param {Object} job like <pre>{
         *    source: {string} source as plain text,
         *    spans: {Array.<number|Node>} alternating span start indices into source
         *       and the text node or element (e.g. {@code <BR>}) corresponding to that
         *       span.
         *    decorations: {Array.<number|string} an array of style classes preceded
         *       by the position at which they start in job.source in order
         * }</pre>
         * @private
         */
        function recombineTagsAndDecorations(job) {
          var isIE = /\bMSIE\b/.test(navigator.userAgent);
          var newlineRe = /\n/g;
        
          var source = job.source;
          var sourceLength = source.length;
          // Index into source after the last code-unit recombined.
          var sourceIndex = 0;
        
          var spans = job.spans;
          var nSpans = spans.length;
          // Index into spans after the last span which ends at or before sourceIndex.
          var spanIndex = 0;
        
          var decorations = job.decorations;
          var nDecorations = decorations.length;
          // Index into decorations after the last decoration which ends at or before sourceIndex.
          var decorationIndex = 0;
        
          // Simplify decorations.
          var decPos = 0;
          for (var i = 0; i < nDecorations;) {
            // Skip over any zero-length decorations.
            var startPos = decorations[i];
            var start = i;
            while (start + 2 < nDecorations && decorations[start + 2] === startPos) {
              start += 2;
            }
            // Conflate all adjacent decorations that use the same style.
            var startDec = decorations[start + 1];
            var end = start + 2;
            while (end + 2 <= nDecorations
                   && (decorations[end + 1] === startDec
                       || decorations[end] === decorations[end + 2])) {
              end += 2;
            }
            decorations[decPos++] = startPos;
            decorations[decPos++] = startDec;
            i = end;
          }
        
          // Strip any zero-length decoration at the end.
          if (decPos && decorations[decPos - 2] === sourceLength) { decPos -= 2; }
          nDecorations = decorations.length = decPos;
        
          var decoration = null;
          while (spanIndex < nSpans) {
            var spanStart = spans[spanIndex];
            var spanEnd = spans[spanIndex + 2] || sourceLength;
        
            var decStart = decorations[decorationIndex];
            var decEnd = decorations[decorationIndex + 2] || sourceLength;
        
            var end = Math.min(spanEnd, decEnd);
        
            var textNode = spans[spanIndex + 1];
            if (textNode.nodeType !== 1) {  // Don't muck with <BR>s or <LI>s
              var styledText = source.substring(sourceIndex, end);
              // This may seem bizarre, and it is.  Emitting LF on IE causes the
              // code to display with spaces instead of line breaks.
              // Emitting Windows standard issue linebreaks (CRLF) causes a blank
              // space to appear at the beginning of every line but the first.
              // Emitting an old Mac OS 9 line separator makes everything spiffy.
              if (isIE) { styledText = styledText.replace(newlineRe, '\r'); }
              textNode.nodeValue = styledText;
              var document = textNode.ownerDocument;
              var span = document.createElement('SPAN');
              span.className = decorations[decorationIndex + 1];
              var parentNode = textNode.parentNode;
              parentNode.replaceChild(span, textNode);
              span.appendChild(textNode);
              if (sourceIndex < spanEnd) {  // Split off a text node.
                spans[spanIndex + 1] = textNode
                    // TODO: Possibly optimize by using '' if there's no flicker.
                    = document.createTextNode(source.substring(end, spanEnd));
                parentNode.insertBefore(textNode, span.nextSibling);
              }
            }
        
            sourceIndex = end;
        
            if (sourceIndex >= spanEnd) {
              spanIndex += 2;
            }
            if (sourceIndex >= decEnd) {
              decorationIndex += 2;
            }
          }
        }
      
      
        /** Maps language-specific file extensions to handlers. */
        var langHandlerRegistry = {};
        /** Register a language handler for the given file extensions.
          * @param {function (Object)} handler a function from source code to a list
          *      of decorations.  Takes a single argument job which describes the
          *      state of the computation.   The single parameter has the form
          *      {@code {
          *        source: {string} as plain text.
          *        decorations: {Array.<number|string>} an array of style classes
          *                     preceded by the position at which they start in
          *                     job.source in order.
          *                     The language handler should assigned this field.
          *        basePos: {int} the position of source in the larger source chunk.
          *                 All positions in the output decorations array are relative
          *                 to the larger source chunk.
          *      } }
          * @param {Array.<string>} fileExtensions
          */
        function registerLangHandler(handler, fileExtensions) {
          for (var i = fileExtensions.length; --i >= 0;) {
            var ext = fileExtensions[i];
            if (!langHandlerRegistry.hasOwnProperty(ext)) {
              langHandlerRegistry[ext] = handler;
            } else if ('console' in window) {
              console['warn']('cannot override language handler %s', ext);
            }
          }
        }
        function langHandlerForExtension(extension, source) {
          if (!(extension && langHandlerRegistry.hasOwnProperty(extension))) {
            // Treat it as markup if the first non whitespace character is a < and
            // the last non-whitespace character is a >.
            extension = /^\s*</.test(source)
                ? 'default-markup'
                : 'default-code';
          }
          return langHandlerRegistry[extension];
        }
        registerLangHandler(decorateSource, ['default-code']);
        registerLangHandler(
            createSimpleLexer(
                [],
                [
                 [PR_PLAIN,       /^[^<?]+/],
                 [PR_DECLARATION, /^<!\w[^>]*(?:>|$)/],
                 [PR_COMMENT,     /^<\!--[\s\S]*?(?:-\->|$)/],
                 // Unescaped content in an unknown language
                 ['lang-',        /^<\?([\s\S]+?)(?:\?>|$)/],
                 ['lang-',        /^<%([\s\S]+?)(?:%>|$)/],
                 [PR_PUNCTUATION, /^(?:<[%?]|[%?]>)/],
                 ['lang-',        /^<xmp\b[^>]*>([\s\S]+?)<\/xmp\b[^>]*>/i],
                 // Unescaped content in javascript.  (Or possibly vbscript).
                 ['lang-js',      /^<script\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],
                 // Contains unescaped stylesheet content
                 ['lang-css',     /^<style\b[^>]*>([\s\S]*?)(<\/style\b[^>]*>)/i],
                 ['lang-in.tag',  /^(<\/?[a-z][^<>]*>)/i]
                ]),
            ['default-markup', 'htm', 'html', 'mxml', 'xhtml', 'xml', 'xsl']);
        registerLangHandler(
            createSimpleLexer(
                [
                 [PR_PLAIN,        /^[\s]+/, null, ' \t\r\n'],
                 [PR_ATTRIB_VALUE, /^(?:\"[^\"]*\"?|\'[^\']*\'?)/, null, '\"\'']
                 ],
                [
                 [PR_TAG,          /^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],
                 [PR_ATTRIB_NAME,  /^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],
                 ['lang-uq.val',   /^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],
                 [PR_PUNCTUATION,  /^[=<>\/]+/],
                 ['lang-js',       /^on\w+\s*=\s*\"([^\"]+)\"/i],
                 ['lang-js',       /^on\w+\s*=\s*\'([^\']+)\'/i],
                 ['lang-js',       /^on\w+\s*=\s*([^\"\'>\s]+)/i],
                 ['lang-css',      /^style\s*=\s*\"([^\"]+)\"/i],
                 ['lang-css',      /^style\s*=\s*\'([^\']+)\'/i],
                 ['lang-css',      /^style\s*=\s*([^\"\'>\s]+)/i]
                 ]),
            ['in.tag']);
        registerLangHandler(
            createSimpleLexer([], [[PR_ATTRIB_VALUE, /^[\s\S]+/]]), ['uq.val']);
        registerLangHandler(sourceDecorator({
                'keywords': CPP_KEYWORDS,
                'hashComments': true,
                'cStyleComments': true,
                'types': C_TYPES
              }), ['c', 'cc', 'cpp', 'cxx', 'cyc', 'm']);
        registerLangHandler(sourceDecorator({
                'keywords': 'null true false'
              }), ['json']);
        registerLangHandler(sourceDecorator({
                'keywords': CSHARP_KEYWORDS,
                'hashComments': true,
                'cStyleComments': true,
                'verbatimStrings': true,
                'types': C_TYPES
              }), ['cs']);
        registerLangHandler(sourceDecorator({
                'keywords': JAVA_KEYWORDS,
                'cStyleComments': true
              }), ['java']);
        registerLangHandler(sourceDecorator({
                'keywords': SH_KEYWORDS,
                'hashComments': true,
                'multiLineStrings': true
              }), ['bsh', 'csh', 'sh']);
        registerLangHandler(sourceDecorator({
                'keywords': PYTHON_KEYWORDS,
                'hashComments': true,
                'multiLineStrings': true,
                'tripleQuotedStrings': true
              }), ['cv', 'py']);
        registerLangHandler(sourceDecorator({
                'keywords': PERL_KEYWORDS,
                'hashComments': true,
                'multiLineStrings': true,
                'regexLiterals': true
              }), ['perl', 'pl', 'pm']);
        registerLangHandler(sourceDecorator({
                'keywords': RUBY_KEYWORDS,
                'hashComments': true,
                'multiLineStrings': true,
                'regexLiterals': true
              }), ['rb']);
        registerLangHandler(sourceDecorator({
                'keywords': JSCRIPT_KEYWORDS,
                'cStyleComments': true,
                'regexLiterals': true
              }), ['js']);
        registerLangHandler(sourceDecorator({
                'keywords': COFFEE_KEYWORDS,
                'hashComments': 3,  // ### style block comments
                'cStyleComments': true,
                'multilineStrings': true,
                'tripleQuotedStrings': true,
                'regexLiterals': true
              }), ['coffee']);
        registerLangHandler(createSimpleLexer([], [[PR_STRING, /^[\s\S]+/]]), ['regex']);
      
        function applyDecorator(job) {
          var opt_langExtension = job.langExtension;
      
          try {
            // Extract tags, and convert the source code to plain text.
            var sourceAndSpans = extractSourceSpans(job.sourceNode);
            /** Plain text. @type {string} */
            var source = sourceAndSpans.source;
            job.source = source;
            job.spans = sourceAndSpans.spans;
            job.basePos = 0;
      
            // Apply the appropriate language handler
            langHandlerForExtension(opt_langExtension, source)(job);
      
            // Integrate the decorations and tags back into the source code,
            // modifying the sourceNode in place.
            recombineTagsAndDecorations(job);
          } catch (e) {
            if ('console' in window) {
              console['log'](e && e['stack'] ? e['stack'] : e);
            }
          }
        }
      
        /**
         * @param sourceCodeHtml {string} The HTML to pretty print.
         * @param opt_langExtension {string} The language name to use.
         *     Typically, a filename extension like 'cpp' or 'java'.
         * @param opt_numberLines {number|boolean} True to number lines,
         *     or the 1-indexed number of the first line in sourceCodeHtml.
         */
        function prettyPrintOne(sourceCodeHtml, opt_langExtension, opt_numberLines) {
          var container = document.createElement('PRE');
          // This could cause images to load and onload listeners to fire.
          // E.g. <img onerror="alert(1337)" src="nosuchimage.png">.
          // We assume that the inner HTML is from a trusted source.
          container.innerHTML = sourceCodeHtml;
          if (opt_numberLines) {
            numberLines(container, opt_numberLines);
          }
      
          var job = {
            langExtension: opt_langExtension,
            numberLines: opt_numberLines,
            sourceNode: container
          };
          applyDecorator(job);
          return container.innerHTML;
        }
      
        function prettyPrint(opt_whenDone) {
          function byTagName(tn) { return document.getElementsByTagName(tn); }
          // fetch a list of nodes to rewrite
          var codeSegments = [byTagName('pre'), byTagName('code'), byTagName('xmp')];
          var elements = [];
          for (var i = 0; i < codeSegments.length; ++i) {
            for (var j = 0, n = codeSegments[i].length; j < n; ++j) {
              elements.push(codeSegments[i][j]);
            }
          }
          codeSegments = null;
      
          var clock = Date;
          if (!clock['now']) {
            clock = { 'now': function () { return (new Date).getTime(); } };
          }
      
          // The loop is broken into a series of continuations to make sure that we
          // don't make the browser unresponsive when rewriting a large page.
          var k = 0;
          var prettyPrintingJob;
      
          function doWork() {
            var endTime = (window['PR_SHOULD_USE_CONTINUATION'] ?
                           clock.now() + 250 /* ms */ :
                           Infinity);
            for (; k < elements.length && clock.now() < endTime; k++) {
              var cs = elements[k];
              if (cs.className && cs.className.indexOf('prettyprint') >= 0) {
                // If the classes includes a language extensions, use it.
                // Language extensions can be specified like
                //     <pre class="prettyprint lang-cpp">
                // the language extension "cpp" is used to find a language handler as
                // passed to PR.registerLangHandler.
                var langExtension = cs.className.match(/\blang-(\w+)\b/);
                if (langExtension) { langExtension = langExtension[1]; }
      
                // make sure this is not nested in an already prettified element
                var nested = false;
                for (var p = cs.parentNode; p; p = p.parentNode) {
                  if ((p.tagName === 'pre' || p.tagName === 'code' ||
                       p.tagName === 'xmp') &&
                      p.className && p.className.indexOf('prettyprint') >= 0) {
                    nested = true;
                    break;
                  }
                }
                if (!nested) {
                  // Look for a class like linenums or linenums:<n> where <n> is the
                  // 1-indexed number of the first line.
                  var lineNums = cs.className.match(/\blinenums\b(?::(\d+))?/);
                  lineNums = lineNums
                        ? lineNums[1] && lineNums[1].length ? +lineNums[1] : true
                        : false;
                  if (lineNums) { numberLines(cs, lineNums); }
      
                  // do the pretty printing
                  prettyPrintingJob = {
                    langExtension: langExtension,
                    sourceNode: cs,
                    numberLines: lineNums
                  };
                  applyDecorator(prettyPrintingJob);
                }
              }
            }
            if (k < elements.length) {
              // finish up in a continuation
              setTimeout(doWork, 250);
            } else if (opt_whenDone) {
              opt_whenDone();
            }
          }
      
          doWork();
        }
      
        window['prettyPrintOne'] = prettyPrintOne;
        window['prettyPrint'] = prettyPrint;
        window['PR'] = {
              'createSimpleLexer': createSimpleLexer,
              'registerLangHandler': registerLangHandler,
              'sourceDecorator': sourceDecorator,
              'PR_ATTRIB_NAME': PR_ATTRIB_NAME,
              'PR_ATTRIB_VALUE': PR_ATTRIB_VALUE,
              'PR_COMMENT': PR_COMMENT,
              'PR_DECLARATION': PR_DECLARATION,
              'PR_KEYWORD': PR_KEYWORD,
              'PR_LITERAL': PR_LITERAL,
              'PR_NOCODE': PR_NOCODE,
              'PR_PLAIN': PR_PLAIN,
              'PR_PUNCTUATION': PR_PUNCTUATION,
              'PR_SOURCE': PR_SOURCE,
              'PR_STRING': PR_STRING,
              'PR_TAG': PR_TAG,
              'PR_TYPE': PR_TYPE
            };
      })();
      ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/site/site/stylesheets/�������������������������������������������������������������������0000775�0000000�0000000�00000000000�13711043772�0016731�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/site/site/stylesheets/github.css���������������������������������������������������������0000664�0000000�0000000�00000000454�13711043772�0020730�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.atn { color:#008080 } 
      .atv { color:#008080 } 
      .com { color:#999988 } 
      .dec { color:#000000; font-weight:bold } 
      .kwd { color:#000000; font-weight:bold } 
      .lit { color:#009999 } 
      .pln { color:#000000 } 
      .pun { color:#666666 } 
      .str { color:#dd1144 } 
      .tag { color:#000080 } 
      .typ { color:#445588 } 
      ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/site/src/��������������������������������������������������������������������������������0000775�0000000�0000000�00000000000�13711043772�0014200�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/site/src/layouts/������������������������������������������������������������������������0000775�0000000�0000000�00000000000�13711043772�0015700�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/site/src/layouts/default.haml������������������������������������������������������������0000664�0000000�0000000�00000003434�13711043772�0020173�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������!!! 5
      %html
        %head
          %meta{:'http-equiv' => "Content-type", :content => "text/html; charset=utf-8"}
          %title Faye: Simple pub/sub messaging for the web
          = stylesheets
          %link{'rel' => 'stylesheet', 'type' => 'text/css', 'href' => '//fonts.googleapis.com/css?family=Inconsolata:400,700|Open+Sans:300italic,400italic,700italic,400,300,700'}
        %body{:onload => 'prettyPrint()'}
      
          .header
            .container
              %h1
                =link "Faye", "/"
              %h2 Simple pub/sub messaging for the web
              .docs
                %h3 Documentation
                %ul
                  %li
                    =link "Node.js server", "/node.html"
                  %li
                    =link "Ruby server", "/ruby.html"
                  %li
                    =link "Browser client", "/browser.html"
                  %li
                    =link "Security advice", "/security.html"
              .community
                %h3 Developers
                %ul
                  %li
                    =link "Architecture"
                  %li
                    =link "GitHub", "https://github.com/faye/faye"
                  %li
                    =link "Mailing list", "http://groups.google.com/group/faye-users"
              .download
                %h3 Download
                %ul
                  %li
                    =link "Packages for Node.js, Ruby and browsers", "/download.html"
      
          .main
            .container
              = yield
              .clear
      
          .footer
            .container
              :textile
                &copy; 2009&ndash;2020 "James Coglan":http://jcoglan.com.
                Released under the Apache 2.0 license.
      
          = javascripts 'prettify'
          :plain
            <script type="text/javascript">
              (function() {
                var pre = document.getElementsByTagName('pre'), n = pre.length
                while (n--) {
                  if (!pre[n].className) pre[n].className = 'prettyprint'
                }
                prettyPrint()
              })()
            </script>
      ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/site/src/pages/��������������������������������������������������������������������������0000775�0000000�0000000�00000000000�13711043772�0015277�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/site/src/pages/architecture.haml���������������������������������������������������������0000664�0000000�0000000�00000013436�13711043772�0020633�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.content
        :textile
          h3. Internal architecture
      
          If you're developing Faye, the following gives an overview of the project's
          architecture. Both the Ruby and JavaScript versions share the same internal
          structure and their implementations are very similar. Faye is based on the
          Bayeux protocol, and is compatible with the reference implementation of
          Bayeux provided by the cometD project.
      
          Messages flow through the system according to this diagram:
      
          <div class="image">
            !/images/faye-internals.png!
          </div>
      
          The components that make up the messaging stack are described below. They
          are designed to be modular such that each layer is easily swappable if the
          need arises.
      
          h4. Storage
      
          The storage layer is the base of the whole stack, and as its name suggests
          it stores the state of the Faye service. This state includes a list of
          active client IDs, which channels each client is subscribed to, and any
          queued messages waiting to be delivered to clients.
      
          State is stored in the server's memory if using the @memory@ engine (the
          default), and in a Redis database if using the @redis@ engine.
      
          h4. Engine
      
          The engine is an object that provides an abstract API on top of the storage
          layer. It implements all the core operations of the Faye service, such as
          registering new clients, storing subscriptions and routing messages. Faye
          currently has two engines available:
      
          * @memory@ - stores state in the server process's memory
          * @redis@ - stores state in a Redis database, can run multiple server
            instances off one DB
      
          All engine types provide the same API so they can be easily swapped out for
          a different backend implementation. The engine's operations assume that all
          necessary validation has been done on incoming data further up the stack so
          the implementations can be kept small and simple.
      
          h4. Server
      
          The server implements the Bayeux messaging protocol, providing the @handshake@,
          @connect@, @disconnect@, @subscribe@, @unsubscribe@ and @publish@ operations.
          It delegates execution of these operations to the Engine; the Server's job
          is simply to wrap the operations with the Bayeux protocol and validate
          incoming messages. This separation makes it easy to swap out the backend
          implementation while keeping the protocol layer consistent.
      
          The Server class does not know anything about HTTP or other network
          transports, it simply provides an object-based implementation of Bayeux.
      
          h4. Server-side extensions
      
          These are components written by the user that intercept messages that flow
          in and out of the Server. Incoming extensions apply when a message comes in
          to the server from the Internet, and outgoing extensions apply when a
          message is being sent back out to the client. The user can change the
          messages' data, and add errors to stop the Server processing them.
      
          h4. Adapter
      
          The Adapter, as implemented by the @NodeAdapter@ and @RackAdapter@ classes,
          exposes the Server's interface over HTTP. It is responsible for serializing
          and deserialzing messages as JSON and accepting connections over various
          flavours of HTTP transport:
      
          * Persistent connections using @WebSocket@
          * Long-polling via HTTP POST
          * Cross Origin Resource Sharing
          * Callback-polling via JSON-P
      
          h4. Transport
      
          The Transport classes implement the client side of the network transports
          supported by the server-side Adapter. Their job is to accept messages from
          the Client, serialize them and send them to the server. When responses
          arrive they are deserialized and given to the Client.
      
          Transport objects are also responsible for detecting and recovering from
          network errors and server restarts using the best strategy available. For
          example, the @WebSocket@ transport can immediately detect disconnections,
          whereas the JSON-P transport has to rely on timeouts.
      
          h4. Client-side extensions
      
          These are components written by the user that intercept messages that flow
          in and out of the Client. Outgoing extensions apply when a message is sent
          by the Client, and incoming extensions apply when a message arrives from the
          server.
      
          h4. Client
      
          This is the component that the user interacts with the most, and provides
          the interface through which subscriptions are registered and messages are
          published. The Client implements the client side of the Bayeux protocol, and
          presents a slightly higher-level interface to the user. For example, the
          user does not have to initiate handshakes or connections, this is done
          automatically.
      
          Adding multiple subscriptions to the same channel only results in one
          @subscribe@ message going to the server, and similarly an @unsubscribe@
          message is not sent until all listeners have been removed from the channel.
          The client's @subscribe@/@unsubscribe@ interface deals with managing these
          subscriptions and distributing messages to the right listeners as new
          messages arrive from the server.
      
          h4. Clustering
      
          Some engines provided by Faye (for example the @redis@ engine) support
          clustering, i.e. they let you run a single Faye service across multiple
          front-end web servers. These engines are slower since they must perform I/O
          to external services, but they let you scale your Faye service if you
          outgrow the network connection limit of one machine.
      
          <div class="image">
            !/images/faye-cluster.png!
          </div>
      
          In clustered engines, the Storage layer (for example a Redis database server)
          is shared and several Engine+Server+Adapter stacks can be run on top of it.
          The server stacks are stateless: a Client should be able to connect to any
          of them at any time and it should behave as though interacting with a single
          service.
      ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/site/src/pages/browser.haml��������������������������������������������������������������0000664�0000000�0000000�00000011021�13711043772�0017620�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.content
        = partial 'browser_navigation'
      
        :textile
          h4. Setting up
      
          In order to use the browser client, you'll need to start up a Faye server to
          handle message delivery between clients. See the "Node.js":/node.html or
          "Ruby":/ruby.html documentation for more information.
      
          With the server up and running, you just need to include the client script
          in your page. Let's assume you've mounted a server at @http://localhost:8000/faye@.
      
          <pre><script type="text/javascript"
                  src="http://localhost:8000/faye/client.js">
                  </script></pre>
      
          You can then create a client. You should make sure you only create one
          client per target server, since browsers impose a per-host connection limit
          and creating more clients will saturate the available connections in many
          browsers.
      
          To make a client, all you need is the mount point of the server you want to
          connect to:
      
          <pre><script type="text/javascript">
            var client = new Faye.Client('http://localhost:8000/faye');
          </script></pre>
      
          h4. Timeouts
      
          You can optionally specify a timeout; if the server does not send back any
          data for the given period of time, the client will assume the server has
          gone away and will attempt to reconnect. Specify the timeout as follows:
      
          <pre>var client = new Faye.Client(url, {timeout: 120});</pre>
      
          The timeout is given in seconds and should be larger that the timeout you
          set up on the server side, so we give the server ample time to respond
          before assuming there's been a network error.
      
          h4. Retry interval
      
          If the connection to the server is lost, the client will periodically try to
          reconnect and redeliver dropped messages. You can control how often these
          retries happen using the @retry@ setting. It sets the amount of time the
          client will wait between detecting a network error and attempting to resend
          a message.
      
          <pre>var client = new Faye.Client(url, {retry: 5});</pre>
      
          h4. Custom headers
      
          Some services require the use of additional HTTP headers to connect to their
          Bayeux server. You can add these headers using the @setHeader()@ method, and
          they will be sent if the underlying transport supports user-defined headers
          (currently @long-polling@ only).
      
          <pre>client.setHeader('Authorization', 'OAuth abcd-1234');</pre>
      
          h4. Cross-domain operation
      
          Faye clients and servers transparently support cross-domain communication,
          so your client can connect to a server on any domain you like without
          further configuration.
      
          h4. Per-transport endpoints
      
          Sometimes, you may need to have the Faye client use different endpoints for
          different transports. For example, you want to send all normal HTTP traffic
          to @http://example.com/faye@ but send WebSocket connections to
          @http://ws.example.com/@. You can set per-transport endpoints using the
          @endpoints@ option:
      
          <pre>var client = new Faye.Client(url, {
            endpoints: { websocket: 'http://ws.example.com/' }
          });</pre>
      
          h4. Disabling specific transports
      
          For some applications you may need to exclude some transports from use. For
          example you may know your deployment environment cannot support WebSockets,
          or you have reasons to stick to polling. In such situations you can disable
          a transport like so:
      
          <pre>client.disable('websocket');</pre>
      
          You can disable any transport, but bear in mind some transports are
          essential for establishing a connection so disabling them can render the
          client useless. These mandatory transports are:
      
          * *@long-polling@* - Based on @XMLHttpRequest@, used for same-domain
            connections
          * *@callback-polling@* - Based on JSON-P, used for cross-domain connections
          * *@in-process@* - Used for server-side clients in the same process as the
            server
      
          h4. Turning off auto-disconnect
      
          When running in a web browser, the client listens to the @beforeunload@
          event and sends a disconnection message to the server. If this message is
          not sent, the server will eventually clean up the client's data due to
          inactivity, but having clients explicitly disconnect helps reduce the work
          the periodic garbage collection has to do.
      
          If this behaviour causes problems for your application, you can stop the
          client automatically disconnecting like so:
      
          <pre>client.disable('autodisconnect');</pre>
      
          h4. Logging
      
          You can set a custom logger that will be used to collect logs from Faye. By
          default there is no logging.
      
          <pre>Faye.logger = window.console;</pre>
      ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/site/src/pages/browser/������������������������������������������������������������������0000775�0000000�0000000�00000000000�13711043772�0016762�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/site/src/pages/browser/dispatch.haml�����������������������������������������������������0000664�0000000�0000000�00000015374�13711043772�0021436�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.content
        = partial 'browser_navigation'
      
        :textile
          h4. Controlling dispatch
      
          When you send a message, the Faye client attempts to resend the message
          until it receives confirmation that the server has received it. There are a
          few options available that let you control how and when the client will
          retry, once it has detected that a message has failed to send.
      
          First, the constructor takes an option called @retry@, which sets the amount
          of time in seconds that the client will wait between detecting a failed
          delivery and retrying the message.
      
          <pre>var client = new Faye.Client(url, {retry: 15});</pre>
      
          When calling @publish()@, the @deadline@ option specifies how long to wait
          before giving up, for example if you call
      
          <pre>client.publish('/foo', {text: 'Hi there'}, {deadline: 10});</pre>
      
          then the client will not attempt to resend the message any later than 10
          seconds after your first @publish()@ call.
      
          The @attempts@ option sets how many times the client will try to send a
          message before giving up, including the first attempt. For example, this
          will make the client try to deliver the message up to 3 times:
      
          <pre>client.publish('/foo', {text: 'Hi there'}, {attempts: 3});</pre>
      
          Bear in mind that error detection depends somewhat on the transport in use,
          and these options do not tightly control when your messages will be retried.
          Faye will make a best effort to ensure delivery of your messages within the
          parameters you specify.
      
          h4. Advanced customisation
      
          To make the above options work, Faye uses an object called a @Scheduler@.
          The Faye client is responsible for selecting which transport to use, sending
          messages, detecting delivery success and failure, scheduling retries, and
          making sure no duplicate messages are sent. But the scheduler gets to decide
          if and when each message is sent. If you want full control over this
          concern, you can supply your own scheduler.
      
          For example, by default Faye uses fixed-period retries, as controlled by the
          @retry@ option above. But, say you wanted to have exponential backoff. You
          can implement that like so:
      
          <pre>var BackoffScheduler = function() {
            Faye.Scheduler.apply(this, arguments);
          };
          BackoffScheduler.prototype = Object.create(Faye.Scheduler.prototype);
      
          BackoffScheduler.prototype.getInterval = function() {
            var interval = this.options.interval,
                attempts = this.attempts;
      
            return interval * Math.pow(2, attempts - 1);
          };
      
          var client = new Faye.Client(url, {scheduler: BackoffScheduler});</pre>
      
          That is, we create a subclass of @Faye.Scheduler@ and override the
          @getInterval()@ method, which the client uses to decide how long to wait
          before the next retry. Then we pass that class into the @Client@ via the
          @scheduler@ option. Note that this option is a _class_, not a single object.
          Faye creates a separate scheduler to track each message.
      
          When you inherit from @Faye.Scheduler@, the class gets the following
          instance properties that you can refer to in its methods:
      
          * *@message@* - the Bayeux message that the scheduler applies to.
          * *@attempts@* - the number of times the client has tried to deliver the
            message.
          * *@options.interval@* - the time in seconds to wait between detecting a
            failed delivery and resending the message.
          * *@options.timeout@* - the time in seconds after which a delivery is
            considered failed if no response has been received.
          * *@options.deadline@* - a @Date@ object representing the latest time at
            which the message should be sent.
          * *@options.attempts@* - the maximum number of times the client should
            attempt to deliver the message.
      
          The default @Faye.Scheduler@ class provides the following API, which you may
          override any method of.
      
          * *@getInterval()@* - should return the time in seconds to wait between
            detecting a failed delivery and resending the message.
          * *@getTimeout()@* - should return the time in seconds after which a
            delivery is considered failed if no response has been received.
          * *@isDeliverable()@* - should return @true@ or @false@ to indicate whether
            the client should attempt to deliver the message. This is called when the
            client is about to send the message, not when an error has just been
            detected.
          * *@send()@* - is called by the client just before it attempts to deliver
            the message.
          * *@succeed()@* - is called by the client when a response is received from
            the server. This includes responses that are 'unsuccessful' within the
            semantics of Bayeux; it simply means the server received the message and
            the client will no try sending it again.
          * *@fail()@* - is called when the client decides a delivery has failed,
            either by an explicitly signalled network error or a timeout.
          * *@abort()@* - is called when the client decides to stop sending the
            message, because @isDeliverable()@ returned @false@.
      
          Note that when you override a method, particularly one of the latter four
          command methods, you should call the @Faye.Scheduler@ method before adding
          your own customisation. For example:
      
          <pre>BackoffScheduler.prototype.send = function() {
            Faye.Scheduler.prototype.send.apply(this, arguments);
            console.log('Sent message:', this.message);
          };</pre>
      
          This also applies to @isDeliverable()@; the client's @deadline@ and
          @attempts@ options rely on this method and so if the default implementation
          returns @false@, you should too:
      
          <pre>BackoffScheduler.prototype.isDeliverable = function() {
            var deliverable = Faye.Scheduler.prototype.isDeliverable.apply(this, arguments);
            if (!deliverable) return false;
            // your own logic here
          };</pre>
      
          This might seem cumbersome but the intent of this API is to allow
          applications to gain full control of the decision-making about if and when
          messages are sent, without having to deal with requests, sockets, timeouts,
          error detection, or the messaging protocol. This does mean you can render
          the client's built-in control options inactive, if you wish.
          
          The scheduler and its support infrastructure is designed to work with almost
          no knowledge of how Bayeux works, so it can deal exclusively with message
          delivery in a clean way.
      
          Note that whereas the @deadline@ and @attempts@ options to
          @client.publish()@ only affect messages the client publishes, the scheduler
          is actually used for _all_ messages the client sends, including the
          @/meta/subscribe@ and @/meta/connect@ messages it uses to receive data from
          the server. The @publish()@ options are simply convenient sugar; if you
          implement your own scheduler you get to control all messages.
      ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/site/src/pages/browser/extensions.haml���������������������������������������������������0000664�0000000�0000000�00000002041�13711043772�0022021�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.content
        = partial 'browser_navigation'
      
        :textile
          h4. Extensions
      
          Faye clients support an extension system that lets you intercept messages as
          they pass between the client and the server. To add an extension to a client,
          just call:
      
          <pre>client.addExtension(extension);</pre>
      
          @extension@ should be an object with an @incoming()@ or @outgoing()@ method
          (or both). These methods accept a message and a callback function, and
          should call the callback with the message after any necessary modifications
          have been made. For example, a simple logging extension would look like:
      
          <pre>Logger = {
            incoming: function(message, callback) {
              console.log('incoming', message);
              callback(message);
            },
            outgoing: function(message, callback) {
              console.log('outgoing', message);
              callback(message);
            }
          };
      
          client.addExtension(Logger);</pre>
      
          For more information on writing extensions, see the "Node server":/node.html
          or "Ruby server":/ruby.html documentation.
      �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/site/src/pages/browser/publishing.haml���������������������������������������������������0000664�0000000�0000000�00000003476�13711043772�0022003�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.content
        = partial 'browser_navigation'
      
        :textile
          h4. Sending messages
      
          Clients do not send each other messages directly, instead they send their
          messages to channels, and the server figures out which clients need to
          receive the message. You can send a message using the @#publish()@ method,
          passing in the channel name and a message object.
      
          <pre>client.publish('/foo', {text: 'Hi there'});</pre>
      
          The message object can be any arbitrary JavaScript object that can be
          serialized to JSON, so it can contain strings, numbers, booleans, arrays and
          other objects. There are no required fields, and the object will be
          delivered verbatim to any subscriber functions listening to that channel.
      
          Just like @subscribe()@, the @publish()@ method returns a
          "promise":http://promisesaplus.com/ that is fulfilled when the server
          acknowledges the message. This just means the server received and routed the
          message successfully, not that it has been received by all other clients.
          The promise is rejected if the server explcitly returns an error saying it
          could not publish the message to other clients; network errors are therefore
          not covered by this API.
      
          <pre>var publication = client.publish('/foo', {text: 'Hi there'});
      
          publication.then(function() {
            alert('Message received by server!');
          }, function(error) {
            alert('There was a problem: ' + error.message);
          });</pre>
      
          The Faye client will automatically try to resend your messages if it
          encounters a network error. It will attempt to resend the message until it
          receives confirmation that the server has processed it. Sometimes, you might
          not want messages to be retried indefinitely, and Faye gives you two ways to
          limit this behaviour; see the "dispatch options":/browser/dispatch.html.
      
      ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/site/src/pages/browser/subscribing.haml��������������������������������������������������0000664�0000000�0000000�00000004220�13711043772�0022135�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.content
        = partial 'browser_navigation'
      
        :textile
          h4. Subscribing to channels
      
          Clients receive data from other clients by subscribing to channels. Whenever
          any client sends a message to a channel you're subscribed to, Faye will
          notify your client with the new message.
      
          Channel names must be formatted as absolute path names whose segments may
          contain only letters, numbers, and the symbols @-@, @_@, @!@, @~@, @(@, @)@,
          @$@ and @@@. Channel names may also end with wildcards:
      
          * The @*@ wildcard matches any channel segment. So @/foo/*@ matches @/foo/bar@
            and @/foo/thing@ but not @/foo/bar/thing@.
          * The @**@ wildcard matches any channel name recursively. So @/foo/**@
            matches @/foo/bar@, @/foo/thing@ and @/foo/bar/thing@.
      
          So for example if you subscribe to @/foo/*@ and someone sends a message to
          @/foo/bar@, you will receive that message.
      
          Clients should subscribe to channels using the @#subscribe()@ method:
      
          <pre>var subscription = client.subscribe('/foo', function(message) {
            // handle message
          });</pre>
      
          The subscriber function will be invoked when anybody sends a message to
          @/foo@, and the @message@ parameter will contain the sent message object. A
          client may bind multiple listeners to a channel, and the Faye client handles
          all the management of those listeners and makes sure the server sends it the
          right messages.
      
          The @subscribe()@ method returns a @Subscription@ object, which you can
          cancel if you want to remove that listener from the channel.
      
          <pre>subscription.cancel();</pre>
      
          The @Subscription@ object is a "promise":http://promisesaplus.com/ that is
          fulfilled when the subscription has been acknowledged by the server:
      
          <pre>subscription.then(function() {
            alert('Subscription is now active!');
          });</pre>
      
          If you're subscribing to a wildcard channel, you may want to receive the
          specific channel the message was published to in your subscriber function.
          You can do this using the `withChannel()` method:
      
          <pre>client.subscribe('/foo/*').withChannel(function(channel, message) {
            // handle message
          });</pre>
      
      ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/site/src/pages/browser/transport.haml����������������������������������������������������0000664�0000000�0000000�00000002672�13711043772�0021670�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.content
        = partial 'browser_navigation'
      
        :textile
          h4. Network errors
      
          As explained in the "architecture documentation":/architecture.html, the
          Faye client does not talk to the network directly but uses a 'transport'
          object based on WebSocket, XMLHttpRequest, and other network APIs.
      
          The client exposes an abstract interface for checking the status of
          whichever connection type is in use, which is useful for giving the user
          feedback about the connection, or making your application deal with being
          offline. You can listen to the @transport:up@ and @transport:down@ events to
          be notified that the client is online or offline.
      
          <pre>client.on('transport:down', function() {
            // the client is offline
          });
      
          client.on('transport:up', function() {
            // the client is online
          });</pre>
      
          Note that these events _do not_ reflect the status of the client's session,
          or whether there is literally a network connection active. For example, you
          are not told that the transport is down just because a long-polling request
          just completed. The transport is only considered down if a request or
          WebSocket connection explicitly fails, or times out, indicating the server
          is unreachable.
      
          Also remember that Faye deals with buffering and re-sending messages for you,
          so you don't need to deal with that. These events are simply for providing
          feedback on the health of the connection.
      ����������������������������������������������������������������������faye-1.4.0/site/src/pages/download.haml�������������������������������������������������������������0000664�0000000�0000000�00000002254�13711043772�0017754�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.content
        :textile
          h3. Download Faye
          
          The latest version is 1.4.0, released July 31 2020. It is open-source
          software, released under the Apache 2.0 license. You can follow development
          on Faye's "GitHub page":https://github.com/faye/faye.
      
          h4. Download for Node.js and web browsers
      
          The Node.js version is available through "npm":https://www.npmjs.com/. This
          package contains a copy of the browser client, which is served up by the
          Faye server when running.
      
          <pre>npm install faye</pre>
      
          If you're using "Browserify":http://browserify.org/,
          "Webpack":https://webpack.github.io/ or a similar build tool, then
          requiring @faye@ will get you the client-side package, for example:
      
          <pre>var faye = require('faye');
      
          var client = new faye.Client('http://localhost:8000/faye');</pre>
      
          If you're not using such a toolchain, you can get the client bundle from
          the @client@ directory within the npm install.
      
          h4. Download for Ruby
      
          For Ruby platforms, Faye is installable through RubyGems.
      
          <pre>gem install faye</pre>
      
          This package also includes the browser client which is served up by the Faye
          server when running.
      ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/site/src/pages/index.haml����������������������������������������������������������������0000664�0000000�0000000�00000003246�13711043772�0017256�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.front-matter
        .intro
          :textile
            h3. What is it?
      
            Faye is a publish-subscribe messaging system based on the
            "Bayeux":https://docs.cometd.org/reference/index.html#_bayeux protocol.
            It provides message servers for "Node.js":http://nodejs.org and
            "Ruby":http://www.ruby-lang.org, and clients for use on the server and in
            all major web browsers.
      
            h3. Who uses it?
      
            "!/images/aha.png!":http://www.aha.io/
            "!/images/buster.png!":http://busterjs.org/
            "!/images/chaxpert.png!":https://community.chaxpert.net/
            "!/images/cloudblocks.png!":http://www.cloud66.com/
            "!/images/gitter.png!":https://gitter.im/
            "!/images/groupme.png!":http://groupme.com/
            "!/images/ineda.png!":http://www.i-neda.com/
            "!/images/medeo.png!":https://medeo.ca/
            "!/images/myspace.png!":http://new.myspace.com/
            "!/images/nokia_mix_party.png!":http://mixparty.nokia.com/
            "!/images/pathient.png!":http://www.pathient.com/
            "!/images/podio.png!":https://podio.com/
            "!/images/xydo.png!":http://www.xydo.com/
      
        :textile
          h3. %(number)1.% Start a server
      
          <pre>var http = require('http'),
              faye = require('faye');
      
          var server = http.createServer(),
              bayeux = new faye.NodeAdapter({mount: '/'});
      
          bayeux.attach(server);
          server.listen(8000);</pre>
      
          h3. %(number)2.% Create a client
      
          <pre>var client = new Faye.Client('http://localhost:8000/');
      
          client.subscribe('/messages', function(message) {
            alert('Got a message: ' + message.text);
          });
          </pre>
      
          h3. %(number)3.% Send messages
      
          <pre>client.publish('/messages', {
            text: 'Hello world'
          });
          </pre>
      ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/site/src/pages/node.haml�����������������������������������������������������������������0000664�0000000�0000000�00000005264�13711043772�0017076�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.content
        = partial 'node_navigation'
      
        :textile
          h4. Setting up
      
          All Faye clients need a central messaging server to communicate with; the
          server records which clients are subscribed to which channels and handles
          routing of messages between clients. Setting up a server in Node.js is
          simple:
      
          <pre>var http = require('http'),
              faye = require('faye');
      
          var server = http.createServer(),
              bayeux = new faye.NodeAdapter({mount: '/faye', timeout: 45});
      
          bayeux.attach(server);
          server.listen(8000);</pre>
      
          The @NodeAdapter@ class supports these options during setup:
      
          * *@mount@* - the path on the host at which the Faye service is available.
            In this example, clients would connect to @http://localhost:8000/faye@ to
            talk to the server. The server will handle _any_ request whose path begins
            with the @mount@ path; this is so that it can interoperate with clients
            that use different request paths for different channels.
          * *@timeout@* - the maximum time to hold a connection open before returning
            the response. This is given in seconds and must be smaller than the
            timeout on your frontend webserver.
          * *@engine@* - (optional) the type and parameters for the engine you want to
            use - see the "engines documentation":/node/engines.html
          * *@ping@* - (optional) how often, in seconds, to send keep-alive ping
            messages over WebSocket and EventSource connections. Use this if your Faye
            server will be accessed through a proxy that kills idle connections.
      
          It also allows WebSocket extensions to be plugged in. For example, to enable
          "permessage-deflate":https://github.com/faye/permessage-deflate-node for
          supporting clients:
      
          <pre>var faye    = require('faye'),
              deflate = require('permessage-deflate');
      
          var bayeux = new faye.NodeAdapter({mount: '/faye', timeout: 45});
          bayeux.addWebsocketExtension(deflate);</pre>
      
          You can use any extension that's compatible with the
          "websocket-extensions":https://github.com/faye/websocket-extensions-node
          framework.
      
          Faye should be attached to an existing HTTP server using the @attach()@
          method. It will handle all requests to paths matching the @mount@ path and
          delegate all other requests to your handlers.
      
          <pre>var http = require('http'),
              faye = require('faye');
      
          var bayeux = new faye.NodeAdapter({mount: '/faye', timeout: 45});
      
          // Handle non-Bayeux requests
          var server = http.createServer(function(request, response) {
            response.writeHead(200, {'Content-Type': 'text/plain'});
            response.end('Hello, non-Bayeux request');
          });
      
          bayeux.attach(server);
          server.listen(8000);</pre>
      ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/site/src/pages/node/���������������������������������������������������������������������0000775�0000000�0000000�00000000000�13711043772�0016224�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/site/src/pages/node/clients.haml���������������������������������������������������������0000664�0000000�0000000�00000005417�13711043772�0020537�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.content
        = partial 'node_navigation'
      
        :textile
          h4. Server-side Node.js clients
      
          You can use Faye clients on the server side to send messages to in-browser
          clients or to other server-side processes. The API is identical to the
          "browser client":/browser.html.
      
          To create a client, just supply the host you want to connect to:
      
          <pre>var client = new faye.Client('http://localhost:8000/faye');</pre>
      
          You can then use @client.subscribe()@ and @client.publish()@ to send
          messages to other clients; see the "browser client":/browser.html
          documentation for more information.
      
          The server has its own client attached to it so you can use the server to
          send messages to browsers. This client has direct access to the server
          without going over HTTP, and is thus more efficient. To send messages
          through the server just use the @#getClient()@ method.
      
          <pre>bayeux.getClient().publish('/email/new', {
            text:       'New email has arrived!',
            inboxSize:  34
          });</pre>
      
          h4. Transport control
      
          When using the client on the server, you can control parts of the transport
          layer that the browser doesn't provide access to. For a start, headers
          added using the @client.setHeader()@ method will be added to WebSocket
          connections, not just regular HTTP requests.
      
          For the transport layer, the server-side client uses the Node.js
          "https":https://nodejs.org/api/https.html and
          "tls":https://nodejs.org/api/tls.html modules to handle HTTPS endpoints. If
          you need to configure anything about the TLS connection, use the `tls`
          option which is passed through to
          "@tls.connect()@":https://nodejs.org/api/tls.html#tls_tls_connect_options_callback.
          For example, to set your own root certificate instead of using the system
          defaults:
      
          <pre>var client = new faye.Client(url, {
            tls: {
              ca: fs.readFileSync('path/to/certificate.pem')
            }
          });</pre>
      
          You can also request that all connections go via an HTTP proxy:
      
          <pre>var client = new faye.Client(url, {
            proxy: 'http://username:password@proxy.example.com'
          });</pre>
      
          You can also set the @http_proxy@ or @https_proxy@ environment variables,
          which will make all Faye client connections use the given proxy by default;
          @http_proxy@ for @http:@ and @ws:@ requests, and @https_proxy@ for @https:@
          and @wss:@ requests.
      
          Finally, the WebSocket transport can be configured to use protocol
          extensions; any extension library compatible with
          "websocket-extensions":https://github.com/faye/websocket-extensions-node
          will work. For example, to add
          "permessage-deflate":https://github.com/faye/permessage-deflate-node:
      
          <pre>var deflate = require('permessage-deflate');
      
          client.addWebsocketExtension(deflate);</pre>
      �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/site/src/pages/node/engines.haml���������������������������������������������������������0000664�0000000�0000000�00000025776�13711043772�0020540�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.content
        = partial 'node_navigation'
      
        :textile
          h4. Engines
      
          In Faye, the 'engine' is the back-end of the server; the engine implements
          the messaging semantics and deals with state and storage, and the server
          maps the Bayeux protocol onto the engine's functionality. See the
          "architecture overview":/architecture.html for more information.
      
          The default engine that's included with Faye stores all state in-process,
          making it fast but making it impossbile to run a single Faye service across
          multiple front-end web servers. The following engines are available as
          separate libraries:
      
          * "faye-redis":https://github.com/faye/faye-redis-node - Uses Redis to store
            state and distribute messages between any number of Faye server processes,
            letting you increase your connection capacity.
          * "faye-redis-sharded":https://github.com/myspace/faye-redis-sharded-node -
            Similar to @faye-redis@ but uses multiple Redis databases. Developed by
            Myspace to help scale their messaging capacity.
          * "faye-couchbase":https://www.npmjs.com/package/faye-couchbase - allows a
            single Faye service to be distributed across many front-end web servers by
            storing state and routing messages through a Couchbase database server
      
          h4. Creating your own engine
      
          The pluggable engine architecture allows anyone to write a new back-end for
          Faye that works the way they want. Engines are passed in when a server is
          instantiated, for example the Redis engine is set up like so:
      
          <pre>var faye      = require('faye'),
              fayeRedis = require('faye-redis');
      
          var server = new faye.NodeAdapter({
            mount:    '/faye',
            timeout:  45,
            engine:   {
              type:   fayeRedis,
              host:   'localhost',
              port:   6379
            }
          });</pre>
      
          The @engine@ field has a @type@ setting that maps to the engine class, and
          further options that are used to configure that particular engine. Faye then
          uses the following API to interact with the engine; this is what you must
          implement if you are writing your own engine.
      
          h4. @Engine.create(proxy, options)@
      
          The object passed in the @type@ field (for example @fayeRedis@ in the above
          example) must respond to @create(proxy, options)@ and return an object that
          implements the rest of the API described below. @options@ is an object
          containing the @engine@ settings supplied by the user, for example in the
          above example it would be @{host: 'localhost', port: 6379}@.
      
          @proxy@ is an object that intermediates between the server and the engine,
          and provides methods the engine needs to communicate with the server. In
          particular it provides the following:
      
          * *@proxy.deliver(clientId, messages)@* - Tells the server to send
            @messages@ to the given @clientId@. Will do nothing if there is no open
            connection for @clientId@, so you should check whether a connection is
            open using @proxy.hasConnection(clientId)@ first. @messages@ is an
            array of hashes representing Bayeux messages.
          * *@proxy.hasConnection(clientId)@* - Returns @true@ iff the server
            currently holds an open @/meta/connect@ request for the given @clientId@.
            This can be used when deciding whether to flush a message queue.
          * *@proxy.generateId()@* - Returns a random valid Faye client ID as a string.
            It does not guarantee that the ID is unique; this is the responsibility of
            the engine.
          * *@proxy.trigger(eventType, *args)@* - Triggers events that the user can
            listen to using @on()@. The engine is required to emit these events at
            certain times as detailed below, e.g.
            @proxy.trigger('disconnect', clientId)@.
          * *@proxy.timeout@* - Returns the server's connection timeout in seconds.
            Can be used to set timeouts for inactive clients.
          * *@proxy.debug, proxy.info, proxy.warn, proxy.error@* - Lets the engine log
            messages using the standard set of log levels, e.g.
            @proxy.info('Created new client: ?', clientId)@. Any @?@ in the message
            are replaced with the values of the other parameters, formatted as
            necessary.
      
          The @proxy@ also emits two events to let you know when a particular client
          connects and disconnects from the server. These correspond with the return
          value of @proxy.hasConnection(clientId)@ changing between @true@ and
          @false@.  You can listen to these events using the @on()@ method.
      
          * *@proxy.on('connection:open', function(clientId) {})@* - Fires when the
            client with ID @clientId@ connects to the server, either by opening a
            socket or sending a @/meta/connect@ request.
          * *@proxy.on('connection:close', function(clientId) {})@* - Fires when the
            client with ID @clientId@ disconnects from the server, either by closing a
            socket or by the server sending a @/meta/connect@ response. Note this does
            not mean the client's session has ended, it just means it does not
            currently have an open network connection to the server.
      
          h4. The Faye engine API
      
          The object returned by @Engine.create(proxy, options)@ must implement the
          following interface to be a valid Faye engine. Note that return values are
          delivered via a callback, which allows engine implementations to be
          asynchronous. Engines do not have to operate asynchronously, but should
          avoid doing anything that would block the event loop for too long.
      
          The engine should assume that all validation required by the Bayeux protocol
          has already been performed by the server for all incoming messages. The
          engine does not need to reimplement any of this, it simply needs to
          implement the messaging semantics.
      
          There are automated tests in the Faye project that you can use to make sure
          your engine conforms to the spec. See the
          "faye-redis":https://github.com/faye/faye-redis-node project for an example
          of using these tests.
      
          h4. @engine.createClient(function(clientId) {})@
      
          Should generate and return a new unique Faye client ID. You can use
          @proxy.generateId()@ to generate valid IDs, but the engine must make sure a
          _unique_ value is assigned. After choosing an ID and allocating any required
          storage for it, the ID should be yielded to the callback.
      
          The engine should call @proxy.trigger('handshake', clientId)@ once a valid
          client ID has been selected.
      
          h4. @engine.destroyClient(clientId, function() {})@
      
          Should destroy any subscriptions for the given @clientId@ and mark the
          @clientId@ as disconnected. No further messages will be sent by the client
          after this happens.
      
          The engine should call @proxy.trigger('disconnect', clientId)@ in the
          current process, and should call @proxy.trigger('close', clientId)@ in _all_
          the Faye server processes, after deleting the client's data. If a callback
          is given it should be called after the client has been deleted.
      
          h4. @engine.clientExists(clientId, function(exists) {})@
      
          Should call the given callback with @true@ if the given @clientId@ exists,
          and @false@ otherwise. This is used by the server when validating incoming
          messages.
      
          h4. @engine.ping(clientId)@
      
          The server calls this to tell the engine a connection has been received for
          the given @clientId@. You can use this as a heartbeat to check clients are
          still alive. No return value is expected.
      
          h4. @engine.subscribe(clientId, channel, function(successful) {})@
      
          Should store a subscription for the given @clientId@ to the given @channel@,
          both of which are strings. If a callback is given, it must be called with
          @true@ or @false@ to indicate whether the subscription was registered
          successfully.
      
          The engine should call @proxy.trigger('subscribe', clientId, channel)@ iff
          the subscription was successful _and_ the client was not previously
          subscribed to the channel.
      
          h4. @engine.unsubscribe(clientId, channel, function(successful) {})@
      
          Should delete a subscription for the given @clientId@ to the given
          @channel@, both of which are strings. If a callback is given, it must be
          called with @true@ or @false@ to indicate whether the subscription was
          destroyed successfully.
      
          The engine should call @proxy.trigger('unsubscribe', clientId, channel)@ iff
          the subscription was destroyed _and_ the client was actually subscribed to
          the channel.
      
          h4. @engine.publish(message, channels)@
      
          Given an object @message@ and an array of channel names in @channels@, the
          engine should deliver the message to all the clients subscribed to the given
          channels. For example, if the message was published to @/msg/foo@ by the
          sender, then @channels@ will be @['/msg/foo', '/msg/*', '/msg/**', '/**']@.
      
          Each message should be delivered to each subscribed client _exactly once_,
          even if the client is subscribed to several channels in the @channels@
          array.  If the client is currently connected (see @proxy.hasConnection()@)
          the engine should use @proxy.deliver()@ to send the message to the client.
          Otherwise the message should be queued until the next time the client
          connects.
      
          On receipt of a message, the engine should call
          @proxy.trigger('publish', message.clientId, message.channel, message.data)@.
      
          Where applicable, the engine must make sure that any published message
          reaches all the clients that should receive it, whichever Faye server
          instance they are connected to. How the message is routed to other Faye
          processes is up to the engine.
      
          h4. @engine.emptyQueue(clientId)@
      
          The server calls this when a client connects to flush any queued messages
          for the client. The engine should check the client is connected, and if
          there are any queued messages for it, use
          @proxy.deliver(clientId, messages)@ to send them to the client.
      
          h4. @engine.disconnect()@
      
          This is called when the server is shut down. The engine should close any
          connections is has to external processes like databases and cancel any
          timeouts it has pending.
      
          h4. Disconnecting inactive clients
      
          When a client makes a connection, the engine will receive a call to
          @engine.ping(clientId)@. This tells the engine the client is still alive.
          Sometimes, clients fail to send an explicit disconnect message when they
          shut down, and the engine must detect such clients and delete them. If a
          client goes much longer than @proxy.timeout@ without sending a ping, the
          engine should do the following in this order:
      
          * Destroy all subscriptions for the client, calling
            @proxy.trigger('unsubscribe', clientId, channel)@ for each subscription
          * Delete the client, calling @proxy.trigger('disconnect', clientId)@ in the
            current process and @proxy.trigger('close', clientId)@ in all processes
          * Delete any other state associated with the client's session
      
          The strategy for cleaning up inactive clients is up to the engine, but the
          important point is it will _not_ be explicitly initiated by the server; the
          engine must make sure this clean-up happens by itself and must trigger
          events so the user can monitor these timeouts.
      ��faye-1.4.0/site/src/pages/node/extensions.haml������������������������������������������������������0000664�0000000�0000000�00000012241�13711043772�0021266�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.content
        = partial 'node_navigation'
      
        :textile
          h4. Extensions
      
          Both the server and client support an extension mechanism that lets you
          intercept messages as they pass in and out. This lets you modify messages
          for any purpose you like, including messages on @/meta/*@ channels that are
          used by the protocol. An extension is just an object that has either an
          @incoming()@ or @outgoing()@ method (or both). These methods should accept a
          message and a callback function, and should call the function with the
          message once they have made any modifications.
      
          Extensions use a callback instead of simply returning the modified message
          since this allows you to use asynchronous logic to make your modifications.
      
          As an example, suppose we want to authenticate subscription messages by
          checking an authentication token against a list we're keeping in a file on
          disk. Clients subscribe to channels by sending a message to the
          @/meta/subscribe@ channel with the channel they want to subscribe in the
          @subscription@ field. Let's say our authentication file contains a JSON
          object that maps channels to required tokens:
      
          <pre>// tokens.json
      
          {
            "/users/jcoglan/updates": "rt6utrb",
            "/artists/mclusky/news":  "99taaec"
          }</pre>
      
          The server can validate subscription messages by checking that they have the
          right auth token attached. By convention, data added by extensions is stored
          in the message's @ext@ field.
      
          <pre>var fs = require('fs');
      
          var serverAuth = {
            incoming: function(message, callback) {
              // Let non-subscribe messages through
              if (message.channel !== '/meta/subscribe')
                return callback(message);
      
              // Get subscribed channel and auth token
              var subscription = message.subscription,
                  msgToken     = message.ext && message.ext.authToken;
      
              // Find the right token for the channel
              this._fileContent = this._fileContent || fs.readFileSync('./tokens.json');
      
              var registry = JSON.parse(this._fileContent.toString()),
                  token    = registry[subscription];
      
              // Add an error if the tokens don't match
              if (token !== msgToken)
                message.error = 'Invalid subscription auth token';
      
              // Call the server back now we're done
              callback(message);
            }
          };
      
          bayeux.addExtension(serverAuth);</pre>
      
          If you add an @error@ property to a message, the server will not process the
          message further and will simply return it to the sender, effectively
          blocking the subscription attempt. You should always make sure your
          extension calls the @callback@, as failing to do so could block delivery of
          other messages in the same request.
      
          *When implementing authentication, remember that a message published to
          channel @/foo/bar/qux@ will be routed to subscriptions to @/foo/bar/qux@,
          @/foo/bar/*@, @/foo/bar/**@, @/foo/**@ and @/**@. Take appropriate measures
          in your extensions to correctly authenticate subscriptions.*
      
          On the client side, you'll need to make sure the client sends the right auth
          token to satisfy the server. We do this by adding an _outgoing_ extension on
          the client side.
      
          <pre>var clientAuth = {
            outgoing: function(message, callback) {
              // Again, leave non-subscribe messages alone
              if (message.channel !== '/meta/subscribe')
                return callback(message);
      
              // Add ext field if it's not present
              if (!message.ext) message.ext = {};
      
              // Set the auth token
              message.ext.authToken = 'rt6utrb';
      
              // Carry on and send the message to the server
              callback(message);
            }
          };
      
          client.addExtension(clientAuth);</pre>
      
          If an extension has an @added()@ method, that will be called when the
          extension is added to its host. To remove an extension, call:
      
          <pre>// Calls extension.removed() if defined
          hostObject.removeExtension(extension);</pre>
      
          h4. Accessing request data
      
          On the server side, you can gain access to details of the request in which
          the message was delivered, by writing extensions with 3 arguments:
      
          <pre>server.addExtension({
            incoming: function(message, request, callback) {
              if (request && request.headers.origin !== 'http://example.com') {
                message.error = '403::Forbidden origin';
              }
              callback(message);
            }
          });</pre>
      
          If an extension method has 3 arguments, the second argument will be a Node
          request object, if the message was delivered by HTTP or WebSocket. For
          messages sent by a local server-side client, @request@ will be @null@.
      
          You should not use the @request@ object to send a response yourself; you
          should only use its properties to make a decision about how to handle the
          message, for example by checking the @Origin@ or @Cookie@ headers.
      
          *If you use @Cookie@ for authorization, you must "implement CSRF
          protection":/security/csrf.html since Faye allows cross-origin connections.*
      
          To write extensions you'll need to know what kinds of messages are used by
          the Bayeux protocol; see "the specification":https://docs.cometd.org/current/reference/#_bayeux
          for more details.
      ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/site/src/pages/node/monitoring.haml������������������������������������������������������0000664�0000000�0000000�00000011040�13711043772�0021250�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.content
        = partial 'node_navigation'
      
        :textile
          h4. Monitoring
      
          Since version 0.7, Faye includes an API for monitoring activity going on
          within the "engine":/node/engines.html. This means you can attach event
          listeners to monitor the creation and destruction of client sessions, find
          out when clients subscribe and unsubscribe from channels, and watch published
          messages.
      
          You attach an event listener to your server like so:
      
          <pre>var bayeux = new Faye.NodeAdapter({mount: '/faye', timeout: 45})
      
          bayeux.on('handshake', function(clientId) {
            // event listener logic
          })</pre>
      
          The available events are:
      
          * *@handshake [clientId]@* - Triggered when a new client connects and is
            issued with an ID.
          * *@subscribe [clientId, channel]@* - Triggered when a client subscribes to
            a channel. This does not fire if a @/meta/subscribe@ message is received
            for a subscription that already exists.
          * *@unsubscribe [clientId, channel]@* - Triggered when a client unsubscribes
            from a channel. This can fire either because the client explicitly sent
            a @/meta/unsubscribe@ message, or because its session was timed out by
            the server.
          * *@publish [clientId, channel, data]@* - Triggered when a non-@/meta/**@
            message is published. Includes the client ID of the publisher (which may
            be @null@), the channel the message was sent to and the data payload.
          * *@disconnect [clientId]@* - Triggered when a client session ends, either
            because it explicitly sent a @/meta/disconnect@ message or because its
            session was timed out by the server.
      
          h4. Events versus extensions
      
          On the surface, this API seems similar to the facilities provided by the
          "extensions":/node/extensions.html system. Historically, a common use case
          for extensions has been to monitor what's going on in your Faye service. The
          difference is that extensions operate within the protocol layer, and
          monitoring events are attached at a lower level: they come from the engine
          and are independent of the protocol. See the "architecture overview":/architecture.html
          for more information.
      
          This means that they can notify you about events that are not caused by
          incoming messages. For example, although clients are supposed to explicitly
          end their session by sending a @/meta/disconnect@ message, they often fail
          to do this and their session is ended when the server notices that no messages
          have been received from that client for a certain amount of time. In this
          case a @disconnect@ event is emitted, along with @unsubscribe@ events for
          all the client's subscriptions, even though no message was received.
      
          The flip side of this is that these events cannot use or modify any data
          being transmitted via the protocol. If you need to implement authentication,
          or use message extension data, then you should write an extension. If not,
          these events provide a lightweight way to monitor messaging activity.
      
          h4. Monitoring in multi-process servers
      
          If you're using the Redis engine to run multiple front-end server processes,
          you should keep in mind that monitoring events only fire once, in the process
          that handled the relevant event. For example if a client subscribes to a
          channel, the @subscribe@ event will only fire in the process that handled
          the @/meta/subscribe@ message, not in any other process.
      
          This has implications if you're storing state derived from this activity.
          For example, if you're storing subscriber counts for channels in memory but
          running multiple Faye servers, each server will have its own local and
          incorrect value of the count for a channel.
      
          h4. A word of warning
      
          While this monitoring API is certainly useful for some applications, you
          are advised to think carefully before using it to implement decisions within
          your application. For example, many people building a chat room initially
          think that listening for @disconnect@ is a good way to monitor presence. In
          reality, a user in your app will create many Faye clients as they navigate
          from page to page, so a Faye session may not have a one-to-one relationship
          with an entity in your app.
      
          You should consider whether it would be better to implement your own
          heartbeat/timeout system on top of Faye for your use case before diving in
          and using monitoring events for this. Binding your application logic to Faye
          client IDs may create unwelcome couplings between your application and your
          network communication stack.
      ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/site/src/pages/node/websockets.haml������������������������������������������������������0000664�0000000�0000000�00000000707�13711043772�0021244�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.content
        = partial 'node_navigation'
      
        :textile
          h4. WebSockets for Node
      
          Since version 0.5, Faye has supported WebSockets as a network transport for
          sending messages to the browser. The code that handles this is decoupled
          from the rest of the library and can be used to make your own WebSocket
          applications.
      
          These classes are available as a stand-alone library,
          "faye-websocket":https://github.com/faye/faye-websocket-node
      ���������������������������������������������������������faye-1.4.0/site/src/pages/ruby.haml�����������������������������������������������������������������0000664�0000000�0000000�00000010103�13711043772�0017116�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.content
        = partial 'ruby_navigation'
      
        :textile
          h4. Setting up
      
          All Faye clients need a central messaging server to communicate with; the
          server records which clients are subscribed to which channels and handles
          routing of messages between clients. Faye is just like any other Rack app,
          and you start it by making a @rackup@ file:
      
          <pre># config.ru
          
          require 'faye'
          bayeux = Faye::RackAdapter.new(:mount => '/faye', :timeout => 25)
          run bayeux</pre>
      
          Faye runs on MRI and JRuby, and requires at least Ruby 1.9.
      
          The @RackAdapter@ class supports these options during setup:
      
          * *@:mount@* - the path on the host at which the Faye service is available.
            In this example, clients would connect to @http://localhost:9292/faye@ to
            talk to the server. The server will handle _any_ request whose path begins
            with the @:mount@ path; this is so that it can interoperate with clients
            that use different request paths for different channels.
          * *@:timeout@* - the maximum time to hold a connection open before returning
            the response. This is given in seconds and must be smaller than the
            timeout on your frontend webserver. Faye uses Thin as its webserver, whose
            default timeout is 30 seconds.
          * *@:engine@* - (optional) the type and parameters for the engine you want
            to use - see the "engines documentation":/ruby/engines.html
          * *@:ping@* - (optional) how often, in seconds, to send keep-alive ping
            messages over WebSocket and EventSource connections. Use this if your Faye
            server will be accessed through a proxy that kills idle connections.
      
          It also allows WebSocket extensions to be plugged in. For example, to enable
          "permessage-deflate":https://github.com/faye/permessage-deflate-ruby for
          supporting clients:
      
          <pre>require 'faye'
          require 'permessage_deflate'
      
          bayeux = Faye::RackAdapter.new(:mount => '/faye', :timeout => 25)
          bayeux.add_websocket_extension(PermessageDeflate)</pre>
      
          You can use any extension that's compatible with the
          "websocket-extensions":https://github.com/faye/websocket-extensions-ruby
          framework.
      
          Faye can also be setup as Rack middleware, for example in front of a Sinatra
          application:
      
          <pre># config.ru
      
          require 'faye'
          require File.expand_path('../app', __FILE__)
      
          use Faye::RackAdapter, :mount => '/faye', :timeout => 25
      
          run Sinatra::Application</pre>
      
          When set up as middleware, extensions can be added using a block that is
          called with the middleware instance once it is created:
      
          <pre>use Faye::RackAdapter, :mount => '/faye', :timeout => 25 do |bayeux|
            bayeux.add_extension(MyExtension.new)
            bayeux.add_websocket_extension(PermessageDeflate)
          end</pre>
      
          h4. Running your Faye application
      
          Faye supports the same set of servers as
          "faye-websocket":https://github.com/faye/faye-websocket-ruby. The
          faye-websocket documentation has plenty of information on running Rack
          applications with these servers (see "Running your socket
          application":https://github.com/faye/faye-websocket-ruby#running-your-socket-application),
          but it's worth restating here that for some servers you must tell
          @Faye::WebSocket@ which web server you're using before booting the
          application, so it can load adapters for it. For example to load the Thin
          adapter:
      
          <pre># config.ru
      
          require 'faye'
          Faye::WebSocket.load_adapter('thin')
      
          app = Faye::RackAdapter.new(:mount => '/faye', :timeout => 25)
      
          run app</pre>
      
          Some servers supported by Faye, such as Passenger, use a multi-process model
          rather than using threads or events in the same process. For those servers,
          the default in-memory "engine":/ruby/engines.html will not work; you should
          use a multi-process engine such as the Redis backend.
      
          Faye uses "MultiJson":http://rubygems.org/gems/multi_json to allow fast JSON
          processing on both MRI and JRuby. For speed, we recommand using the
          "@:oj@":http://rubygems.org/gems/oj engine on MRI and the
          "@:gson@":http://rubygems.org/gems/gson engine on JRuby.
      �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/site/src/pages/ruby/���������������������������������������������������������������������0000775�0000000�0000000�00000000000�13711043772�0016260�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/site/src/pages/ruby/clients.haml���������������������������������������������������������0000664�0000000�0000000�00000007074�13711043772�0020574�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.content
        = partial 'ruby_navigation'
      
        :textile
          h4. Server-side Ruby clients
      
          You can use Faye clients on the server side to send messages to in-browser
          clients or to other server-side processes. The API is identical to the
          "browser client":/browser.html.
      
          To create a client, just supply the host you want to connect to:
      
          <pre>client = Faye::Client.new('http://localhost:9292/faye')</pre>
      
          You can then use @client.subscribe()@ and @client.publish()@ to send
          messages to other clients; the API is similar to the "browser client":/browser.html
          only you need to run the client inside EventMachine:
      
          <pre>require 'eventmachine'
      
          EM.run {
            client = Faye::Client.new('http://localhost:9292/faye')
      
            client.subscribe('/foo') do |message|
              puts message.inspect
            end
      
            client.publish('/foo', 'text' => 'Hello world')
          }</pre>
      
          Note that the Ruby client uses @EventMachine::Deferrable@ instead of
          "promises":http://promisesaplus.com/, for example you detect success and
          failure of a publication like so:
      
          <pre>publication = client.publish('/foo', 'text' => 'Hello world')
      
          publication.callback do
            puts 'Message received by server!'
          end
      
          publication.errback do |error|
            puts 'There was a problem: ' + error.message
          end</pre>
      
          If you need to set custom headers to talk to your Bayeux server, use the
          @set_header@ method:
      
          <pre>client.set_header('Authorization', 'OAuth abcd-1234')</pre>
      
          The server has its own client attached to it so you can use the server to
          send messages to browsers. This client has direct access to the server
          without going over HTTP, and is thus more efficient. To send messages
          through the server just use the @#get_client@ method.
      
          <pre>bayeux.get_client.publish('/email/new', {
            'text'      => 'New email has arrived!',
            'inboxSize' => 34
          })</pre>
      
          h4. Transport control
      
          When using the client on the server, you can control parts of the transport
          layer that the browser doesn't provide access to. For a start, headers
          added using the @client.set_header@ method will be added to WebSocket
          connections, not just regular HTTP requests.
      
          From version 1.4.0 onwards, @Faye::Client@ uses the underlying transport
          libraries ("em-http-request":https://rubygems.org/gems/em-http-request and
          "faye-websocket":https://rubygems.org/gems/em-http-request, both based on
          "EventMachine":https://rubygems.org/gems/eventmachine) to perform TLS
          certificate validation by default for HTTPS endpoints. If you don't want
          this behaviour, you can turn it off via the @:tls@ option:
      
          <pre>client = Faye::Client.new(url, {
            :tls => { :verify_peer => false }
          })</pre>
      
          You can also request that all connections go via an HTTP proxy:
      
          <pre>client = Faye::Client.new(url, {
            :proxy => 'http://username:password@proxy.example.com'
          })</pre>
      
          You can also set the @http_proxy@ or @https_proxy@ environment variables,
          which will make all Faye client connections use the given proxy by default;
          @http_proxy@ for @http:@ and @ws:@ requests, and @https_proxy@ for @https:@
          and @wss:@ requests.
      
          Finally, the WebSocket transport can be configured to use protocol
          extensions; any extension library compatible with
          "websocket-extensions":https://github.com/faye/websocket-extensions-ruby
          will work. For example, to add
          "permessage-deflate":https://github.com/faye/permessage-deflate-ruby:
      
          <pre>require 'permessage_deflate'
      
          client.add_websocket_extension(PermessageDeflate)</pre>
      ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/site/src/pages/ruby/engines.haml���������������������������������������������������������0000664�0000000�0000000�00000024753�13711043772�0020566�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.content
        = partial 'ruby_navigation'
      
        :textile
          h4. Engines
      
          In Faye, the 'engine' is the back-end of the server; the engine implements
          the messaging semantics and deals with state and storage, and the server
          maps the Bayeux protocol onto the engine's functionality. See the
          "architecture overview":/architecture.html for more information.
      
          The default engine that's included with Faye stores all state in-process,
          making it fast but making it impossbile to run a single Faye service across
          multiple front-end web servers. The following engines are available as
          separate libraries:
      
          * "faye-redis":https://github.com/faye/faye-redis-ruby - Uses Redis to store
            state and distribute messages between any number of Faye server processes,
            letting you increase your connection capacity.
      
          h4. Creating your own engine
      
          The pluggable engine architecture allows anyone to write a new back-end for
          Faye that works the way they want. Engines are passed in when a server is
          instantiated, for example the Redis engine is set up like so:
      
          <pre>server = Faye::RackAdapter.new(
            :mount   => '/faye',
            :timeout => 25,
            :engine  => {
              :type  => Faye::Redis,
              :host  => 'localhost',
              :port  => 6379
            }
          )</pre>
      
          The @:engine@ field has a @:type@ setting that maps to the engine class, and
          further options that are used to configure that particular engine. Faye then
          uses the following API to interact with the engine; this is what you must
          implement if you are writing your own engine.
      
          h4. @Engine.create(proxy, options)@
      
          The object passed in the @:type@ field (for example @Faye::Redis@ in the
          above example) must respond to @create(proxy, options)@ and return an object
          that implements the rest of the API described below. @options@ is a hash
          containing the @:engine@ settings supplied by the user, for example in the
          above example it would be @{:host => 'localhost', :port => 6379}@.
      
          @proxy@ is an object that intermediates between the server and the engine,
          and provides methods the engine needs to communicate with the server. In
          particular it provides the following:
      
          * *@proxy.deliver(client_id, messages)@* - Tells the server to send
            @messages@ to the given @client_id@. Will do nothing if there is no open
            connection for @client_id@, so you should check whether a connection is
            open using @proxy.has_connection?(client_id)@ first. @messages@ is an
            array of hashes representing Bayeux messages.
          * *@proxy.has_connection?(client_id)@* - Returns @true@ iff the server
            currently holds an open @/meta/connect@ request for the given @client_id@.
            This can be used when deciding whether to flush a message queue.
          * *@proxy.generate_id@* - Returns a random valid Faye client ID as a string.
            It does not guarantee that the ID is unique; this is the responsibility of
            the engine.
          * *@proxy.trigger(event_type, *args)@* - Triggers events that the user can
            listen to using @on()@. The engine is required to emit these events at
            certain times as detailed below, e.g.
            @proxy.trigger(:disconnect, client_id)@.
          * *@proxy.timeout@* - Returns the server's connection timeout in seconds.
            Can be used to set timeouts for inactive clients.
          * *@proxy.debug, proxy.info, proxy.warn, proxy.error@* - Lets the engine log
            messages using the standard set of log levels, e.g.
            @proxy.info('Created new client: ?', client_id)@. Any @?@ in the message
            are replaced with the values of the other parameters, formatted as
            necessary.
      
          The @proxy@ also emits two events to let you know when a particular client
          connects and disconnects from the server. These correspond with the return
          value of @proxy.has_connection?(client_id)@ changing between @true@ and
          @false@. You can listen to these events using the @on()@ method.
      
          * *@proxy.on('connection:open') { |client_id| }@* - Fires when the
            client with ID @client_id@ connects to the server, either by opening a
            socket or sending a @/meta/connect@ request.
          * *@proxy.on('connection:close') { |client_id| }@* - Fires when the
            client with ID @client_id@ disconnects from the server, either by closing
            a socket or by the server sending a @/meta/connect@ response. Note this
            does not mean the client's session has ended, it just means it does not
            currently have an open network connection to the server.
      
          h4. The Faye engine API
      
          The object returned by @Engine.create(proxy, options)@ must implement the
          following interface to be a valid Faye engine. Note that return values are
          delivered via a block, which allows engine implementations to be
          asynchronous. Engines do not have to operate asynchronously, but should
          avoid doing anything that would block the event loop for too long.
      
          The engine should assume that all validation required by the Bayeux protocol
          has already been performed by the server for all incoming messages. The
          engine does not need to reimplement any of this, it simply needs to
          implement the messaging semantics.
      
          There are automated tests in the Faye project that you can use to make sure
          your engine conforms to the spec. See the
          "faye-redis":https://github.com/faye/faye-redis-ruby project for an example
          of using these tests.
      
          h4. @engine.create_client { |client_id| ... }@
      
          Should generate and return a new unique Faye client ID. You can use
          @proxy.generate_id@ to generate valid IDs, but the engine must make sure a
          _unique_ value is assigned. After choosing an ID and allocating any required
          storage for it, the ID should be yielded to the block.
      
          The engine should call @proxy.trigger(:handshake, client_id)@ once a valid
          client ID has been selected.
      
          h4. @engine.destroy_client(client_id) { ... }@
      
          Should destroy any subscriptions for the given @client_id@ and mark the
          @client_id@ as disconnected. No further messages will be sent by the client
          after this happens.
      
          The engine should call @proxy.trigger(:disconnect, client_id)@ in the
          current process, and should call @proxy.trigger(:close, client_id)@ in _all_
          the Faye server processes, after deleting the client's data. If a block is
          given it should be called after the client has been deleted.
      
          h4. @engine.client_exists(client_id) { |exists| ... }@
      
          Should call the given block with @true@ if the given @client_id@ exists, and
          @false@ otherwise. This is used by the server when validating incoming
          messages.
      
          h4. @engine.ping(client_id)@
      
          The server calls this to tell the engine a connection has been received for
          the given @client_id@. You can use this as a heartbeat to check clients are
          still alive. No return value is expected.
      
          h4. @engine.subscribe(client_id, channel) { |successful| ... }@
      
          Should store a subscription for the given @client_id@ to the given
          @channel@, both of which are strings. If a block is given, it must be called
          with @true@ or @false@ to indicate whether the subscription was registered
          successfully.
      
          The engine should call @proxy.trigger(:subscribe, client_id, channel)@ iff
          the subscription was successful _and_ the client was not previously
          subscribed to the channel.
      
          h4. @engine.unsubscribe(client_id, channel) { |successful| ... }@
      
          Should delete a subscription for the given @client_id@ to the given
          @channel@, both of which are strings. If a block is given, it must be called
          with @true@ or @false@ to indicate whether the subscription was destroyed
          successfully.
      
          The engine should call @proxy.trigger(:unsubscribe, client_id, channel)@ iff
          the subscription was destroyed _and_ the client was actually subscribed to
          the channel.
      
          h4. @engine.publish(message, channels)@
      
          Given a hash in @message@ and an array of channel names in @channels@, the
          engine should deliver the message to all the clients subscribed to the given
          channels. For example, if the message was published to @/msg/foo@ by the
          sender, then @channels@ will be @['/msg/foo', '/msg/*', '/msg/**', '/**']@.
      
          Each message should be delivered to each subscribed client _exactly once_,
          even if the client is subscribed to several channels in the @channels@
          array.  If the client is currently connected (see @proxy.has_connection?@)
          the engine should use @proxy.deliver@ to send the message to the client.
          Otherwise the message should be queued until the next time the client
          connects.
      
          On receipt of a message, the engine should call
          @proxy.trigger(:publish, message['clientId'], message['channel'], message['data'])@.
      
          Where applicable, the engine must make sure that any published message
          reaches all the clients that should receive it, whichever Faye server
          instance they are connected to. How the message is routed to other Faye
          processes is up to the engine.
      
          h4. @engine.empty_queue(client_id)@
      
          The server calls this when a client connects to flush any queued messages
          for the client. The engine should check the client is connected, and if
          there are any queued messages for it, use
          @proxy.deliver(client_id, messages)@ to send them to the client.
      
          h4. @engine.disconnect@
      
          This is called when the server is shut down. The engine should close any
          connections is has to external processes like databases and cancel any
          timeouts it has pending.
      
          h4. Disconnecting inactive clients
      
          When a client makes a connection, the engine will receive a call to
          @engine.ping(client_id)@. This tells the engine the client is still alive.
          Sometimes, clients fail to send an explicit disconnect message when they
          shut down, and the engine must detect such clients and delete them. If a
          client goes much longer than @proxy.timeout@ without sending a ping, the
          engine should do the following in this order:
      
          * Destroy all subscriptions for the client, calling
            @proxy.trigger(:unsubscribe, client_id, channel)@ for each subscription
          * Delete the client, calling @proxy.trigger(:disconnect, client_id)@ in the
            current process and @proxy.trigger(:close, client_id)@ in all processes
          * Delete any other state associated with the client's session
      
          The strategy for cleaning up inactive clients is up to the engine, but the
          important point is it will _not_ be explicitly initiated by the server; the
          engine must make sure this clean-up happens by itself and must trigger
          events so the user can monitor these timeouts.
      ���������������������faye-1.4.0/site/src/pages/ruby/extensions.haml������������������������������������������������������0000664�0000000�0000000�00000012160�13711043772�0021322�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.content
        = partial 'ruby_navigation'
      
        :textile
          h4. Extensions
      
          Both the server and client support an extension mechanism that lets you
          intercept messages as they pass in and out. This lets you modify messages
          for any purpose you like, including messages on @/meta/*@ channels that are
          used by the protocol. An extension is just an object that has either an
          @incoming()@ or @outgoing()@ method (or both). These methods should accept a
          message and a callback function, and should call the function with the
          message once they have made any modifications.
      
          Extensions use a callback instead of simply returning the modified message
          since this allows you to use asynchronous logic to make your modifications.
      
          As an example, suppose we want to authenticate subscription messages by
          checking an authentication token against a list we're keeping in a file on
          disk. Clients subscribe to channels by sending a message to the
          @/meta/subscribe@ channel with the channel they want to subscribe in the
          @subscription@ field. Let's say our authentication file contains a JSON
          object that maps channels to required tokens:
      
          <pre>// tokens.json
      
          {
            "/users/jcoglan/updates": "rt6utrb",
            "/artists/mclusky/news":  "99taaec"
          }</pre>
      
          The server can validate subscription messages by checking that they have the
          right auth token attached. By convention, data added by extensions is stored
          in the message's @ext@ field.
      
          <pre>class ServerAuth
            def incoming(message, callback)
              # Let non-subscribe messages through
              unless message['channel'] == '/meta/subscribe'
                return callback.call(message)
              end
      
              # Get subscribed channel and auth token
              subscription = message['subscription']
              msg_token    = message['ext'] && message['ext']['authToken']
      
              # Find the right token for the channel
              @file_content ||= File.read('./tokens.json')
      
              registry = JSON.parse(@file_content)
              token    = registry[subscription]
      
              # Add an error if the tokens don't match
              if token != msg_token
                message['error'] = 'Invalid subscription auth token'
              end
      
              # Call the server back now we're done
              callback.call(message)
            end
          end
      
          bayeux.add_extension(ServerAuth.new)</pre>
      
          If you add an @error@ property to a message, the server will not process the
          message further and will simply return it to the sender, effectively
          blocking the subscription attempt. You should always make sure your
          extension calls the @callback@, as failing to do so could block delivery of
          other messages in the same request.
      
          *When implementing authentication, remember that a message published to
          channel @/foo/bar/qux@ will be routed to subscriptions to @/foo/bar/qux@,
          @/foo/bar/*@, @/foo/bar/**@, @/foo/**@ and @/**@. Take appropriate measures
          in your extensions to correctly authenticate subscriptions.*
      
          On the client side, you'll need to make sure the client sends the right auth
          token to satisfy the server. We do this by adding an _outgoing_ extension on
          the client side.
      
          <pre>class ClientAuth
            def outgoing(message, callback)
              # Again, leave non-subscribe messages alone
              unless message['channel'] == '/meta/subscribe'
                return callback.call(message)
              end
      
              # Add ext field if it's not present
              message['ext'] ||= {}
      
              # Set the auth token
              message['ext']['authToken'] = 'rt6utrb'
      
              # Carry on and send the message to the server
              callback.call(message)
            end
          end
      
          client.add_extension(ClientAuth.new)</pre>
      
          If an extension has an @added()@ method, that will be called when the
          extension is added to its host. To remove an extension, call:
      
          <pre># Calls extension.removed() if defined
          host_object.remove_extension(extension)</pre>
      
          h4. Accessing request data
      
          On the server side, you can gain access to details of the request in which
          the message was delivered, by writing extensions with 3 arguments:
      
          <pre>class ServerExt
            def incoming(message, request, callback)
              if request && request.env['HTTP_ORIGIN'] != 'http://example.com'
                message['error'] = '403::Forbidden origin'
              end
              callback.call(message)
            end
          end</pre>
      
          If an extension method has 3 arguments, the second argument will be a
          @Rack::Request@ object, if the message was delivered by HTTP or WebSocket.
          For messages sent by a local server-side client, @request@ will be @nil@.
      
          You should not use the @request@ object to send a response yourself; you
          should only use its properties to make a decision about how to handle the
          message, for example by checking the @Origin@ or @Cookie@ headers.
      
          *If you use @Cookie@ for authorization, you must "implement CSRF
          protection":/security/csrf.html since Faye allows cross-origin connections.*
      
          To write extensions you'll need to know what kinds of messages are used by
          the Bayeux protocol; see "the specification":https://docs.cometd.org/current/reference/#_bayeux
          for more details.
      ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/site/src/pages/ruby/monitoring.haml������������������������������������������������������0000664�0000000�0000000�00000011650�13711043772�0021313�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.content
        = partial 'ruby_navigation'
      
        :textile
          h4. Monitoring
      
          Since version 0.7, Faye includes an API for monitoring activity going on
          within the "engine":/ruby/engines.html. This means you can attach event
          listeners to monitor the creation and destruction of client sessions, find
          out when clients subscribe and unsubscribe from channels, and watch published
          messages.
      
          You attach an event listener to your server like so:
      
          <pre>bayeux = Faye::RackAdapter.new(:mount => '/faye', :timeout => 25)
      
          bayeux.on(:handshake) do |client_id|
            # event listener logic
          end</pre>
      
          When you insert @RackAdapter@ as Rack middleware from @config.ru@ or Rails
          @config/application.rb@, you can provide a block that gives you access to
          the adapter after it has been instantiated.
      
          <pre>use Faye::RackAdapter, {:mount => '/faye', :timeout => 25} do |bayeux|
            bayeux.on(:handshake) do |client_id|
              # event listener logic
            end
          end</pre>
      
          The available events are:
      
          * *@:handshake [client_id]@* - Triggered when a new client connects and is
            issued with an ID.
          * *@:subscribe [client_id, channel]@* - Triggered when a client subscribes to
            a channel. This does not fire if a @/meta/subscribe@ message is received
            for a subscription that already exists.
          * *@:unsubscribe [client_id, channel]@* - Triggered when a client unsubscribes
            from a channel. This can fire either because the client explicitly sent
            a @/meta/unsubscribe@ message, or because its session was timed out by
            the server.
          * *@:publish [client_id, channel, data]@* - Triggered when a non-@/meta/**@
            message is published. Includes the client ID of the publisher (which may
            be @nil@), the channel the message was sent to and the data payload.
          * *@:disconnect [client_id]@* - Triggered when a client session ends, either
            because it explicitly sent a @/meta/disconnect@ message or because its
            session was timed out by the server.
      
          h4. Events versus extensions
      
          On the surface, this API seems similar to the facilities provided by the
          "extensions":/ruby/extensions.html system. Historically, a common use case
          for extensions has been to monitor what's going on in your Faye service. The
          difference is that extensions operate within the protocol layer, and
          monitoring events are attached at a lower level: they come from the engine
          and are independent of the protocol. See the "architecture overview":/architecture.html
          for more information.
      
          This means that they can notify you about events that are not caused by
          incoming messages. For example, although clients are supposed to explicitly
          end their session by sending a @/meta/disconnect@ message, they often fail
          to do this and their session is ended when the server notices that no messages
          have been received from that client for a certain amount of time. In this
          case a @:disconnect@ event is emitted, along with @:unsubscribe@ events for
          all the client's subscriptions, even though no message was received.
      
          The flip side of this is that these events cannot use or modify any data
          being transmitted via the protocol. If you need to implement authentication,
          or use message extension data, then you should write an extension. If not,
          these events provide a lightweight way to monitor messaging activity.
      
          h4. Monitoring in multi-process servers
      
          If you're using the Redis engine to run multiple front-end server processes,
          you should keep in mind that monitoring events only fire once, in the process
          that handled the relevant event. For example if a client subscribes to a
          channel, the @:subscribe@ event will only fire in the process that handled
          the @/meta/subscribe@ message, not in any other process.
      
          This has implications if you're storing state derived from this activity.
          For example, if you're storing subscriber counts for channels in memory but
          running multiple Faye servers, each server will have its own local and
          incorrect value of the count for a channel.
      
          h4. A word of warning
      
          While this monitoring API is certainly useful for some applications, you
          are advised to think carefully before using it to implement decisions within
          your application. For example, many people building a chat room initially
          think that listening for @:disconnect@ is a good way to monitor presence. In
          reality, a user in your app will create many Faye clients as they navigate
          from page to page, so a Faye session may not have a one-to-one relationship
          with an entity in your app.
      
          You should consider whether it would be better to implement your own
          heartbeat/timeout system on top of Faye for your use case before diving in
          and using monitoring events for this. Binding your application logic to Faye
          client IDs may create unwelcome couplings between your application and your
          network communication stack.
      ����������������������������������������������������������������������������������������faye-1.4.0/site/src/pages/ruby/websockets.haml������������������������������������������������������0000664�0000000�0000000�00000000707�13711043772�0021300�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.content
        = partial 'ruby_navigation'
      
        :textile
          h4. WebSockets for Ruby
      
          Since version 0.5, Faye has supported WebSockets as a network transport for
          sending messages to the browser. The code that handles this is decoupled
          from the rest of the library and can be used to make your own WebSocket
          applications.
      
          These classes are available as a stand-alone library,
          "faye-websocket":https://github.com/faye/faye-websocket-ruby
      ���������������������������������������������������������faye-1.4.0/site/src/pages/security.haml�������������������������������������������������������������0000664�0000000�0000000�00000006365�13711043772�0020023�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.content
        = partial 'security_navigation'
      
        :textile
          h4. Securing your realtime applications
      
          Like any web-accessible service, Faye must be protected against malicious
          usage by attackers. Out of the box, it is a cross-domain-accessible server
          with no restrictions on subscribing and publishing, but its extension system
          allows you to easily impose restrictions appropriate to your application.
      
          Though written primarily for Faye, this guide contains advice that affects
          many realtime and socket-based applications. The core concerns with such
          applications are:
      
          * Can a client get access to data it should not have access to?
          * Can a client trust the origin of the messages it receives?
      
          The details of these questions will depend on your application, but the
          following is a set of general guidelines for avoiding broad classes of
          mistakes. It is not prescriptive, in that the solutions presented here are
          not how you _have to_ implement things. They simply try to illustrate
          patterns of security risks and possible solutions.
      
          As with all web applications, it is crucial to remember that any program
          with Internet access, including server-side scripts and in-browser
          JavaScript code on other domains, can send requests to your server. It is up
          to you to protect it and your users from harm.
      
          h4. What is Faye?
      
          Faye is an implementation of the "Bayeux":https://docs.cometd.org/current/reference/#_bayeux
          protocol. Clients send messages to each other via a central server, by
          sending JSON messages over various flavours of HTTP-based transport,
          including WebSocket, EventSource, XMLHttpRequest, CORS and JSON-P. Clients
          that run in the browser are not constrained by the same-origin policy.
      
          Messages are routed using subscriptions. When a client publishes a message,
          the server determines which clients are subscribed to the message's
          @channel@ and forwards the message to them verbatim. This means the
          _whole wire message is forwarded_, not just the @data@ field containing the
          application payload.
      
          A special set of channels whose names begin with @/meta/@ are used for
          operating the protocol itself, and messages sent to these channels are
          never forwarded to other clients.
      
          Messages pass through extensions on their way into and out of the clients
          and server. Data required by extensions is typically sent in the message's
          @ext@ field. Any changes made to a message by a server-side extension will
          be reflected in the messages forward to subscribed clients.
      
          Authorization credentials embedded in messages should be deleted from them
          by server-side extensions to prevent these credentials being forwarded to
          subscribed clients.
      
          h4. Transport layer security
      
          It should go without saying that if you want to keep the messages or any
          aspect of the HTTP transport layer secret from potential eavesdroppers, you
          should access the Faye server over a secure connection. You should use
          Node's @https@ module to create a server, or run Thin in SSL mode, or you
          can use an SSL terminator like STunnel in front of your Faye server.
      
          On the client side, you should make sure you use an @https:@ URL for the
          client to connect to.
      ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/site/src/pages/security/�����������������������������������������������������������������0000775�0000000�0000000�00000000000�13711043772�0017146�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/site/src/pages/security/authentication.haml����������������������������������������������0000664�0000000�0000000�00000020071�13711043772�0023030�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.content
        = partial 'security_navigation'
      
        :textile
          h4. Authentication
      
          Outside the trivial case of just allowing the server to publish, we need a
          way to authenticate the client connections and decide what each one is
          allowed to do. Before we get into that, it is worth reviewing the mechanics
          of the transport mechanisms Faye uses:
      
          * *WebSocket* - bidirectional message-passing socket between the client and
            server. Initiated using an HTTP @GET@ request with an @Upgrade: websocket@
            header. Can be opened to any origin.
          * *EventSource* - unidirectional socket that delivers from the server to the
            client. Initiated by the client using a @GET@ request with the
            @Accept: text/event-stream@ header. Obeys the same-origin policy.
          * *XMLHttpRequest (including CORS)* - request/response HTTP client, with
            some restrictions. Cannot set various headers, e.g. @Cookie@, @Host@,
            @Origin@, @Referer@. Can only read the response if the server grants
            permission through the @Access-Control-Allow-Origin@ header. May use a
            pre-flight @OPTIONS@ request to determine whether the request can be sent
            at all.
          * *JSON-P* - cross-domain @GET@ requests based on injecting a @script@ tag
            and the server enclosing some JSON data in a JavaScript function call. No
            origin restrictions, @GET@ only, no access to headers.
      
          All of these transports have in common that the browser will attach any
          cookies for the target domain to the outgoing request, _regardless_ of where
          the request originates. This means another site can send any of these
          requests to your server and the browser will attach your cookies. This is
          where "CSRF":/security/csrf.html attacks come from.
      
          Faye may use any of these at any given time, based on what the server, the
          user's browser, and the intervening network support. If operating a
          cross-origin connection, it will pick a transport that allows this. The Faye
          server makes no restrictions based on the @Origin@ of a request, and allows
          all requests through @Access-Control-Allow-Origin@.
      
          h4. How should I authenticate clients?
      
          The core question you're trying to answer when authenticating a client's
          subscription or publication message is, how does the server know that the
          client is acting on behalf of an authorized user? That is, how can the
          client prove it should be given access to do what it wants?
      
          For server-side clients, that run on private hardware rather than running on
          the web or as part of a native app installed on the user's hardware, this is
          simple: require a password. You can easily pick a password and require it to
          be present in messages to perform certain actions. See "Push-only
          servers":/security/push.html for an example of this pattern, and remember to
          delete the password from messages as they leave the server lest they leak to
          third parties.
      
          For clients running on a web page, acting on behalf of a user, you need to
          provably associate the client with the user's session. We can take an
          approach similar to how one prevents CSRF in traditional web apps.
      
          To prevent CSRF, we generate an unguessable token from the user's session,
          embed it in any forms we render, then require that all @POST@ requests
          contain a valid token for the session. This works because JavaScript on
          other domains cannot read pages served by your app, or the user's cookies
          for your domain, so cannot get the token they need to make @POST@ requests.
          Thus, the presence of such a token proves the request came from one of your
          pages.
      
          Note that this defense relies on third-party JavaScript not being able to
          get a token. Any resources tied to the user's session _must not_ be
          accessible cross-domain, that is to say a resource that depends on the
          request's @Cookie@ should never include @Access-Control-Allow-Origin@ in the
          response.
      
          So, taking inspiration from this approach, we can imagine having our web
          application render some JS data into the page that contains the user's ID
          and a token generated from the session, just like how we would embed a
          hidden CSRF token field in a rendered form. The Faye client embeds the token
          and the current user ID in the request before sending it.
      
          <pre>// Rendered by the server:
          var USER_ID    = 18787,
              USER_TOKEN = 'ifd63cylqwsyaq9c2ptzywjujgtfpxs';
      
          client.addExtension({
            outgoing: function(message, callback) {
              if (message.channel !== '/meta/subscribe')
                return callback(message);
      
              message.ext = message.ext || {};
              message.ext.userId = USER_ID;
              message.ext.token = USER_TOKEN;
              callback(message);
            }
          });</pre>
      
          The server would then have an extension that checked incoming
          @/meta/subscribe@ messages for @ext.userId@ and @ext.token@ and validated
          these values. If the token/ID pair is not valid, or if the given user is not
          allowed to subscribe to the given channel, the server refuses the
          subscription request.
      
          h4. How should I generate tokens?
      
          There are many potential ways of doing this, depending on what data you
          decide to send along with the subscription message and the security
          requirements of your system. If you have questions or are not well-versed in
          basic cryptography, "please ask the mailing list":http://groups.google.com/group/faye-users -
          there are plenty of people there who've dealt with these problems.
      
          The important thing is that the incoming message contains a user ID, that
          you're going to use to decide if the request should be allowed. You need to
          know that the message _really_ came from that user. You must also make sure
          that the token does not reveal how it was generated, since it will be
          exposed to any script running in the browser and you must not let third
          parties discover how to create real-looking tokens.
      
          So, when implementing your code to generate @USER_TOKEN@, an easy solution
          might be to have the server determine the user ID from the
          session, then sign the ID using a secret key stored on the server. For
          example:
      
          <pre>var crypto = require('crypto'),
              hmac   = crypto.createHmac('sha256', 'your secret key'),
              token  = hmac.update('the user ID').digest('hex');
      
          // -> 'cb20f4a58ea060d186fb4fad7949dfad0eea8bcf6ae2f4f9594e9c8f1dc9b509'</pre>
      
          Bear in mind that "you should use a Message Authentication Code (MAC) like
          HMAC for this":http://blog.jcoglan.com/2012/06/09/why-you-should-never-use-hash-functions-for-message-authentication/,
          rather than a bare hashing function.
      
          The server must only return a token based on the current logged-in user; the
          client should not be able to specify a different user ID in its request.
          Otherwise, a client can easily gain authenticated access to other users' 
          data.
      
          When the client sends its subscription request, you regenerate the token
          using the given user ID and your secret key, and check it matches the token
          in the message. If it does, you know the user ID is genuine and proceed with
          your access control.
      
          You could also have the token generator accept the name of the channel the
          client wants to subscribe to, and make it sign a combination of the user ID
          and channel name if the user is allowed to subscribe:
      
          <pre>var crypto  = require('crypto'),
              userId  = session.userId,
              channel = params.channel,
              hmac, token;
      
          if (canSubscribe(userId, channel)) {
            hmac  = crypto.createHmac('sha256', 'your secret key'),
            token = hmac.update(userId + ':' + channel).digest('hex');
          } else {
            token = null;
          }</pre>
      
          The token then acts as proof that your application has granted access to the
          channel, and all the Faye server has to do is check the token matches the
          user ID and channel in the message.
      
          Remember that if you are using this technique to protect _publication_, you
          must delete any credentials using an outgoing extension on the server side
          to avoid forwarding the credentials to subscribed clients.
      �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/site/src/pages/security/csrf.haml��������������������������������������������������������0000664�0000000�0000000�00000021465�13711043772�0020756�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.content
        = partial 'security_navigation'
      
        :textile
          h4. CSRF protection
      
          Faye lets you write server-side extensions that have access to the request
          data for the current incoming message. This allows you to use the @Cookie@
          header to authorize messages based on the user's session, but *you must
          implement CSRF protection*.
      
          CSRF stands for 'cross-site request forgery', and it works like this. When a
          web browser makes a request, it searches for any cookies it has stored that
          are scoped to that URL. Cookie scope is determined by various factors,
          including the domain and path of the URL, and whether it uses @https@. It
          doesn't matter which page initiates the request, or what kind of request it
          is; the browser attaches all in-scope cookies to _every_ request and sends
          them in the request's @Cookie@ header.
      
          Since Faye allows cross-origin connections, any site the user has open can
          send requests to your Faye server, and they will have the user's session
          cookies attached. This means you can't use @Cookie@ on its own to
          authenticate a message since you don't know where the message came from. You
          must prove the message actually came from _your_ site.
      
          You can't use the @Origin@ header for this, since the browser doesn't send
          an @Origin@ header for most transports that Faye uses. So, you need
          implement explicit CSRF protection.
      
          Most web frameworks do this as follows:
      
          * Generate a large random token and store it in the user's session
          * Add this token to all forms using an @<input type="hidden">@
          * Reject all @POST@ requests that don't have a form parameter matching the
            session's token
      
          The idea is that other sites can't scrape the token out of your site's pages
          since sites can't access each other's DOM. Since each session gets a
          different token, an attacker site would need to steal the user's cookies for
          your site, or access your site's DOM, both of which the browser prevents.
      
          The best way to implement CSRF protection is to piggy-back on the protection
          provided by your web framework. The following examples cover some common use
          cases.
      
          h4. Example: Rails
      
          Rails includes CSRF protection that uses a session variable called
          @_csrf_token@. You can check incoming messages and compare their attached
          token against the value stored in the session. If the tokens do not match,
          you add an error to make the server reject them. You delete @csrfToken@ from
          the message so that it's not forwarded to subscribers.
      
          <pre>class CsrfProtection < ActionController::Base
            def incoming(message, request, callback)
              message_token = message['ext'] && message['ext'].delete('csrfToken')
      
              unless valid_authenticity_token?(request.session, message_token)
                message['error'] = '401::Access denied'
              end
      
              callback.call(message)
            end
          end</pre>
      
          This extension needs to access the Rails session, and to allow this you must
          put Faye at a specific place in the Rails middleware stack. In your
          application config, add @Faye::RackAdapter@ after your app's session store
          middleware, adding @CsrfProtection@ as an extension.
      
          <pre>config.middleware.insert_after ActionDispatch::Session::CookieStore,
                                         Faye::RackAdapter,
                                         :extensions => [CsrfProtection.new],
                                         :mount      => '/bayeux',
                                         :timeout    => 25</pre>
      
          Finally you need to make the client attach the CSRF token to all messages it
          sends. Rails includes the token in all pages using a @meta@ tag called
          @csrf-token@, and you can use jQuery to grab its content and attach it to
          outgoing messages.
      
          <pre>var client = new Faye.Client('/bayeux');
      
          client.addExtension({
            outgoing: function(message, callback) {
              message.ext = message.ext || {};
              message.ext.csrfToken = $('meta[name=csrf-token]').attr('content');
              callback(message);
            }
          });</pre>
      
          h4. Example: Sinatra and other Rack apps
      
          If you're using Sinatra or any other Rack-based framework, you can use
          standard Rack middlewares to add session support and CSRF protection to your
          stack.
      
          Start by making a Faye extension that blocks incoming messages that lack a
          token matching the CSRF token stored in the session.
      
          <pre>class CsrfProtection
            def incoming(message, request, callback)
              session_token = request.session['csrf']
              message_token = message['ext'] && message['ext'].delete('csrfToken')
      
              unless session_token == message_token
                message['error'] = '401::Access denied'
              end
      
              callback.call(message)
            end
          end</pre>
      
          To enable session support, you need a Rack session middleware _before_ Faye
          in your @config.ru@ middleware stack.
      
          Here's a sample @config.ru@ for this design:
      
          <pre>require 'faye'
          require 'rack'
          require './path/to/sinatra_app'
      
          use Rack::Session::Cookie,
              :secret => '55912e33cad379c8ff64c8a97fe2f096ef013111'
      
          use Faye::RackAdapter,
              :extensions => [CsrfProtection.new],
              :mount      => '/bayeux',
              :timeout    => 25
      
          run Sinatra::Application</pre>
      
          You'll need to generate and render the CSRF token in your templates for the
          client to pick up:
      
          <pre><% token = request.session['csrf'] ||= SecureRandom.base64(32) %>
          <meta name="csrf-token" content="<%= token %>"></pre>
      
          And finally, configure the client to attach this token to outgoing messages.
      
          <pre>var client = new Faye.Client('/bayeux');
      
          client.addExtension({
            outgoing: function(message, callback) {
              message.ext = message.ext || {};
              message.ext.csrfToken = $('meta[name=csrf-token]').attr('content');
              callback(message);
            }
          });</pre>
      
          h4. Example: Express
      
          Express uses Connect middlewares to provide session support and CSRF
          protection.  With a little work, you can use these middlewares with Faye to
          include CSRF protection in your extensions.
      
          Start by adding the @cookieParser@, @cookieSession@ and @csrf@ middlewares
          to your stack. Here's a quick example with an app with one request handler.
          We keep a reference to the @cookieParser@ and @cookieSession@ middlewares
          since we'll need to use them to process Faye requests.
      
          <pre>var express = require('express'),
              ejs     = require('ejs'),
              app     = express(),
              parser  = express.cookieParser(),
              session = express.cookieSession({secret: '55912e33cad379c8ff64c8a97fe2f096ef013111'});
      
          app.use(parser);
          app.use(session);
          app.use(express.csrf());
      
          app.engine('html', ejs.renderFile);
      
          app.get('/', function(request, response) {
            response.render('index.html', {request: request});
          });
      
          var server = app.listen(8000);</pre>
      
          Next, attach the Faye server to your app.
      
          <pre>var faye = require('faye');
      
          var bayeux = new faye.NodeAdapter({mount: '/bayeux', timeout: 45});
          bayeux.attach(server);</pre>
      
          Now you can add an extension that checks whether the incoming message has a
          CSRF token that matches the one in the session. Since this requires session
          access but Faye is outside the Connect middleware stack, you need to use the
          middleware you created earlier to pre-process the request before your
          extension handles it. You can do this with a helper function:
      
          <pre>var EventEmitter = require('events').EventEmitter;
          
          var buildSession = function(request, callback) {
            var response = new EventEmitter();
            parser(request, response, function() {
              session(request, response, callback);
            });
          };</pre>
      
          Now you can add the CSRF protection extension. Remember to delete
          @csrfToken@ from the message so it's not forwarded to any subscribers.
      
          <pre>bayeux.addExtension({
            incoming: function(message, request, callback) {
              buildSession(request, function() {
                var sessionToken = request.session._csrfSecret,
                    messageToken = message.ext && message.ext.csrfToken;
      
                if (message.ext) delete message.ext.csrfToken;
      
                if (sessionToken !== messageToken)
                  message.error = '401::Access denied';
      
                callback(message);
              });
            }
          });</pre>
      
          Finally, you need to render the CSRF token in your templates:
      
          <pre><meta name="csrf-token" content="<%= request.session._csrfSecret %>"></pre>
      
          And use jQuery to pick that token up and attach it to outgoing messages from
          the client:
      
          <pre>var client = new Faye.Client('/bayeux');
      
          client.addExtension({
            outgoing: function(message, callback) {
              message.ext = message.ext || {};
              message.ext.csrfToken = $('meta[name=csrf-token]').attr('content');
              callback(message);
            }
          });</pre>
      �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/site/src/pages/security/headers.haml�����������������������������������������������������0000664�0000000�0000000�00000004603�13711043772�0021427�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.content
        = partial 'security_navigation'
      
        :textile
          h4. Can I use cookies?
      
          As of version 1.0, Faye allows server-side extensions to access the request
          data for the current message. We have introduced this capability in order to
          support integration with various other HTTP-based authorization mechanisms,
          but still recommend that this task is done within the messaging protocol
          using signed or encrypted data.
          
          However some users will want to use HTTP-based methods, in particular the
          @Cookie@ header that carries the user's session. There are several caveats
          you must be aware of to use cookies safely.
      
          First, the browser will send cookies regardless of which site the request
          came from, so to make sure you're only granting access to your own pages,
          you must "implement CSRF protection":/security/csrf.html. If you don't do
          this, any site the user has open will get privileged access to your Faye
          server by impersonating the user.
      
          Second, some transports like WebSocket and EventSource use a very long-lived
          request and only send one @Cookie@ header on first connection. This means
          the information you're using to authorize messages may have been sent a long
          time ago and may therefore be stale. If the session has changed or been
          invalidated since the initial connection, relying on stale data can cause
          security holes.
          
          Instead of cookies that contain the session data, we recommend keeping a
          session ID in the cookies and storing the data on the server, either in
          memory or on disk or in a database. This way, you will look up the session
          afresh on every message instead of using stale data.
      
          h4. Can I use Origin, Referer, etc.?
      
          Although the @Origin@ header was introduced to combat CSRF, these headers
          can be easily guessed and spoofed by server-side clients, browser
          extensions and malicious JavaScript applications. They are not
          cryptographically secure proof that you can trust where the request claims
          to have come from.
      
          This still allows third parties to inject messages into your application,
          and is especially bad if you have clients that receive JavaScript and
          @eval()@ it.
      
          The @Origin@ header is also not sent by most browser transports that Faye
          uses, so filtering based on it will actually block most legit traffic,
          including the initial handshake request.
      �����������������������������������������������������������������������������������������������������������������������������faye-1.4.0/site/src/pages/security/javascript.haml��������������������������������������������������0000664�0000000�0000000�00000003157�13711043772�0022165�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.content
        = partial 'security_navigation'
      
        :textile
          h4. Publishing JavaScript
      
          Most realtime applications work by pushing data to the client for it to act
          on. Assuming the data can be trusted by the client, this is a good setup:
          the client's behaviour is somewhat constrained. It can only do what its code
          allows it to do, with the caveat that some crafted inputs may lead to
          unexpected behaviour.
      
          However some realtime applications directly script the client by pushing
          JavaScript code that the client runs with @eval()@. This is extremely
          dangerous unless you make sure that nobody but your own private servers can
          publish to your Faye server. I recommend that realtime apps operate by
          exchanging data, not sending code. If anyone but your own server-side
          applications can push JavaScript unchecked, your site has a serious XSS
          problem that can allow an attacker to easily steal the user's session and
          other private data.
      
          To illustrate how easy this is, I have in the past hijacked the browsers of
          all the attendees at a conference that were running a demo app hosted by the
          speaker. The speaker put the app's publishing key on the screen and the
          application ran any JavaScript pushed to it, so was trivial to exploit. Of
          course, normally this key would have been kept private, but if you don't
          have any such key your app automatically has an XSS hole.
      
          A JavaScript-pushing server can be made safe by ensuring your clients only
          run code sent by your application, and this can be done by turning Faye into
          a "push-only server":/security/push.html.
      �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/site/src/pages/security/publication.haml�������������������������������������������������0000664�0000000�0000000�00000003737�13711043772�0022334�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.content
        = partial 'security_navigation'
      
        :textile
          h4. Restricting publication access
      
          Applications typically only allow authenticated users to modify things: you
          must prove you 'own' a resource or that someone has given you permission
          before you go and change someone's database. In Faye, publishing is the
          write operation: publishing a message to the server causes it to be sent to
          all subscribed clients, which will act based on the data in the message. By
          publishing a message, you are sending instructions to other clients, and the
          clients must be able to trust that the data they receive is genuine.
      
          If you do not protect publication, your site probably has a "Cross-Site
          Request Forgery":/security/csrf.html (CSRF) vulnerability, and possibly a
          "Cross-Site Scripting":/security/javascript.html (XSS) one too.
      
          Protecting publication on the server side is simpler than protecting
          subscription, because publication messages (those with channels other than
          @/meta/*@) cannot be addressed to wildcards. So, to protect a channel's
          publications, you _only_ need to check that literal channel name.
      
          The channel the message is being published to will be in the
          @message.channel@ field. An important fact to remember here is that messages
          are forwarded verbatim to other clients, so if they contain authentication
          data you should delete this from the message during the @authorized()@
          function so it is not leaked to third parties.
      
          <pre>var channel = '/foo/bar/qux';
      
          var authorized = function(message) {
            // returns true or false
          };
      
          server.addExtension({
            incoming: function(message, callback) {
              if (message.channel === channel) {
                if (!authorized(message))
            	    message.error = '403::Authentication required';
              }
              callback(message);
            }
          });</pre>
      
          See "Authentication":/security/authentication.html for a discussing of the
          @authorized()@ function.
      ���������������������������������faye-1.4.0/site/src/pages/security/push.haml��������������������������������������������������������0000664�0000000�0000000�00000003455�13711043772�0020777�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.content
        = partial 'security_navigation'
      
        :textile
          h4. Push-only servers
      
          Sometimes you only want to use Faye to push events from your server-side
          application to your clients, and you don't want clients to be able to
          publish at all. This can easily be done by requiring a password for
          publishing. On any non-@/meta/@ message, check for the password. If it's not
          present, add an error to the message. Finally, delete the password from the
          message to prevent leaking it to clients.
      
          <pre>var secret = 'some long and unguessable application-specific string';
      
          server.addExtension({
            incoming: function(message, callback) {
              if (!message.channel.match(/^\/meta\//)) {
                var password = message.ext && message.ext.password;
                if (password !== secret)
                  message.error = '403::Password required';
              }
              callback(message);
            },
      
            outgoing: function(message, callback) {
              if (message.ext) delete message.ext.password;
              callback(message);
            }
          });</pre>
      
          Then you can add a client-side extension to your server-side client to add
          the password:
      
          <pre>var secret = 'some long and unguessable application-specific string';
      
          client.addExtension({
            outgoing: function(message, callback) {
              message.ext = message.ext || {};
              message.ext.password = secret;
              callback(message);
            }
          });</pre>
      
          If you're using a plain HTTP client to publish messages, include the
          password in the JSON body:
      
          <pre>$ curl -X POST www.example.com/faye \
              -H 'Content-Type: application/json' \
              -d '{"channel": "/foo", "data": "hi", "ext": {"password": "..."}}'</pre>
      
          Remember to keep the password secret, and do not let it leak out of your
          servers into the outside world.
      �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/site/src/pages/security/subscription.haml������������������������������������������������0000664�0000000�0000000�00000004071�13711043772�0022537�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.content
        = partial 'security_navigation'
      
        :textile
          h4. Restricting subscription access
      
          Most web applications have a concept of access control: some content is only
          accessible to certain people, and you must be logged in to prove your
          identity. In Faye, you might want subscriptions to certain channels to
          require authentication, if you are publishing private data on such channels.
          This is easily done with a server-side extension that filters incoming
          @/meta/subscribe@ messages.
      
          An important point here is that subscriptions can contain wildcards, and you
          must protect these. A message published to @/foo/bar/qux@ will be routed to
          any client subscribed to @/foo/bar/qux@, @/foo/bar/*@, @/foo/bar/**@,
          @/foo/**@ or @/**@.
      
          Adding an @error@ field to any incoming message will stop the server from
          processing it. The channel the client is attempting to subscribe to will be
          in the @message.subscription@ field.
      
          <pre>var subscriptions = [
            '/foo/bar/qux',
            '/foo/bar/*',
            '/foo/bar/**',
            '/foo/**',
            '/**'
          ];
      
          var authorized = function(message) {
            // returns true or false
          };
      
          server.addExtension({
            incoming: function(message, callback) {
              if (message.channel === '/meta/subscribe') {
                if (subscriptions.indexOf(message.subscription) >= 0) {
                  if (!authorized(message))
                    message.error = '403::Authentication required';
                }
              }
              callback(message);
            }
          });</pre>
      
          How the @authorized()@ function is implemented depends on your clients'
          capabilities and is covered in more detail under
          "Authentication":/security/authentication.html. Note that because extensions
          are asynchronous (you hand the message back to the server using a callback),
          your authentication logic can contain async operations, which is useful if
          you need to do some I/O.
      
          A simple extension like this, with properly implemented authentication, will
          prevent unauthorized access to published data on the selected channel.
      �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/site/src/pages/security/summary.haml�����������������������������������������������������0000664�0000000�0000000�00000004416�13711043772�0021513�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.content
        = partial 'security_navigation'
      
        :textile
          h4. Other techniques
      
          The above is a fairly comprehensive picture of restricting access to your
          Faye server. The important thing to remember is that when exchanging
          messages, you just need a way to prove that the data is genuine. This relies
          heavily on cryptograhpic techniques and you should always use standard
          functions for this rather than inventing your own.
      
          However, sometimes, it's just a case of using data that is very hard to
          guess. For example, say you want to send messages to one particular user and
          nobody else. Instead of naming a channel after a username and requiring an
          access token to subcribe to it, you could just make the channel name
          _contain_ the access token. For example, the client could call an endpoint
          on your server to get a channel name for the logged-in user, then subscribe
          to that channel in Faye. When publishing, you would just regenerate the
          channel name from the username you want to publish to.
      
          These channel names may be a cryptograhpically signed copy of the user's
          name or ID, or they could simply be very large random numbers (larger than
          160 bits is advisable) that you store in a database next to each user ID. As
          long as they cannot be guessed by a third party, you're alright. Just
          remember that 'cannot be guessed' is surprisingly hard to implement
          correctly, and you should consult someone with a grounding in crypto if
          you're not sure what you're doing is safe.
      
          h4. Summary
      
          This guide, while not exhaustive should give you enough grounding on the
          topic to safely implement a real-time application using Faye.  If you have
          further questions you should "ask on the mailing
          list":http://groups.google.com/group/faye-users - many people there have run
          into the same problems as you and will likely have already thought of a
          solution.  If you have a genuinely unusual case then you will most likely
          benefit from their sage advice.
      
          Thank you for taking the time to familiarise yourself with this advice and
          for using Faye.  Your feedback on this document is eagerly solicited; issues
          and pull requests can be submitted on the "Faye
          project":https://github.com/faye/faye on GitHub.
      ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/site/src/partials/�����������������������������������������������������������������������0000775�0000000�0000000�00000000000�13711043772�0016017�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/site/src/partials/browser_navigation.haml������������������������������������������������0000664�0000000�0000000�00000000523�13711043772�0022564�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������:textile
        h3. Browser client
      
        <div class="sections">
      
        * "Setting up":/browser.html
        * "Subscribing to channels":/browser/subscribing.html
        * "Sending messages":/browser/publishing.html
        * "Controlling dispatch":/browser/dispatch.html
        * "Network errors":/browser/transport.html
        * "Extensions":/browser/extensions.html
      
        </div>
      �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/site/src/partials/node_navigation.haml���������������������������������������������������0000664�0000000�0000000�00000000442�13711043772�0022026�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������:textile
        h3. Node.js server
      
        <div class="sections">
      
        * "Setting up":/node.html
        * "Extensions":/node/extensions.html
        * "Monitoring":/node/monitoring.html
        * "Server-side clients":/node/clients.html
        * "Engines":/node/engines.html
        * "WebSockets":/node/websockets.html
      
        </div>
      ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/site/src/partials/ruby_navigation.haml���������������������������������������������������0000664�0000000�0000000�00000000437�13711043772�0022066�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������:textile
        h3. Ruby server
      
        <div class="sections">
      
        * "Setting up":/ruby.html
        * "Extensions":/ruby/extensions.html
        * "Monitoring":/ruby/monitoring.html
        * "Server-side clients":/ruby/clients.html
        * "Engines":/ruby/engines.html
        * "WebSockets":/ruby/websockets.html
      
        </div>
      ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/site/src/partials/security_navigation.haml�����������������������������������������������0000664�0000000�0000000�00000000776�13711043772�0022762�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������:textile
        h3. Security advice
      
        <div class="sections">
      
        * "Overview":/security.html
        * "Restricting subscriptions":/security/subscription.html
        * "Restricting publication":/security/publication.html
        * "Publishing JavaScript":/security/javascript.html
        * "Push-only servers":/security/push.html
        * "Authentication":/security/authentication.html
        * "Cookies, Origin, and Referer":/security/headers.html
        * "CSRF protection":/security/csrf.html
        * "Other techniques":/security/summary.html
      
        </div>
      ��faye-1.4.0/site/src/stylesheets/��������������������������������������������������������������������0000775�0000000�0000000�00000000000�13711043772�0016554�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/site/src/stylesheets/screen.sass���������������������������������������������������������0000664�0000000�0000000�00000006454�13711043772�0020737�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������body
        background:         #3f3f3f
        color:              #fff
        font:               14px/1.5 Open Sans, FreeSans, Helvetica, Arial, sans-serif
        text-align:         center
        margin:             0 0 0 0
        padding:            0 0 0 0
      
      .container
        width:              960px
        margin:             0 auto
        text-align:         left
        position:           relative
      
        a
          color:            #6fbc62
          font-weight:      bold
          text-decoration:  none
      
        a:hover
          text-decoration:  underline
      
        .clear
          clear:            both
          display:          block
          height:           0
          overflow:         hidden
      
      .header
        border-bottom:      4px solid #ccc
      
        h1
          margin:           0 0 0 0
          padding:          26px 0 0 0
          a
            display:        block
            width:          286px
            height:         0
            overflow:       hidden
            padding:        84px 0 0 0
            background:     #3f3f3f url(/images/faye-logo.gif) 0 0 no-repeat
            border-bottom:  8px solid #3f3f3f
      
        h2
          font-size:        16px
          font-weight:      normal
          margin:           0 0 0 0
          padding:          0 8px 40px
      
        .docs, .download, .community
          position:         absolute
          top:              0
          border-left:      1px solid #666
          padding:          40px 0 0 20px
          height:           112px
      
          a
            color:          #999
            font-weight:    normal
          a:hover
            color:          #fff
      
          h3
            margin:         0 0 0 0
            padding:        0 0 0 0
            font-weight:    bold
            font-size:      100%
      
          ul, li
            list-style:     none
            margin:         0 0 0 0
            padding:        0 0 0 0
      
        .docs
          left:             480px
        .community
          left:             640px
        .download
          left:             800px
      
      .footer
        border-top:         4px solid #ccc
        padding-bottom:     2em
        p
          font-size:        80%
          margin:           1em 0 1em 320px
      
      .main
        background:         #fff
        color:              #3f3f3f
        padding:            2em 0 4em
      
      .front-matter
        .intro
          font-size:        140%
          h3
            margin-top:     0
          p
            border-top:     none
            margin-top:     0.5em
            padding-top:    0
      
          a img
            margin-bottom:  18px
            margin-right:   32px
        h3
          font-weight:      normal
          font-style:       italic
          font-size:        150%
          float:            left
          margin:           1em 0 0 80px
          position:         relative
      
          .number
            color:          #6fbc62
            font-weight:    bold
      
        p, pre
          border-top:       1px solid #eee
          margin:           1.5em 0
          padding:          1.5em 0 0 320px
      
        pre
          font-family:      Inconsolata, Monaco, Lucida Console, Courier New, monospace
      
      .content
        h3
          float:            left
          margin:           -0.5em 0 0 0
          font-size:        200%
          font-weight:      bold
      
        .sections
          clear:            left
          float:            left
          margin:           32px 0
          padding:          0 0 0 2em
      
          ul, li
            margin:         0
            padding:        0
      
        h4
          font-size:        120%
          font-weight:      bold
      
        h4, p, pre, ul, .image
          margin:           1.5em 0 1.5em 320px
      
        .image img
          display:          block
          margin:           1em auto
      
        pre
          border-left:      1em solid #f0f0f0
          font-family:      Inconsolata, Monaco, Lucida Console, Courier New, monospace
          padding-left:     2em
      
        code
          background:       #eee
      ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/������������������������������������������������������������������������������������0000775�0000000�0000000�00000000000�13711043772�0013377�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/browser.js��������������������������������������������������������������������������0000664�0000000�0000000�00000000525�13711043772�0015422�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������require("./javascript/util/copy_object_spec")
      require("./javascript/channel_spec")
      require("./javascript/client_spec")
      require("./javascript/dispatcher_spec")
      require("./javascript/grammar_spec")
      require("./javascript/publisher_spec")
      require("./javascript/transport_spec")
      require("./javascript/uri_spec")
      
      require("jstest").Test.autorun()
      ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/index.html��������������������������������������������������������������������������0000664�0000000�0000000�00000000263�13711043772�0015375�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<!doctype html>
      <html>
        <head>
          <meta charset="utf-8">
          <title>Faye test suite</title>
        </head>
        <body>
          <script src="./browser_bundle.js"></script>
        </body>
      </html>
      ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/javascript/�������������������������������������������������������������������������0000775�0000000�0000000�00000000000�13711043772�0015545�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/javascript/channel_spec.js����������������������������������������������������������0000664�0000000�0000000�00000001776�13711043772�0020540�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������var jstest = require("jstest").Test
      
      var Channel = require("../../src/protocol/channel")
      
      jstest.describe("Channel", function() { with(this) {
        describe("expand", function() { with(this) {
          it("returns all patterns that match a channel", function() { with(this) {
      
            assertEqual( ["/**", "/foo", "/*"],
                         Channel.expand("/foo") )
      
            assertEqual( ["/**", "/foo/bar", "/foo/*", "/foo/**"],
                         Channel.expand("/foo/bar") )
      
            assertEqual( ["/**", "/foo/bar/qux", "/foo/bar/*", "/foo/**", "/foo/bar/**"],
                         Channel.expand("/foo/bar/qux") )
          }})
        }})
      
        describe("Set", function() { with(this) {
          describe("subscribe", function() { with(this) {
            it("subscribes and unsubscribes without callback", function() { with(this) {
              var channels = new Channel.Set()
              channels.subscribe(["/foo/**"], null)
              assertEqual( ["/foo/**"], channels.getKeys() )
              assert( channels.unsubscribe("/foo/**", null) )
            }})
          }})
        }})
      }})
      ��faye-1.4.0/spec/javascript/client_spec.js�����������������������������������������������������������0000664�0000000�0000000�00000062302�13711043772�0020376�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������var jstest = require("jstest").Test
      
      var asap         = require("asap"),
          Client       = require("../../src/protocol/client"),
          Dispatcher   = require("../../src/protocol/dispatcher"),
          Publisher    = require("../../src/mixins/publisher"),
          Subscription = require("../../src/protocol/subscription"),
          assign       = require("../../src/util/assign"),
          Promise      = require("../../src/util/promise"),
          URI          = require("../../src/util/uri")
      
      jstest.describe("Client", function() { with(this) {
        before(function() { with(this) {
          var uri = URI.parse("http://localhost/bayeux")
      
          this.dispatcher = { endpoint: uri, connectionType: "fake-transport", retry: 5 }
          stub(dispatcher, "getConnectionTypes").returns(["fake-transport", "another-transport"])
          stub(dispatcher, "selectTransport")
          stub(dispatcher, "sendMessage")
      
          assign(dispatcher, Publisher)
          stub(Dispatcher, "create").returns(dispatcher)
      
          stub("setTimeout")
        }})
      
        define("stubResponse", function(response) { with(this) {
          stub(dispatcher, "sendMessage", function(message) {
            response.id = message.id
            dispatcher.emit("message", response)
          })
        }})
      
        define("createClient", function() { with(this) {
          this.client = new Client("http://localhost/")
        }})
      
        define("createConnectedClient", function() { with(this) {
          createClient()
          stubResponse({ channel:    "/meta/handshake",
                         successful: true,
                         version:    "1.0",
                         supportedConnectionTypes: ["websocket"],
                         clientId:   "fakeid" })
      
          client.handshake()
        }})
      
        define("subscribe", function(client, channel, callback) { with(this) {
          stubResponse({ channel:      "/meta/subscribe",
                         successful:   true,
                         clientId:     "fakeid",
                         subscription: channel })
      
          this.subsCalled = 0
          callback = callback || function() { subsCalled += 1 }
          return client.subscribe(channel, callback)
        }})
      
        describe("initialize", function() { with(this) {
          it("puts the client in the UNCONNECTED state", function() { with(this) {
            var client = new Client("http://localhost/")
            assertEqual( client.UNCONNECTED, client._state )
          }})
        }})
      
        describe("handshake", function() { with(this) {
          before(function() { this.createClient() })
      
          it("creates a transport the server must support", function() { with(this) {
            expect(dispatcher, "selectTransport").given(["long-polling", "callback-polling", "in-process"])
            client.handshake()
          }})
      
          it("sends a handshake message to the server", function() { with(this) {
            expect(dispatcher, "sendMessage").given({
              channel:  "/meta/handshake",
              version:  "1.0",
              supportedConnectionTypes: ["fake-transport", "another-transport"],
              id:       instanceOf("string")
            }, 72, {})
            client.handshake()
          }})
      
          it("puts the client in the CONNECTING state", function() { with(this) {
            client.handshake()
            assertEqual( client.CONNECTING, client._state )
          }})
      
          describe("with an outgoing extension installed", function() { with(this) {
            before(function() { with(this) {
              var extension = {
                outgoing: function(message, callback) {
                  message.ext = { auth: "password" }
                  callback(message)
                }
              }
              client.addExtension(extension)
            }})
      
            it("passes the handshake message through the extension", function() { with(this) {
              expect(dispatcher, "sendMessage").given({
                channel:  "/meta/handshake",
                version:  "1.0",
                supportedConnectionTypes: ["fake-transport", "another-transport"],
                id:       instanceOf("string"),
                ext:      { auth: "password" }
              }, 72, {})
              client.handshake()
            }})
          }})
      
          describe("on successful response", function() { with(this) {
            before(function() { with(this) {
              stubResponse({ channel:    "/meta/handshake",
                             successful: true,
                             version:    "1.0",
                             supportedConnectionTypes: ["long-polling", "websocket"],
                             clientId:   "handshakeid" })
            }})
      
            it("stores the clientId", function() { with(this) {
              client.handshake()
              assertEqual( "handshakeid", client._dispatcher.clientId )
            }})
      
            it("puts the client in the CONNECTED state", function() { with(this) {
              client.handshake()
              assertEqual( client.CONNECTED, client._state )
            }})
      
            it("registers any pre-existing subscriptions", function() { with(this) {
              expect(client, "subscribe").given([], true)
              client.handshake()
            }})
      
            it("selects a new transport based on what the server supports", function() { with(this) {
              expect(dispatcher, "selectTransport").given(["long-polling", "websocket"])
              client.handshake()
            }})
          }})
      
          describe("on unsuccessful response", function() { with(this) {
            before(function() { with(this) {
              stubResponse({ channel:    "/meta/handshake",
                             successful: false,
                             version:    "1.0",
                             supportedConnectionTypes: ["websocket"] })
            }})
      
            it("schedules a retry", function() { with(this) {
              expect("setTimeout")
              client.handshake()
            }})
      
            it("puts the client in the UNCONNECTED state", function() { with(this) {
              client.handshake()
              assertEqual( client.UNCONNECTED, client._state )
            }})
          }})
      
          describe("with existing subscriptions after a server restart", function() { with(this) {
            before(function(resume) { with(this) {
              createConnectedClient()
              this.message = null
      
              subscribe(client, "/messages/foo", function(m) { message = m }).then(function() {
                client._receiveMessage({ advice: { reconnect: "handshake" }})
      
                stubResponse({ channel:      "/meta/handshake",
                               successful:   true,
                               version:      "1.0",
                               supportedConnectionTypes: ["websocket"],
                               clientId:     "reconnectid",
                               subscription: "/messages/foo" })  // tacked on to trigger subscribe() callback
      
                resume()
              })
            }})
      
            it("resends the subscriptions to the server", function(resume) { with(this) {
              expect(dispatcher, "sendMessage").given({
                channel:      "/meta/subscribe",
                clientId:     "reconnectid",
                subscription: "/messages/foo",
                id:           instanceOf("string")
              }, 72, {})
              client.connect(resume)
            }})
      
            it("retains the listeners for the subscriptions", function() { with(this) {
              client.handshake()
              client._receiveMessage({ channel: "/messages/foo", "data": "ok" })
              assertEqual( "ok", message )
            }})
          }})
      
          describe("with a connected client", function() { with(this) {
            before(function() { this.createConnectedClient() })
      
            it("does not send a handshake message to the server", function() { with(this) {
              expect(dispatcher, "sendMessage").given({
                channel:  "/meta/handshake",
                version:  "1.0",
                supportedConnectionTypes: ["fake-transport", "another-transport"],
                id:       instanceOf("string")
              }, 72, {})
              .exactly(0)
      
              client.handshake()
            }})
          }})
        }})
      
        describe("connect", function() { with(this) {
          describe("with an unconnected client", function() { with(this) {
            before(function() { with(this) {
              stubResponse({ channel:    "/meta/handshake",
                             successful: true,
                             version:    "1.0",
                             supportedConnectionTypes: ["websocket"],
                             clientId:   "handshakeid" })
      
              createClient()
            }})
      
            it("handshakes before connecting", function(resume) { with(this) {
              expect(dispatcher, "sendMessage").given({
                channel:        "/meta/connect",
                clientId:       "handshakeid",
                connectionType: "fake-transport",
                id:             instanceOf("string")
              }, 72, {})
              client.connect()
              asap(resume)
            }})
          }})
      
          describe("with a connected client", function() { with(this) {
            before(function() { this.createConnectedClient() })
      
            it("sends a connect message to the server", function() { with(this) {
              expect(dispatcher, "sendMessage").given({
                channel:        "/meta/connect",
                clientId:       "fakeid",
                connectionType: "fake-transport",
                id:             instanceOf("string")
              }, 72, {})
              client.connect()
            }})
      
            it("only opens one connect request at a time", function() { with(this) {
              expect(dispatcher, "sendMessage").given({
                channel:        "/meta/connect",
                clientId:       "fakeid",
                connectionType: "fake-transport",
                id:             instanceOf("string")
              }, 72, {})
              .exactly(1)
      
              client.connect()
              client.connect()
            }})
          }})
        }})
      
        describe("disconnect", function() { with(this) {
          before(function() { this.createConnectedClient() })
      
          it("sends a disconnect message to the server", function() { with(this) {
            expect(dispatcher, "sendMessage").given({
              channel:  "/meta/disconnect",
              clientId: "fakeid",
              id:       instanceOf("string")
            }, 72, {})
            client.disconnect()
          }})
      
          it("puts the client in the DISCONNECTED state", function() { with(this) {
            stub(dispatcher, "close")
            client.disconnect()
            assertEqual( client.DISCONNECTED, client._state )
          }})
      
          describe("on successful response", function() { with(this) {
            before(function() { with(this) {
              stubResponse({ channel:      "/meta/disconnect",
                             successful:   true,
                             clientId:     "fakeid" })
            }})
      
            it("closes the dispatcher", function() { with(this) {
              expect(dispatcher, "close")
              client.disconnect()
            }})
          }})
        }})
      
        describe("subscribe", function() { with(this) {
          before(function() { with(this) {
            createConnectedClient()
            this.subscribeMessage = {
                channel:      "/meta/subscribe",
                clientId:     "fakeid",
                subscription: "/foo",
                id:           instanceOf("string")
              }
          }})
      
          describe("with no prior subscriptions", function() { with(this) {
            it("sends a subscribe message to the server", function(resume) { with(this) {
              expect(dispatcher, "sendMessage").given(subscribeMessage, 72, {})
              client.subscribe("/foo")
              client.connect(resume)
            }})
      
            // The Bayeux spec says the server should accept a list of subscriptions
            // in one message but the cometD server doesn't actually support this
            describe("with an array of subscriptions", function() { with(this) {
              it("sends multiple subscribe messages", function(resume) { with(this) {
                expect(dispatcher, "sendMessage").given({
                  channel:      "/meta/subscribe",
                  clientId:     "fakeid",
                  subscription: "/foo",
                  id:           instanceOf("string")
                }, 72, {})
                expect(dispatcher, "sendMessage").given({
                  channel:      "/meta/subscribe",
                  clientId:     "fakeid",
                  subscription: "/bar",
                  id:           instanceOf("string")
                }, 72, {})
                client.subscribe(["/foo", "/bar"])
                client.connect(resume)
              }})
      
              it("returns an array of subscriptions", function() { with(this) {
                var subs = client.subscribe(["/foo", "/bar"])
                assertEqual( 2, subs.length )
                assertKindOf( Subscription, subs[0] )
              }})
            }})
      
            describe("on successful response", function() { with(this) {
              before(function() { with(this) {
                stubResponse({ channel:      "/meta/subscribe",
                               successful:   true,
                               clientId:     "fakeid",
                               subscription: "/foo/*" })
              }})
      
              it("sets up a listener for the subscribed channel", function(resume) { with(this) {
                var message
                client.subscribe("/foo/*", function(m) { message = m }).then(function() {
                  resume(function() {
                    client._receiveMessage({ channel: "/foo/bar", data: "hi" })
                    assertEqual( "hi", message )
                  })
                })
              }})
      
              it("yields the channel when requested", function(resume) { with(this) {
                var message
                client.subscribe("/foo/*").withChannel(function(c, m) { message = [c, m] }).then(function() {
                  resume(function() {
                    client._receiveMessage({ channel: "/foo/bar", data: "hi" })
                    assertEqual( ["/foo/bar", "hi"], message )
                  })
                })
              }})
      
              it("does not call the listener for non-matching channels", function() { with(this) {
                var message
                client.subscribe("/foo/*", function(m) { message = m })
                client._receiveMessage({ channel: "/bar", data: "hi" })
                assertEqual( undefined, message )
              }})
      
              it("activates the subscription", function(resume) { with(this) {
                client.subscribe("/foo/*").callback(resume)
              }})
      
              describe("with an incoming extension installed", function() { with(this) {
                before(function(resume) { with(this) {
                  var extension = {
                    incoming: function(message, callback) {
                      if (message.data) message.data.changed = true
                      callback(message)
                    }
                  }
                  client.addExtension(extension)
                  this.message = null
                  client.subscribe("/foo/*", function(m) { message = m }).then(resume)
                }})
      
                it("passes delivered messages through the extension", function() { with(this) {
                  client._receiveMessage({ channel: "/foo/bar", data: { hello: "there" }})
                  assertEqual( { hello: "there", changed: true }, message )
                }})
              }})
      
              describe("with an outgoing extension installed", function() { with(this) {
                before(function(resume) { with(this) {
                  var extension = {
                    outgoing: function(message, callback) {
                      if (message.data) message.data.changed = true
                      callback(message)
                    }
                  }
                  client.addExtension(extension)
                  this.message = null
                  client.subscribe("/foo/*", function(m) { message = m }).then(resume)
                }})
      
                it("leaves messages unchanged", function() { with(this) {
                  client._receiveMessage({ channel: "/foo/bar", data: { hello: "there" }})
                  assertEqual( { hello: "there" }, message )
                }})
              }})
      
              describe("with an incoming extension that invalidates the response", function() { with(this) {
                before(function() { with(this) {
                  var extension = {
                    incoming: function(message, callback) {
                      if (message.channel === "/meta/subscribe") message.successful = false
                      callback(message)
                    }
                  }
                  client.addExtension(extension)
                }})
      
                it("does not set up a listener for the subscribed channel", function() { with(this) {
                  var message
                  client.subscribe("/foo/*", function(m) { message = m })
                  client._receiveMessage({ channel: "/foo/bar", data: "hi" })
                  assertEqual( undefined, message )
                }})
      
                it("does not activate the subscription", function() { with(this) {
                  var active = false
                  client.subscribe("/foo/*").callback(function() { active = true })
                  assert( !active )
                }})
              }})
            }})
      
            describe("on unsuccessful response", function() { with(this) {
              before(function() { with(this) {
                stubResponse({ channel:      "/meta/subscribe",
                               successful:   false,
                               error:        "403:/meta/foo:Forbidden channel",
                               clientId:     "fakeid",
                               subscription: "/meta/foo" })
              }})
      
              it("does not set up a listener for the subscribed channel", function() { with(this) {
                var message
                client.subscribe("/meta/foo", function(m) { message = m })
                client._receiveMessage({ channel: "/meta/foo", data: "hi" })
                assertEqual( undefined, message )
              }})
      
              it("does not activate the subscription", function(resume) { with(this) {
                client.subscribe("/meta/foo").errback(function() { resume() })
              }})
      
              it("reports the error through an errback", function(resume) { with(this) {
                client.subscribe("/meta/foo").errback(function(error) {
                  resume(function() {
                    assertEqual( objectIncluding({ code: 403, params: ["/meta/foo"], message: "Forbidden channel" }), error )
                  })
                })
              }})
            }})
          }})
      
          describe("with an existing subscription", function() { with(this) {
            before(function() { with(this) {
              subscribe(client, "/foo/*")
            }})
      
            it("does not send another subscribe message to the server", function() { with(this) {
              expect(dispatcher, "sendMessage").given(subscribeMessage, 72, {}).exactly(0)
              client.subscribe("/foo/*")
            }})
      
            it("sets up another listener on the channel", function(resume) { with(this) {
              client.subscribe("/foo/*", function() { subsCalled += 1 }).then(function() {
                resume(function() {
                  client._receiveMessage({ channel: "/foo/bar", data: "hi" })
                  assertEqual( 2, subsCalled )
                })
              })
            }})
      
            it("activates the subscription", function(resume) { with(this) {
              client.subscribe("/foo/*").callback(resume)
            }})
          }})
        }})
      
        describe("unsubscribe", function() { with(this) {
          before(function() { with(this) {
            createConnectedClient()
            this.unsubscribeMessage = {
                channel:      "/meta/unsubscribe",
                clientId:     "fakeid",
                subscription: "/foo/*",
                id:           instanceOf("string")
              }
          }})
      
          describe("with no subscriptions", function() { with(this) {
            it("does not send an unsubscribe message to the server", function() { with(this) {
              expect(dispatcher, "sendMessage").given(unsubscribeMessage, 72, {}).exactly(0)
              client.unsubscribe("/foo/*")
            }})
          }})
      
          describe("with a single subscription", function() { with(this) {
            before(function(resume) { with(this) {
              this.message = null
              this.subscription = subscribe(client, "/foo/*", function(m) { message = m })
              this.subscription.then(resume)
            }})
      
            it("sends an unsubscribe message to the server", function(resume) { with(this) {
              expect(dispatcher, "sendMessage").given(unsubscribeMessage, 72, {})
              client.unsubscribe("/foo/*")
              client.connect(resume)
            }})
      
            it("removes the listener from the channel", function() { with(this) {
              client._receiveMessage({ channel: "/foo/bar", data: "first" })
              client.unsubscribe("/foo/*", subscription)
              client._receiveMessage({ channel: "/foo/bar", data: "second" })
              assertEqual( "first", message )
            }})
          }})
      
          describe("with multiple subscriptions to the same channel", function() { with(this) {
            before(function(resume) { with(this) {
              this.messages = []
              this.hey = subscribe(client, "/foo/*", function(m) { messages.push("hey " + m.text) })
              this.bye = subscribe(client, "/foo/*", function(m) { messages.push("bye " + m.text) })
      
              hey.then(function() { bye.then(resume) })
            }})
      
            it("removes one of the listeners from the channel", function() { with(this) {
              client._receiveMessage({ channel: "/foo/bar", data: { text: "you" }})
              client.unsubscribe("/foo/*", hey)
              client._receiveMessage({ channel: "/foo/bar", data: { text: "you" }})
              assertEqual( ["hey you", "bye you", "bye you"], messages)
            }})
      
            it("does not send an unsubscribe message if one listener is removed", function() { with(this) {
              expect(dispatcher, "sendMessage").given(unsubscribeMessage, 72, {}).exactly(0)
              client.unsubscribe("/foo/*", bye)
            }})
      
            it("sends an unsubscribe message if each listener is removed", function(resume) { with(this) {
              expect(dispatcher, "sendMessage").given(unsubscribeMessage, 72, {})
              client.unsubscribe("/foo/*", bye)
              client.unsubscribe("/foo/*", hey)
              client.connect(resume)
            }})
      
            it("sends an unsubscribe message if all listeners are removed", function(resume) { with(this) {
              expect(dispatcher, "sendMessage").given(unsubscribeMessage, 72, {})
              client.unsubscribe("/foo/*")
              client.connect(resume)
            }})
          }})
      
          describe("with multiple subscriptions to different channels", function() { with(this) {
            before(function(resume) { with(this) {
              subscribe(client, "/foo").then(function() {
                return subscribe(client, "/bar")
              }).then(resume)
            }})
      
            it("sends multiple unsubscribe messages if given an array", function(resume) { with(this) {
              expect(dispatcher, "sendMessage").given({
                channel:      "/meta/unsubscribe",
                clientId:     "fakeid",
                subscription: "/foo",
                id:           instanceOf("string")
              }, 72, {})
              expect(dispatcher, "sendMessage").given({
                channel:      "/meta/unsubscribe",
                clientId:     "fakeid",
                subscription: "/bar",
                id:           instanceOf("string")
              }, 72, {})
              client.unsubscribe(["/foo", "/bar"])
              client.connect(resume)
            }})
          }})
        }})
      
        describe("publish", function() { with(this) {
          before(function() { this.createConnectedClient() })
      
          it("sends the message to the server with an ID", function(resume) { with(this) {
            expect(dispatcher, "sendMessage").given({
              channel:  "/messages/foo",
              clientId: "fakeid",
              data:     { hello: "world" },
              id:       instanceOf("string")
            }, 72, {})
            client.publish("/messages/foo", { hello: "world" })
            client.connect(resume)
          }})
      
          describe("on publish failure", function() { with(this) {
            before(function() { with(this) {
              stubResponse({ channel:    "/messages/foo",
                             error:      "407:/messages/foo:Failed to publish",
                             successful: false,
                             clientId:   "fakeid" })
            }})
      
            it("should not be published", function() { with(this) {
              var published = false
              client.publish("/messages/foo", { text: "hi" }).callback(function() { published = true })
              assert( !published )
            }})
      
            it("reports the error through an errback", function(resume) { with(this) {
              client.publish("/messages/foo", { text: "hi" }).errback(function(error) {
                resume(function() {
                  assertEqual( 407, error.code )
                  assertEqual( ["/messages/foo"], error.params )
                  assertEqual( "Failed to publish", error.message )
                })
              })
            }})
          }})
      
          describe("on receipt of the published message", function() { with(this) {
            before(function() { with(this) {
              stubResponse({ channel:    "/messages/foo",
                             data:       { text: "hi" },
                             clientId:   "fakeid" })
            }})
      
            it("does not trigger the callbacks", function() { with(this) {
              var published = false
              var publication = client.publish("/messages/foo", { text: "hi" })
              publication.callback(function() { published = true })
              publication.errback(function() { published = true })
              assert( !published )
            }})
          }})
      
          describe("with an outgoing extension installed", function() { with(this) {
            before(function() { with(this) {
              var extension = {
                outgoing: function(message, callback) {
                  message.ext = { auth: "password" }
                  callback(message)
                }
              }
              client.addExtension(extension)
            }})
      
            it("passes messages through the extension", function(resume) { with(this) {
              expect(dispatcher, "sendMessage").given({
                channel:  "/messages/foo",
                clientId: "fakeid",
                data:     { hello: "world" },
                id:       instanceOf("string"),
                ext:      { auth: "password" }
              }, 72, {})
              client.publish("/messages/foo", { hello: "world" })
              client.connect(resume)
            }})
          }})
      
          describe("with an incoming extension installed", function() { with(this) {
            before(function() { with(this) {
              var extension = {
                incoming: function(message, callback) {
                  message.ext = { auth: "password" }
                  callback(message)
                }
              }
              client.addExtension(extension)
            }})
      
            it("leaves the message unchanged", function(resume) { with(this) {
              expect(dispatcher, "sendMessage").given({
                channel:  "/messages/foo",
                clientId: "fakeid",
                data:     { hello: "world" },
                id:       instanceOf("string")
              }, 72, {})
              client.publish("/messages/foo", { hello: "world" })
              client.connect(resume)
            }})
          }})
        }})
      }})
      ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/javascript/dispatcher_spec.js�������������������������������������������������������0000664�0000000�0000000�00000033466�13711043772�0021257�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������var jstest = require("jstest").Test
      
      var asap       = require("asap"),
          Dispatcher = require("../../src/protocol/dispatcher"),
          Scheduler  = require("../../src/protocol/scheduler"),
          Transport  = require("../../src/transport"),
          Promise    = require("../../src/util/promise"),
          URI        = require("../../src/util/uri")
      
      var CustomScheduler = function() {
        Scheduler.apply(this, arguments)
        CustomScheduler.instance = this
      }
      CustomScheduler.prototype = new Scheduler()
      
      jstest.describe("Dispatcher", function() { with(this) {
        include(jstest.FakeClock)
      
        before(function() { with(this) {
          this.client    = {}
          this.endpoint  = "http://localhost/"
          this.transport = { endpoint: URI.parse(endpoint), connectionType: "long-polling" }
      
          stub(client, "trigger")
          stub(Transport, "get").yields([transport])
      
          this.dispatcher = Dispatcher.create(client, endpoint, options())
      
          clock.stub()
        }})
      
        after(function() { with(this) {
          clock.reset()
        }})
      
        define("options", function() {
          return {}
        })
      
        describe("endpointFor", function() { with(this) {
          define("options", function() {
            return {
              endpoints: { websocket: "http://sockets/" }
            }
          })
      
          it("returns the main endpoint for unspecified connection types", function() { with(this) {
            assertEqual( "http://localhost/", dispatcher.endpointFor("long-polling").href )
          }})
      
          it("returns an alternate endpoint where specified", function() { with(this) {
            assertEqual( "http://sockets/", dispatcher.endpointFor("websocket").href )
          }})
        }})
      
        describe("selectTransport", function() { with(this) {
          before(function() { with(this) {
            this.connectionTypes = ["long-polling", "callback-polling", "websocket"]
          }})
      
          it("asks Transport to select one of the given transports", function() { with(this) {
            expect(Transport, "get").given(dispatcher, connectionTypes, []).yields([transport])
            dispatcher.selectTransport(connectionTypes)
          }})
      
          it("asks Transport not to select disabled transports", function() { with(this) {
            dispatcher.disable("websocket")
            expect(Transport, "get").given(dispatcher, connectionTypes, ["websocket"]).yields([transport])
            dispatcher.selectTransport(connectionTypes)
          }})
      
          it("sets connectionType on the dispatcher", function() { with(this) {
            transport.connectionType = "transport-connection-type"
            dispatcher.selectTransport(connectionTypes)
            assertEqual( "transport-connection-type", dispatcher.connectionType )
          }})
      
          it("closes the existing transport if a new one is selected", function() { with(this) {
            var oldTransport = { endpoint: URI.parse(endpoint) }
            stub(Transport, "get").given(dispatcher, ["long-polling"], []).yields([oldTransport])
            dispatcher.selectTransport(["long-polling"])
      
            expect(oldTransport, "close").exactly(1)
            dispatcher.selectTransport(connectionTypes)
          }})
      
          it("does not close the existing transport if the same one is selected", function() { with(this) {
            dispatcher.selectTransport(["long-polling"])
      
            expect(transport, "close").exactly(0)
            dispatcher.selectTransport(connectionTypes)
          }})
        }})
      
        describe("messaging", function() { with(this) {
          before(function() { with(this) {
            this.message    = { id: 1 }
            this.request    = {}
            this.reqPromise = Promise.resolve(request)
      
            stub(transport, "close")
            stub(transport, "sendMessage").returns(reqPromise)
      
            dispatcher.selectTransport([])
          }})
      
          describe("sendMessage", function() { with(this) {
            it("does not send a message to the transport if closed", function() { with(this) {
              dispatcher.close()
              expect(transport, "sendMessage").exactly(0)
              dispatcher.sendMessage(message, 25)
            }})
      
            it("sends a message to the transport", function() { with(this) {
              expect(transport, "sendMessage").given({ id: 1 }).exactly(1).returning(reqPromise)
              dispatcher.sendMessage(message, 25)
            }})
      
            it("sends several different messages to the transport", function() { with(this) {
              expect(transport, "sendMessage").given({ id: 1 }).exactly(1).returning(reqPromise)
              expect(transport, "sendMessage").given({ id: 2 }).exactly(1).returning(reqPromise)
              dispatcher.sendMessage({ id: 1 }, 25)
              dispatcher.sendMessage({ id: 2 }, 25)
            }})
      
            it("does not resend a message if it's already being sent", function() { with(this) {
              expect(transport, "sendMessage").given({ id: 1 }).exactly(1).returning(reqPromise)
              dispatcher.sendMessage(message, 25)
              dispatcher.sendMessage(message, 25)
            }})
      
            it("sets a timeout to fail the message", function() { with(this) {
              dispatcher.sendMessage(message, 25)
              expect(dispatcher, "handleError").given({ id: 1 }).exactly(1)
              clock.tick(25000)
            }})
          }})
      
          describe("handleError", function() { with(this) {
            before(function() { with(this) {
              dispatcher.sendMessage(message, 25)
            }})
      
            it("does not try to resend messagess immediately", function() { with(this) {
              expect(transport, "sendMessage").exactly(0)
              dispatcher.handleError(message)
            }})
      
            it("resends messages immediately if instructed", function() { with(this) {
              expect(transport, "sendMessage").given({ id: 1 }).exactly(1).returning(reqPromise)
              dispatcher.handleError(message, true)
            }})
      
            it("resends a message automatically after a timeout on error", function() { with(this) {
              dispatcher.handleError(message)
              expect(transport, "sendMessage").given({ id: 1 }).exactly(1).returning(reqPromise)
              clock.tick(5500)
            }})
      
            it("does not resend a message if the dispatcher was closed while waiting", function() { with(this) {
              dispatcher.handleError(message)
              expect(transport, "sendMessage").exactly(0)
              clock.tick(3500)
              dispatcher.close()
              clock.tick(2000)
            }})
      
            it("aborts the request used to send the message", function(resume) { with(this) {
              expect(request, "abort").exactly(1)
              dispatcher.handleError(message)
              asap(resume)
            }})
      
            it("does not resend a message with an ID it does not recognize", function() { with(this) {
              dispatcher.handleError({ id: 2 })
              expect(transport, "sendMessage").exactly(0)
              clock.tick(5500)
            }})
      
            it("does not resend a message if it's waiting to resend", function() { with(this) {
              dispatcher.handleError(message)
              expect(transport, "sendMessage").exactly(0)
              clock.tick(2500)
              dispatcher.sendMessage(message, 25)
            }})
      
            it("does not schedule another resend if an error is reported while waiting to resend", function() { with(this) {
              expect(transport, "sendMessage").given({ id: 1 }).exactly(1)
              dispatcher.handleError(message)
              clock.tick(2500)
              dispatcher.handleError(message)
              clock.tick(5500)
            }})
      
            it("does not schedule a resend if the number of attempts has been exhausted", function() { with(this) {
              expect(transport, "sendMessage").given({ id: 2 }).exactly(2).returning(reqPromise)
              dispatcher.sendMessage({ id: 2 }, 25, { attempts: 2 })
              dispatcher.handleError({ id: 2 }, true)
              dispatcher.handleError({ id: 2 }, true)
            }})
      
            it("does not count down attempts when an error is reported while waiting to resend", function() { with(this) {
              dispatcher.sendMessage({ id: 2 }, 25, { attempts: 3 })
              dispatcher.handleError({ id: 2 })
              clock.tick(2500)
              dispatcher.handleError({ id: 2 }, true)
              clock.tick(5000)
              expect(transport, "sendMessage").given({ id: 2 }).exactly(1).returning(reqPromise)
              dispatcher.handleError({ id: 2 }, true)
            }})
      
            it("does not schedule a resend if the deadline has been reached", function() { with(this) {
              dispatcher.handleResponse({ id: 1, successful: true })
              dispatcher.sendMessage({ id: 2 }, 25, { deadline: 60 })
              expect(transport, "sendMessage").given({ id: 2 }).exactly(2).returning(reqPromise)
              clock.tick(90000)
            }})
      
            it("emits the transport:down event via the client", function() { with(this) {
              expect(client, "trigger").given("transport:down").exactly(1)
              dispatcher.handleError(message)
            }})
      
            it("only emits transport:down once, when the first error is received", function() { with(this) {
              dispatcher.sendMessage({ id: 2 }, 25)
              expect(client, "trigger").given("transport:down").exactly(1)
              dispatcher.handleError({ id: 1 })
              dispatcher.handleError({ id: 2 })
            }})
      
            it("emits transport:down again if there was a message since the last event", function() { with(this) {
              dispatcher.sendMessage({ id: 2 }, 25)
              expect(client, "trigger").given("transport:down").exactly(2)
              dispatcher.handleError({ id: 1 })
              dispatcher.handleResponse({ id: 3 })
              dispatcher.handleError({ id: 2 })
            }})
          }})
      
          describe("with a scheduler", function() { with(this) {
            define("options", function() {
              return { scheduler: CustomScheduler }
            })
      
            before(function() { with(this) {
              dispatcher.sendMessage(message, 25)
            }})
      
            it("notifies the scheduler that the message failed", function() { with(this) {
              expect(CustomScheduler.instance, "fail").exactly(1)
              dispatcher.handleError(message)
            }})
      
            it("asks the scheduler how long to wait before retrying", function() { with(this) {
              expect(CustomScheduler.instance, "getInterval").exactly(1).returning(1)
              dispatcher.handleError(message)
            }})
      
            it("resends a message after the interval given by the scheduler", function() { with(this) {
              stub(CustomScheduler.instance, "getInterval").returns(3)
              dispatcher.handleError(message)
              expect(transport, "sendMessage").given({ id: 1 }).exactly(1).returning(reqPromise)
              clock.tick(3500)
            }})
      
            it("asks the scheduler what the message timeout should be", function() { with(this) {
              expect(CustomScheduler.instance, "getTimeout").exactly(1).returning(25)
              dispatcher.handleError(message, true)
            }})
      
            it("waits the specified amount of time to fail the message", function() { with(this) {
              stub(CustomScheduler.instance, "getTimeout").returns(3)
              dispatcher.handleError(message, true)
              expect(dispatcher, "handleError").given({ id: 1 }).exactly(1)
              clock.tick(3000)
            }})
      
            it("asks the scheduler whether the message is deliverable", function() { with(this) {
              expect(CustomScheduler.instance, "isDeliverable").returning(true)
              dispatcher.handleError(message, true)
            }})
      
            it("resends the message if it's deliverable", function() { with(this) {
              stub(CustomScheduler.instance, "isDeliverable").returns(true)
              expect(transport, "sendMessage").given({ id: 1 }).exactly(1).returning(reqPromise)
              dispatcher.handleError(message, true)
            }})
      
            it("does not resend the message if it's not deliverable", function() { with(this) {
              stub(CustomScheduler.instance, "isDeliverable").returns(false)
              expect(transport, "sendMessage").exactly(0)
              dispatcher.handleError(message, true)
            }})
      
            it("notifies the scheduler that the message is being sent", function() { with(this) {
              expect(CustomScheduler.instance, "send").exactly(1)
              dispatcher.handleError(message, true)
            }})
      
            it("notifies the scheduler to abort of it's not deliverable", function() { with(this) {
              stub(CustomScheduler.instance, "isDeliverable").returns(false)
              expect(CustomScheduler.instance, "abort").exactly(1)
              dispatcher.handleError(message, true)
            }})
          }})
      
          describe("handleResponse", function() { with(this) {
            before(function() { with(this) {
              dispatcher.sendMessage(message, 25)
            }})
      
            it("clears the timeout to resend the message if successful=true", function() { with(this) {
              expect(dispatcher, "handleError").exactly(0)
              dispatcher.handleResponse({ id: 1, successful: true })
              clock.tick(25000)
            }})
      
            it("clears the timeout to resend the message if successful=false", function() { with(this) {
              expect(dispatcher, "handleError").exactly(0)
              dispatcher.handleResponse({ id: 1, successful: false })
              clock.tick(25000)
            }})
      
            it("leaves the timeout to resend the message if successful is missing", function() { with(this) {
              expect(dispatcher, "handleError").given({ id: 1 }).exactly(1)
              dispatcher.handleResponse(message)
              clock.tick(25000)
            }})
      
            it("emits the message as an event", function() { with(this) {
              expect(dispatcher, "trigger").given("message", { id: 3 }).exactly(1)
              dispatcher.handleResponse({ id: 3 })
            }})
      
            it("emits the transport:up event via the client", function() { with(this) {
              expect(client, "trigger").given("transport:up").exactly(1)
              dispatcher.handleResponse(message)
            }})
      
            it("only emits transport:up once, when the first message is received", function() { with(this) {
              expect(client, "trigger").given("transport:up").exactly(1)
              dispatcher.handleResponse({ id: 1 })
              dispatcher.handleResponse({ id: 2 })
            }})
      
            it("emits transport:up again if there was an error since the last event", function() { with(this) {
              expect(client, "trigger").given("transport:up").exactly(2)
              dispatcher.handleResponse({ id: 2 })
              dispatcher.handleError({ id: 1 })
              dispatcher.handleResponse({ id: 3 })
            }})
      
            it("handles id collisions from another client", function() { with(this) {
              expect(client, "trigger").given("transport:down").exactly(1)
              dispatcher.handleResponse({ 'id': 1 })
              dispatcher.handleError({ 'id': 1 })
            }})
          }})
        }})
      }})
      ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/javascript/engine/������������������������������������������������������������������0000775�0000000�0000000�00000000000�13711043772�0017012�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/javascript/engine/memory_spec.js����������������������������������������������������0000664�0000000�0000000�00000000433�13711043772�0021672�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������var jstest = require("jstest").Test
      
      var Memory = require("../../../src/engines/memory")
      
      require("../engine_spec")
      
      jstest.describe("Memory engine", function() { with(this) {
        before(function() {
          this.engineOpts = { type: Memory }
        })
      
        itShouldBehaveLike("faye engine")
      }})
      �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/javascript/engine_spec.js�����������������������������������������������������������0000664�0000000�0000000�00000034505�13711043772�0020371�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������var jstest = require("jstest").Test,
          Set    = require("jstest").Set
      
      var Proxy  = require("../../src/engines/proxy"),
          assign = require("../../src/util/assign"),
          random = require("../../src/util/random")
      
      var EngineSteps = jstest.asyncSteps({
        disconnect_engine: function(resume) {
          this.engine.disconnect()
          resume()
        },
      
        create_client: function(name, resume) {
          var inboxes = this._inboxes = this._inboxes || {}
          var clients = this._clients = this._clients || {}
          this.engine.createClient(function(clientId) {
            clients[name] = clientId
            inboxes[name] = inboxes[name] || []
            resume()
          })
        },
      
        connect: function(name, engine, resume) {
          var clientId = this._clients[name]
          var inboxes  = this._inboxes
          engine.connect(clientId, {}, function(messages) {
            for (var i = 0, n = messages.length; i < n; i++) {
              delete messages[i].id
              inboxes[name].push(messages[i])
            }
          })
          setTimeout(resume, 10)
        },
      
        destroy_client: function(name, resume) {
          this.engine.destroyClient(this._clients[name], resume)
        },
      
        check_client_id: function(name, pattern, resume) {
          this.assertMatch(pattern, this._clients[name])
          resume()
        },
      
        check_num_clients: function(n, resume) {
          var ids = new Set()
          for (var key in this._clients) ids.add(this._clients[key])
          this.assertEqual(n, ids.count())
          resume()
        },
      
        check_client_exists: function(name, exists, resume) {
          var tc = this
          tc.engine.clientExists(tc._clients[name], function(actual) {
            tc.assertEqual(exists, actual)
            resume()
          })
        },
      
        subscribe: function(name, channel, resume) {
          this.engine.subscribe(this._clients[name], channel, resume)
        },
      
        unsubscribe: function(name, channel, resume) {
          this.engine.unsubscribe(this._clients[name], channel, resume)
        },
      
        publish: function(messages, resume) {
          messages = [].concat(messages)
          for (var i = 0, n = messages.length; i < n; i++) {
            var message = assign({ id: random() }, messages[i])
            this.engine.publish(message)
          }
          setTimeout(resume, 20)
        },
      
        publish_by: function(name, message, resume) {
          message = assign({ clientId: this._clients[name], id: random() }, message)
          this.engine.publish(message)
          setTimeout(resume, 10)
        },
      
        ping: function(name, resume) {
          this.engine.ping(this._clients[name])
          resume()
        },
      
        clock_tick: function(time, resume) {
          setTimeout(resume, time)
        },
      
        expect_non_exclusive_event: function(name, event, args, engine, resume) {
          var params  = [this._clients[name]].concat(args),
              handler = function() {};
      
          // we don't care if the event is called for other clients
          var filter = function() {
            if (arguments[0] == params[0]) {
              handler.apply(undefined, Array.prototype.slice.call(arguments));
            }
          };
      
          engine.bind(event, filter)
          this.expect(handler, "apply").given(undefined, params).exactly(1)
          resume()
        },
      
        expect_event: function(name, event, args, resume) {
          var params  = [this._clients[name]].concat(args),
              handler = function() {}
      
          this.engine.bind(event, handler)
          this.expect(handler, "apply").given(undefined, params)
          resume()
        },
      
        expect_no_event: function(name, event, args, resume) {
          var params  = [this._clients[name]].concat(args),
              handler = function() {}
      
          this.engine.bind(event, handler)
          this.expect(handler, "apply").given(undefined, params).exactly(0)
          resume()
        },
      
        expect_message: function(name, messages, resume) {
          this.assertEqual(messages, this._inboxes[name])
          resume()
        },
      
        expect_no_message: function(name, resume) {
          this.assertEqual([], this._inboxes[name])
          resume()
        },
      
        check_different_messages: function(a, b, resume) {
          this.assertNotSame(this._inboxes[a][0], this._inboxes[b][0])
          resume()
        }
      })
      
      jstest.describe("Pub/sub engines", function() { with(this) {
        sharedExamplesFor("faye engine", function() { with(this) {
          include(jstest.Helpers)
          include(EngineSteps)
      
          define("create_engine", function() { with(this) {
            var opts = assign(options(), engineOpts)
            return new Proxy(opts)
          }})
      
          define("options", function() { return { timeout: 1 } })
      
          before(function() { with(this) {
            this.engine = create_engine()
            create_client("alice")
            create_client("bob")
            create_client("carol")
          }})
      
          describe("createClient", function() { with(this) {
            it("returns a client id", function() { with(this) {
              create_client("dave")
              check_client_id("dave", /^[a-z0-9]+$/)
            }})
      
            it("returns a different id every time", function() { with(this) {
              $R(1,7).forEach(function(i) { create_client("client" + i) })
              check_num_clients(10)
            }})
      
            it("publishes an event", function() { with(this) {
              expect(engine, "trigger").given("handshake", match(/^[a-z0-9]+$/))
              create_client("dave")
            }})
      
            describe("gc", function() { with(this) {
              define("options", function() { return { timeout: 0.3, gc: 0.2 } })
      
              it("doesn't prematurely remove a client after creation", function() { with(this) {
                clock_tick(250)
                check_client_exists("alice", true)
              }})
            }})
          }})
      
          describe("clientExists", function() { with(this) {
            it("returns true if the client id exists", function() { with(this) {
              check_client_exists("alice", true)
            }})
      
            it("returns false if the client id does not exist", function() { with(this) {
              check_client_exists("anything", false)
            }})
          }})
      
          describe("ping", function() { with(this) {
            define("options", function() { return { timeout: 0.3, gc: 0.08 } })
      
            it("removes a client if it does not ping often enough", function() { with(this) {
              clock_tick(700)
              check_client_exists("alice", false)
            }})
      
            it("prolongs the life of a client", function() { with(this) {
              clock_tick(450)
              ping("alice")
              clock_tick(450)
              check_client_exists("alice", true)
              clock_tick(450)
              check_client_exists("alice", false)
            }})
          }})
      
          describe("destroyClient", function() { with(this) {
            it("removes the given client", function() { with(this) {
              destroy_client("alice")
              check_client_exists("alice", false)
            }})
      
            it("publishes an event", function() { with(this) {
              expect_event("alice", "disconnect", [])
              destroy_client("alice")
            }})
      
            describe("when the client has subscriptions", function() { with(this) {
              before(function() { with(this) {
                this.message = { "channel": "/messages/foo", "data": "ok" }
                subscribe("alice", "/messages/foo")
              }})
      
              it("stops the client receiving messages", function() { with(this) {
                connect("alice", engine)
                destroy_client("alice")
                publish(message)
                expect_no_message("alice")
              }})
      
              it("publishes an event", function() { with(this) {
                expect_event("alice", "disconnect", [])
                destroy_client("alice")
              }})
            }})
          }})
      
          describe("subscribe", function() { with(this) {
            it("publishes an event", function() { with(this) {
              expect_event("alice", "subscribe", ["/messages/foo"])
              subscribe("alice", "/messages/foo")
            }})
      
            describe("when the client is subscribed to the channel", function() { with(this) {
              before(function() { this.subscribe("alice", "/messages/foo") })
      
              it("does not publish an event", function() { with(this) {
                expect_no_event("alice", "subscribe", ["/messages/foo"])
                subscribe("alice", "/messages/foo")
              }})
            }})
          }})
      
      
          describe("unsubscribe", function() { with(this) {
            before(function() { this.subscribe("alice", "/messages/bar") })
      
            it("does not publish an event", function() { with(this) {
              expect_no_event("alice", "unsubscribe", ["/messages/foo"])
              unsubscribe("alice", "/messages/foo")
            }})
      
            describe("when the client is subscribed to the channel", function() { with(this) {
              before(function() { this.subscribe("alice", "/messages/foo") })
      
              it("publishes an event", function() { with(this) {
                expect_event("alice", "unsubscribe", ["/messages/foo"])
                unsubscribe("alice", "/messages/foo")
              }})
            }})
          }})
      
          describe("publish", function() { with(this) {
            before(function() { with(this) {
              this.message = { "channel": "/messages/foo", "data": "ok", "blank": null }
              connect("alice", engine)
              connect("bob", engine)
              connect("carol", engine)
            }})
      
            describe("with no subscriptions", function() { with(this) {
              it("delivers no messages", function() { with(this) {
                publish(message)
                expect_no_message("alice")
                expect_no_message("bob")
                expect_no_message("carol")
              }})
      
              it("publishes a :publish event with a clientId", function() { with(this) {
                expect_event("bob", "publish", ["/messages/foo", "ok"])
                publish_by("bob", message)
              }})
      
              it("publishes a :publish event with no clientId", function() { with(this) {
                expect_event(null, "publish", ["/messages/foo", "ok"])
                publish(message)
              }})
            }})
      
            describe("with a subscriber", function() { with(this) {
              before(function() { with(this) {
                subscribe("alice", "/messages/foo")
              }})
      
              it("delivers multibyte messages correctly", function() { with(this) {
                message.data = "Apple = "
                publish(message)
                expect_message("alice", [message])
              }})
      
              it("delivers messages to the subscribed client", function() { with(this) {
                publish(message)
                expect_message("alice", [message])
              }})
      
              it("publishes a :publish event with a clientId", function() { with(this) {
                expect_event("bob", "publish", ["/messages/foo", "ok"])
                publish_by("bob", message)
              }})
            }})
      
            describe("with a subscriber that is removed", function() { with(this) {
              before(function() { with(this) {
                subscribe("alice", "/messages/foo")
                unsubscribe("alice", "/messages/foo")
              }})
      
              it("does not deliver messages to unsubscribed clients", function() { with(this) {
                publish(message)
                expect_no_message("alice")
                expect_no_message("bob")
                expect_no_message("carol")
              }})
      
              it("publishes a :publish event with a clientId", function() { with(this) {
                expect_event("bob", "publish", ["/messages/foo", "ok"])
                publish_by("bob", message)
              }})
            }})
      
            describe("with multiple subscribers", function() { with(this) {
              before(function() { with(this) {
                subscribe("alice", "/messages/foo")
                subscribe("bob",   "/messages/bar")
                subscribe("carol", "/messages/foo")
              }})
      
              it("delivers messages to the subscribed clients", function() { with(this) {
                publish(message)
                expect_message("alice", [message])
                expect_no_message("bob")
                expect_message("carol", [message])
              }})
            }})
      
            describe("with a single wildcard", function() { with(this) {
              before(function() { with(this) {
                subscribe("alice", "/messages/*")
                subscribe("bob",   "/messages/bar")
                subscribe("carol", "/*")
              }})
      
              it("delivers messages to matching subscriptions", function() { with(this) {
                publish(message)
                expect_message("alice", [message])
                expect_no_message("bob")
                expect_no_message("carol")
              }})
            }})
      
            describe("with a double wildcard", function() { with(this) {
              before(function() { with(this) {
                subscribe("alice", "/messages/**")
                subscribe("bob",   "/messages/bar")
                subscribe("carol", "/**")
              }})
      
              it("delivers messages to matching subscriptions", function() { with(this) {
                publish(message)
                expect_message("alice", [message])
                expect_no_message("bob")
                expect_message("carol", [message])
              }})
      
              it("delivers a unique copy of the message to each client", function() { with(this) {
                publish(message)
                check_different_messages("alice", "carol")
              }})
            }})
      
            describe("with multiple matching subscriptions for the same client", function() { with(this) {
              before(function() { with(this) {
                subscribe("alice", "/messages/*")
                subscribe("alice", "/messages/foo")
              }})
      
              it("delivers each message once to each client", function() { with(this) {
                publish(message)
                expect_message("alice", [message])
              }})
      
              it("delivers the message as many times as it is published", function() { with(this) {
                publish([message, message])
                expect_message("alice", [message, message])
              }})
            }})
          }})
        }})
      
        sharedBehavior("distributed engine", function() { with(this) {
          include(jstest.Helpers)
          include(EngineSteps)
      
          define("create_engine", function() { with(this) {
            var opts = assign(options(), engineOpts)
            return new Proxy(opts)
          }})
      
          define("options", function() { return { timeout: 1 } })
      
          before(function() { with(this) {
            this.left   = create_engine()
            this.right  = create_engine()
            this.engine = left
      
            create_client("alice")
            create_client("bob")
      
            connect("alice", left)
          }})
      
          describe("publish", function() { with(this) {
            before(function() { with(this) {
              subscribe("alice", "/foo")
              publish({ channel: "/foo", data: "first" })
            }})
      
            it("only delivers each message once", function() { with(this) {
              expect_message("alice", [{ channel: "/foo", data: "first" }])
              publish({ channel: "/foo", data: "second" })
              connect("alice", right)
              expect_message("alice", [{ channel: "/foo", data: "first" }, { channel: "/foo", data: "second" }])
            }})
          }})
      
          describe("gc", function() { with(this) {
            define("options", function() { return { timeout: 0.3, gc: 0.08 } })
      
            it("calls close in each engine when a client is removed", function() { with(this) {
              expect_non_exclusive_event("alice", "close", [], this.left);
              expect_non_exclusive_event("alice", "close", [], this.right);
      
              clock_tick(700);
            }})
          }})
        }})
      }})
      �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/javascript/grammar_spec.js����������������������������������������������������������0000664�0000000�0000000�00000004703�13711043772�0020547�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������var jstest = require("jstest").Test
      
      var Grammar = require("../../src/protocol/grammar")
      
      jstest.describe("Grammar", function() { with(this) {
        describe("CHANNEL_NAME", function() { with(this) {
          it("matches valid channel names", function() { with(this) {
            assertMatch( Grammar.CHANNEL_NAME, "/fo_o/$@()bar" )
          }})
      
          it("does not match channel patterns", function() { with(this) {
            assertNoMatch( Grammar.CHANNEL_NAME, "/foo/**" )
          }})
      
          it("does not match invalid channel names", function() { with(this) {
            assertNoMatch( Grammar.CHANNEL_NAME, "foo/$@()bar" )
            assertNoMatch( Grammar.CHANNEL_NAME, "/foo/$@()bar/" )
            assertNoMatch( Grammar.CHANNEL_NAME, "/fo o/$@()bar" )
          }})
        }})
      
        describe("CHANNEL_PATTERN", function() { with(this) {
          it("does not match channel names", function() { with(this) {
            assertNoMatch( Grammar.CHANNEL_PATTERN, "/fo_o/$@()bar" )
          }})
      
          it("matches valid channel patterns", function() { with(this) {
            assertMatch( Grammar.CHANNEL_PATTERN, "/foo/**" )
            assertMatch( Grammar.CHANNEL_PATTERN, "/foo/*" )
          }})
      
          it("does not match invalid channel patterns", function() { with(this) {
            assertNoMatch( Grammar.CHANNEL_PATTERN, "/foo/**/*" )
          }})
        }})
      
        describe("ERROR", function() { with(this) {
          it("matches an error with an argument", function() { with(this) {
            assertMatch( Grammar.ERROR, "402:xj3sjdsjdsjad:Unknown Client ID" )
          }})
      
          it("matches an error with many arguments", function() { with(this) {
            assertMatch( Grammar.ERROR, "403:xj3sjdsjdsjad,/foo/bar:Subscription denied" )
          }})
      
          it("matches an error with no arguments", function() { with(this) {
            assertMatch( Grammar.ERROR, "402::Unknown Client ID" )
          }})
      
          it("does not match an error with no code", function() { with(this) {
            assertNoMatch( Grammar.ERROR, ":xj3sjdsjdsjad:Unknown Client ID" )
          }})
      
          it("does not match an error with an invalid code", function() { with(this) {
            assertNoMatch( Grammar.ERROR, "40:xj3sjdsjdsjad:Unknown Client ID" )
          }})
        }})
      
        describe("VERSION", function() { with(this) {
          it("matches a version number", function() { with(this) {
            assertMatch( Grammar.VERSION, "9" )
            assertMatch( Grammar.VERSION, "9.0.a-delta1" )
          }})
      
          it("does not match invalid version numbers", function() { with(this) {
            assertNoMatch( Grammar.VERSION, "9.0.a-delta1." )
            assertNoMatch( Grammar.VERSION, "" )
          }})
        }})
      }})
      �������������������������������������������������������������faye-1.4.0/spec/javascript/node_adapter_spec.js�����������������������������������������������������0000664�0000000�0000000�00000030143�13711043772�0021543�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������var jstest      = require("jstest").Test,
          http        = require("http"),
          querystring = require("querystring")
      
      var NodeAdapter = require("../../src/adapters/node_adapter"),
          Server      = require("../../src/protocol/server"),
          assign      = require("../../src/util/assign")
      
      function handleResponse(request, self, resume) {
        request.on("response", function(response) {
          self._response = response
      
          var data = ""
          response.on("data", function(c) { data += c })
      
          response.on("end", function() {
            self._responseBody = data
            resume()
          })
        })
      }
      
      var NodeAdapterSteps = jstest.asyncSteps({
        start_server: function(port, resume) {
          this._port = port
          this._app  = new NodeAdapter(this.options())
          this._http = http.createServer()
          this._app.attach(this._http)
          this._http.listen(port, resume)
        },
      
        stop_server: function(resume) {
          this._http.on('close', resume)
          this._http.close()
        },
      
        header: function(key, value, resume) {
          this._headers = this._headers || {}
          this._headers[key] = value
          resume()
        },
      
        options_request: function(path, params, resume) {
          var request = http.request({
            method:  "OPTIONS",
            host:    "localhost",
            port:    this._port,
            path:    path,
            headers: this._headers
          })
      
          handleResponse(request, this, resume)
          request.end()
        },
      
        get: function(path, params, resume) {
          var body = querystring.stringify(params)
      
          var request = http.request({
            method: "GET",
            host:   "localhost",
            port:   this._port,
            path:   path + (body ? "?" + body : "")
          })
      
          handleResponse(request, this, resume)
          request.end()
        },
      
        post: function(path, params, resume) {
          var body = (typeof params === "string") ? params : querystring.stringify(params)
      
          var headers = assign({}, this._headers, {
            "Host":           "localhost",
            "Content-Length": body.length
          })
      
          var request = http.request({
            method:   "POST",
            host:     "localhost",
            port:     this._port,
            path:     path,
            headers:  headers
          })
      
          handleResponse(request, this, resume)
      
          request.write(body)
          request.end()
        },
      
        check_status: function(code, resume) {
          this.assertEqual(code, this._response.statusCode)
          resume()
        },
      
        check_header(name, value, resume) {
          this.assertEqual(value, this._response.headers[name])
          resume()
        },
      
        check_content_type: function(type, resume) {
          this.assertEqual(type + "; charset=utf-8", this._response.headers["content-type"])
          resume()
        },
      
        check_body: function(body, resume) {
          if (typeof body === "string")
            this.assertEqual(body, this._responseBody)
          else
            this.assertMatch(body, this._responseBody)
          resume()
        },
      
        check_json: function(object, resume) {
          this.assertEqual(object, JSON.parse(this._responseBody))
          resume()
        }
      })
      
      jstest.describe("NodeAdapter", function() { with(this) {
        include(NodeAdapterSteps)
      
        define("options", function() {
          return { mount: "/bayeux", timeout: 30 }
        })
      
        before(function() { with(this) {
          this.server = {}
          expect(Server, "create").given(options()).returning(server)
          start_server(8282)
        }})
      
        after(function() { this.stop_server() })
      
        describe("OPTIONS requests", function() { with(this) {
          describe("with origin specified", function() { with(this) {
            before(function() { with(this) {
              header("Origin", "http://example.com")
            }})
      
            it("returns a matching cross-origin access control header", function() { with(this) {
              options_request("/bayeux")
              check_header("access-control-allow-origin", "http://example.com")
              check_header("access-control-allow-credentials", "true")
              check_header("access-control-allow-headers", "Accept, Authorization, Content-Type, Pragma, X-Requested-With")
              check_header("access-control-allow-methods", "POST, GET")
              check_header("access-control-max-age", "86400")
            }})
          }})
      
          describe("with referer specified", function() { with(this) {
            before(function() { with(this) {
              header("referer", "http://example.com")
            }})
      
            it("returns a matching cross-origin access control header", function() { with(this) {
              options_request("/bayeux")
              check_header("access-control-allow-origin", "http://example.com")
              check_header("access-control-allow-credentials", "true")
              check_header("access-control-allow-headers", "Accept, Authorization, Content-Type, Pragma, X-Requested-With")
              check_header("access-control-allow-methods", "POST, GET")
              check_header("access-control-max-age", "86400")
            }})
          }})
      
          describe("with no origin specified", function() { with(this) {
            it("returns a wildcard cross-origin access control header", function() { with(this) {
              options_request("/bayeux")
              check_header("access-control-allow-origin", "*")
              check_header("access-control-allow-credentials", "true")
              check_header("access-control-allow-headers", "Accept, Authorization, Content-Type, Pragma, X-Requested-With")
              check_header("access-control-allow-methods", "POST, GET")
              check_header("access-control-max-age", "86400")
            }})
          }})
        }})
      
        describe("POST requests", function() { with(this) {
          describe("with cross-origin access control", function() { with(this) {
            sharedBehavior("cross-origin request", function() { with(this) {
              before(function() { with(this) {
                header("Origin", "http://example.com")
              }})
      
              it("returns a matching cross-origin access control header", function() { with(this) {
                stub(server, "process").yields([[]])
                post("/bayeux", { message: "[]" })
                check_header("access-control-allow-origin", "http://example.com")
              }})
      
              it("forwards the message param onto the server", function() { with(this) {
                expect(server, "process").given({ channel: "/plain" }, objectIncluding({ headers: instanceOf(Object) })).yielding([[]])
                post("/bayeux", "message=%7B%22channel%22%3A%22%2Fplain%22%7D")
              }})
      
              it("returns the server's response as JSON", function() { with(this) {
                stub(server, "process").yields([[{ channel: "/meta/handshake" }]])
                post("/bayeux", "message=%5B%5D")
                check_status(200)
                check_content_type("application/json")
                check_header("content-length", "31")
                check_json([{ channel: "/meta/handshake" }])
              }})
      
              it("returns a 400 response if malformed JSON is given", function() { with(this) {
                expect(server, "process").exactly(0)
                post("/bayeux", "message=%7B%5B")
                check_status(400)
                check_content_type("text/plain")
              }})
      
              it("returns a 400 response if primitive JSON is given", function() { with(this) {
                expect(server, "process").exactly(0)
                post("/bayeux", "message=1")
                check_status(400)
                check_content_type("text/plain")
              }})
            }})
      
            describe("with text/plain", function() { with(this) {
              before(function() { this.header("Content-Type", "text/plain") })
              behavesLike("cross-origin request")
            }})
      
            describe("with application/xml", function() { with(this) {
              before(function() { this.header("Content-Type", "application/xml") })
              behavesLike("cross-origin request")
            }})
          }})
      
          describe("with application/json", function() { with(this) {
            before(function() { with(this) {
              header("Content-Type", "application/json")
            }})
      
            it("does not return an access control header", function() { with(this) {
              stub(server, "process").yields([[]])
              post("/bayeux", "[]")
              check_header("access-control-allow-origin", undefined)
            }})
      
            it("forwards the POST body onto the server", function() { with(this) {
              expect(server, "process").given({ channel: "/foo" }, objectIncluding({ headers: instanceOf(Object) })).yielding([[]])
              post("/bayeux", '{ "channel":"/foo" }')
            }})
      
            it("returns the server's response as JSON", function() { with(this) {
              stub(server, "process").yields([[{ channel: "/meta/handshake" }]])
              post("/bayeux", "[]")
              check_status(200)
              check_content_type("application/json")
              check_header("content-length", "31")
              check_json([{ channel: "/meta/handshake" }])
            }})
      
            it("returns a 400 response if malformed JSON is given", function() { with(this) {
              expect(server, "process").exactly(0)
              post("/bayeux", "[ }")
              check_status(400)
              check_content_type("text/plain")
            }})
          }})
      
          describe("with no content type", function() { with(this) {
            it("forwards the message param onto the server", function() { with(this) {
              expect(server, "process").given({ channel: "/foo" }, objectIncluding({ headers: instanceOf(Object) })).yielding([[]])
              post("/bayeux", { message: '{ "channel":"/foo" }' })
            }})
      
            it("returns the server's response as JSON", function() { with(this) {
              stub(server, "process").yields([[{ channel: "/meta/handshake" }]])
              post("/bayeux", { message: "[]" })
              check_status(200)
              check_content_type("application/json")
              check_header("content-length", "31")
              check_json([{ channel: "/meta/handshake" }])
            }})
      
            it("returns a 400 response if malformed JSON is given", function() { with(this) {
              expect(server, "process").exactly(0)
              post("/bayeux", { message: "[ }" })
              check_status(400)
              check_content_type("text/plain")
            }})
          }})
        }})
      
        describe("GET requests", function() { with(this) {
          before(function() { with(this) {
            this.params = { message: '{ "channel":"/foo" }', jsonp: "callback" }
          }})
      
          describe("with valid params", function() { with(this) {
            it("forwards the message param onto the server", function() { with(this) {
              expect(server, "process").given({ channel: "/foo" }, objectIncluding({ headers: instanceOf(Object) })).yielding([[]])
              get("/bayeux", params)
            }})
      
            it("returns the server's response as JavaScript", function() { with(this) {
              stub(server, "process").yields([[{ channel: "/meta/handshake" }]])
              get("/bayeux", params)
              check_status(200)
              check_content_type("text/javascript")
              check_header("content-length", "46")
              check_body('/**/callback([{"channel":"/meta/handshake"}]);')
            }})
      
            it("does not let the client cache the response", function() { with(this) {
              stub(server, "process").yields([[{ channel: "/meta/handshake" }]])
              get("/bayeux", params)
              check_header("cache-control", "no-cache, no-store")
            }})
          }})
      
          describe("missing jsonp", function() { with(this) {
            before(function() { with(this) {
              delete params.jsonp
            }})
      
            it("returns the server's response using the default callback", function() { with(this) {
              stub(server, "process").yields([[{ channel: "/meta/handshake" }]])
              get("/bayeux", params)
              check_status(200)
              check_content_type("text/javascript")
              check_header("content-length", "51")
              check_body('/**/jsonpcallback([{"channel":"/meta/handshake"}]);')
            }})
          }})
      
          sharedBehavior("bad GET request", function() { with(this) {
            it("does not call the server", function() { with(this) {
              expect(server, "process").exactly(0)
              get("/bayeux", params)
            }})
      
            it("returns a 400 response", function() { with(this) {
              get("/bayeux", params)
              check_status(400)
              check_content_type("text/plain")
            }})
          }})
      
          describe("with malformed JSON", function() { with(this) {
            before(function() { with(this) {
              params.message = "[ }"
            }})
            behavesLike("bad GET request")
          }})
      
          describe("with a callback that's not a JS identifier", function() { with(this) {
            before(function() { with(this) {
              params.jsonp = "42"
            }})
            behavesLike("bad GET request")
          }})
      
          describe("missing message", function() { with(this) {
            before(function() { with(this) {
              delete params.message
            }})
            behavesLike("bad GET request")
          }})
        }})
      }})
      �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/javascript/publisher_spec.js��������������������������������������������������������0000664�0000000�0000000�00000001514�13711043772�0021113�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������var jstest = require("jstest").Test
      
      var Publisher = require("../../src/mixins/publisher"),
          assign    = require("../../src/util/assign")
      
      jstest.describe("Publisher", function() { with(this) {
        before(function() { with(this) {
          this.publisher = assign({}, Publisher)
        }})
      
        describe("with subscribers that remove themselves", function() { with(this) {
          before(function() { with(this) {
            this.calledA = false
            this.calledB = false
      
            this.handler = function() {
              calledA = true
              publisher.unbind("event", handler)
            }
      
            publisher.bind("event", handler)
            publisher.bind("event", function() { calledB = true })
          }})
      
          it("successfully calls all the callbacks", function() { with(this) {
            publisher.trigger("event")
            assert( calledA )
            assert( calledB )
          }})
        }})
      }})
      ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/javascript/server/������������������������������������������������������������������0000775�0000000�0000000�00000000000�13711043772�0017053�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/javascript/server/connect_spec.js���������������������������������������������������0000664�0000000�0000000�00000013272�13711043772�0022061�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������var jstest = require("jstest").Test
      
      var Engine = require("../../../src/engines/proxy"),
          Server = require("../../../src/protocol/server")
      
      jstest.describe("Server connect", function() { with(this) {
        before(function() { with(this) {
          this.engine = {}
          stub(Engine, "get").returns(engine)
          this.server = new Server()
        }})
      
        describe("#connect", function() { with(this) {
          before(function() { with(this) {
            this.clientId = "fakeclientid"
            this.message = { channel:  "/meta/connect",
                             clientId: "fakeclientid",
                             connectionType: "long-polling" }
          }})
      
          describe("with valid parameters", function() { with(this) {
            before(function() { with(this) {
              message.advice = { timeout: 60 }
              expect(engine, "clientExists").given(clientId).yielding([true])
            }})
      
            it("connects to the engine to wait for new messages", function() { with(this) {
              expect(engine, "connect").given(clientId, { timeout: 60 }).yielding([[]])
              server.connect(message, false, function() {})
            }})
      
            it("returns a successful response and any queued messages", function() { with(this) {
              stub(engine, "connect").yields([{ channel: "/x", data: "hello" }])
              server.connect(message, false, function(response) {
                assertEqual([
                  { channel:    "/meta/connect",
                    successful: true,
                    clientId:   clientId
                  },
                  { channel: "/x",
                    data:    "hello"
                  }
                ], response)
              })
            }})
      
            describe("with a message id", function() { with(this) {
              before(function() { this.message.id = "foo" })
      
              it("returns the same id", function() { with(this) {
                stub(engine, "connect")
                server.connect(message, false, function(response) {
                  assertEqual({
                      channel:    "/meta/connect",
                      successful: true,
                      clientId:   clientId,
                      id:         "foo"
                    }, response)
                })
              }})
            }})
          }})
      
          describe("with an unknown client", function() { with(this) {
            before(function() { with(this) {
              expect(engine, "clientExists").given(clientId).yielding([false])
            }})
      
            it("does not connect to the engine", function() { with(this) {
              expect(engine, "connect").exactly(0)
              server.connect(message, false, function() {})
            }})
      
            it("returns an unsuccessful response", function() { with(this) {
              server.connect(message, false, function(response) {
                assertEqual({
                    channel:    "/meta/connect",
                    successful: false,
                    error:      "401:fakeclientid:Unknown client"
                  }, response)
              })
            }})
          }})
      
          describe("missing clientId", function() { with(this) {
            before(function() { with(this) {
              delete message.clientId
              expect(engine, "clientExists").given(undefined).yielding([false])
            }})
      
            it("does not connect to the engine", function() { with(this) {
              expect(engine, "connect").exactly(0)
              server.connect(message, false, function() {})
            }})
      
            it("returns an unsuccessful response", function() { with(this) {
              server.connect(message, false, function(response) {
                assertEqual({
                    channel:    "/meta/connect",
                    successful: false,
                    error:      "402:clientId:Missing required parameter"
                  }, response)
              })
            }})
          }})
      
          describe("missing connectionType", function() { with(this) {
            before(function() { with(this) {
              delete message.connectionType
              expect(engine, "clientExists").given(clientId).yielding([true])
            }})
      
            it("does not connect to the engine", function() { with(this) {
              expect(engine, "connect").exactly(0)
              server.connect(message, false, function() {})
            }})
      
            it("returns an unsuccessful response", function() { with(this) {
              server.connect(message, false, function(response) {
                assertEqual({
                    channel:    "/meta/connect",
                    successful: false,
                    error:      "402:connectionType:Missing required parameter"
                  }, response)
              })
            }})
          }})
      
          describe("with an unknown connectionType", function() { with(this) {
            before(function() { with(this) {
              message.connectionType = "flash"
              expect(engine, "clientExists").given(clientId).yielding([true])
            }})
      
            it("does not connect to the engine", function() { with(this) {
              expect(engine, "connect").exactly(0)
              server.connect(message, false, function() {})
            }})
      
            it("returns an unsuccessful response", function() { with(this) {
              server.connect(message, false, function(response) {
                assertEqual({
                    channel:    "/meta/connect",
                    successful: false,
                    error:      "301:flash:Connection types not supported"
                  }, response)
              })
            }})
          }})
      
          describe("with an error", function() { with(this) {
            before(function() { with(this) {
              message.error = "invalid"
              expect(engine, "clientExists").given(clientId).yielding([true])
            }})
      
            it("does not connect to the engine", function() { with(this) {
              expect(engine, "connect").exactly(0)
              server.connect(message, false, function() {})
            }})
      
            it("returns an unsuccessful response", function() { with(this) {
              server.connect(message, false, function(response) {
                assertEqual({
                    channel:    "/meta/connect",
                    successful: false,
                    error:      "invalid"
                  }, response)
              })
            }})
          }})
        }})
      }})
      ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/javascript/server/disconnect_spec.js������������������������������������������������0000664�0000000�0000000�00000010071�13711043772�0022553�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������var jstest = require("jstest").Test
      
      var Engine = require("../../../src/engines/proxy"),
          Server = require("../../../src/protocol/server")
      
      jstest.describe("Server disconnect", function() { with(this) {
        before(function() { with(this) {
          this.engine = {}
          stub(Engine, "get").returns(engine)
          this.server = new Server()
        }})
      
        describe("#disconnect", function() { with(this) {
          before(function() { with(this) {
            this.clientId = "fakeclientid"
            this.message = { channel: "/meta/disconnect",
                             clientId: "fakeclientid" }
          }})
      
          describe("with valid parameters", function() { with(this) {
            before(function() { with(this) {
              expect(engine, "clientExists").given(clientId).yielding([true])
            }})
      
            it("destroys the client", function() { with(this) {
              expect(engine, "destroyClient").given(clientId)
              server.disconnect(message, false, function() {})
            }})
      
            it("returns a successful response", function() { with(this) {
              stub(engine, "destroyClient")
              server.disconnect(message, false, function(response) {
                assertEqual({
                    channel:   "/meta/disconnect",
                    successful: true,
                    clientId:   clientId
                  }, response)
              })
            }})
      
            describe("with a message id", function() { with(this) {
              before(function() { this.message.id = "foo" })
      
              it("returns the same id", function() { with(this) {
                stub(engine, "destroyClient")
                server.disconnect(message, false, function(response) {
                  assertEqual({
                    channel:    "/meta/disconnect",
                    successful: true,
                    clientId:   clientId,
                    id:         "foo"
                    }, response)
                })
              }})
            }})
          }})
      
          describe("with an unknown client", function() { with(this) {
            before(function() { with(this) {
              expect(engine, "clientExists").given(clientId).yielding([false])
            }})
      
            it("does not destroy the client", function() { with(this) {
              expect(engine, "destroyClient").exactly(0)
              server.disconnect(message, false, function() {})
            }})
      
            it("returns an unsuccessful response", function() { with(this) {
              stub(engine, "destroyClient")
              server.disconnect(message, false, function(response) {
                assertEqual({
                    channel:   "/meta/disconnect",
                    successful: false,
                    error:      "401:fakeclientid:Unknown client"
                  }, response)
              })
            }})
          }})
      
          describe("missing clientId", function() { with(this) {
            before(function() { with(this) {
              delete message.clientId
              expect(engine, "clientExists").given(undefined).yielding([false])
            }})
      
            it("does not destroy the client", function() { with(this) {
              expect(engine, "destroyClient").exactly(0)
              server.disconnect(message, false, function() {})
            }})
      
            it("returns an unsuccessful response", function() { with(this) {
              stub(engine, "destroyClient")
              server.disconnect(message, false, function(response) {
                assertEqual({
                    channel:   "/meta/disconnect",
                    successful: false,
                    error:      "402:clientId:Missing required parameter"
                  }, response)
              })
            }})
          }})
      
          describe("with an error", function() { with(this) {
            before(function() { with(this) {
              message.error = "invalid"
              expect(engine, "clientExists").given(clientId).yielding([true])
            }})
      
            it("does not destroy the client", function() { with(this) {
              expect(engine, "destroyClient").exactly(0)
              server.disconnect(message, false, function() {})
            }})
      
            it("returns an unsuccessful response", function() { with(this) {
              stub(engine, "destroyClient")
              server.disconnect(message, false, function(response) {
                assertEqual({
                    channel:   "/meta/disconnect",
                    successful: false,
                    error:      "invalid"
                  }, response)
              })
            }})
          }})
        }})
      }})
      �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/javascript/server/extensions_spec.js������������������������������������������������0000664�0000000�0000000�00000006617�13711043772�0022634�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������var jstest = require("jstest").Test
      
      var Engine = require("../../../src/engines/proxy"),
          Server = require("../../../src/protocol/server")
      
      jstest.describe("Server extensions", function() { with(this) {
          before(function() { with(this) {
          this.engine = {}
          stub(Engine, "get").returns(engine)
          this.server = new Server()
        }})
      
        describe("with an incoming extension installed", function() { with(this) {
          before(function() { with(this) {
            var extension = {
              incoming: function(message, callback) {
                message.ext = { auth: "password" }
                callback(message)
              }
            }
            server.addExtension(extension)
            this.message = { channel: "/foo", data: "hello" }
          }})
      
          it("passes incoming messages through the extension", function() { with(this) {
            expect(engine, "publish").given({ channel: "/foo", data: "hello", ext: { auth: "password" }})
            server.process(message, false, function() {})
          }})
      
          it("does not pass outgoing messages through the extension", function() { with(this) {
            stub(server, "handshake").yields([message])
            stub(engine, "publish")
            var response = null
            server.process({ channel: "/meta/handshake" }, false, function(r) { response = r })
            assertEqual( [{ channel: "/foo", data: "hello" }], response )
          }})
        }})
      
        describe("with subscription auth installed", function() { with(this) {
          before(function() { with(this) {
            var extension = {
              incoming: function(message, callback) {
                if (message.channel === "/meta/subscribe" && !message.auth) {
                  message.error = "Invalid auth"
                }
                callback(message)
              }
            }
            server.addExtension(extension)
          }})
      
          it("does not subscribe using the intended channel", function() { with(this) {
            var message = {
              channel: "/meta/subscribe",
              clientId: "fakeclientid",
              subscription: "/foo"
            }
            stub(engine, "clientExists").yields([true])
            expect(engine, "subscribe").exactly(0)
            server.process(message, false, function() {})
          }})
      
          it("does not subscribe using an extended channel", function() { with(this) {
            var message = {
              channel: "/meta/subscribe/x",
              clientId: "fakeclientid",
              subscription: "/foo"
            }
            stub(engine, "clientExists").yields([true])
            expect(engine, "subscribe").exactly(0)
            server.process(message, false, function() {})
          }})
        }})
      
        describe("with an outgoing extension installed", function() { with(this) {
          before(function() { with(this) {
            var extension = {
              outgoing: function(message, callback) {
                message.ext = { auth: "password" }
                callback(message)
              }
            }
            server.addExtension(extension)
            this.message = { channel: "/foo", data: "hello" }
          }})
      
          it("does not pass incoming messages through the extension", function() { with(this) {
            expect(engine, "publish").given({ channel: "/foo", data: "hello" })
            server.process(message, false, function() {})
          }})
      
          it("passes outgoing messages through the extension", function() { with(this) {
            stub(server, "handshake").yields([message])
            stub(engine, "publish")
            var response = null
            server.process({ channel: "/meta/handshake" }, false, function(r) { response = r })
            assertEqual( [{ channel: "/foo", data: "hello", ext: { auth: "password" }}], response )
          }})
        }})
      }})
      �����������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/javascript/server/handshake_spec.js�������������������������������������������������0000664�0000000�0000000�00000012163�13711043772�0022354�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������var jstest = require("jstest").Test
      
      var Engine = require("../../../src/engines/proxy"),
          Server = require("../../../src/protocol/server")
      
      jstest.describe("Server handshake", function() { with(this) {
        before(function() { with(this) {
          this.engine = {}
          stub(Engine, "get").returns(engine)
          this.server = new Server()
      
          this.connectionTypes = ["long-polling", "cross-origin-long-polling",
                                  "callback-polling","websocket",
                                  "eventsource","in-process"]
        }})
      
        describe("#handshake", function() { with(this) {
          before(function() { with(this) {
            this.message = { channel: "/meta/handshake",
                             version: "1.0",
                             supportedConnectionTypes: ["long-polling"] }
          }})
      
          describe("with valid parameters", function() { with(this) {
            it("creates a client", function() { with(this) {
              expect(engine, "createClient")
              server.handshake(message, false, function() {})
            }})
      
            it("returns a successful response", function() { with(this) {
              stub(engine, "createClient").yields(["clientid"])
              server.handshake(message, false, function(response) {
                assertEqual({
                    channel:    "/meta/handshake",
                    successful: true,
                    version:    "1.0",
                    supportedConnectionTypes: connectionTypes,
                    clientId:   "clientid"
                  }, response)
              })
            }})
      
            describe("with a message id", function() { with(this) {
              before(function() { this.message.id = "foo" })
      
              it("returns the same id", function() { with(this) {
                stub(engine, "createClient").yields(["clientid"])
                server.handshake(message, false, function(response) {
                  assertEqual({
                      channel:    "/meta/handshake",
                      successful: true,
                      version:    "1.0",
                      supportedConnectionTypes: connectionTypes,
                      clientId:   "clientid",
                      id:         "foo"
                    }, response)
                })
              }})
            }})
          }})
      
          describe("missing version", function() { with(this) {
            before(function() { delete this.message.version })
      
            it("does not create a client", function() { with(this) {
              expect(engine, "createClient").exactly(0)
              server.handshake(message, false, function() {})
            }})
      
            it("returns an unsuccessful response", function() { with(this) {
              server.handshake(message, false, function(response) {
                assertEqual({
                    channel:    "/meta/handshake",
                    successful: false,
                    error:      "402:version:Missing required parameter",
                    version:    "1.0",
                    supportedConnectionTypes: connectionTypes
                  }, response)
              })
            }})
          }})
      
          describe("missing supportedConnectionTypes", function() { with(this) {
            before(function() { delete this.message.supportedConnectionTypes })
      
            it("does not create a client", function() { with(this) {
              expect(engine, "createClient").exactly(0)
              server.handshake(message, false, function() {})
            }})
      
            it("returns an unsuccessful response", function() { with(this) {
              server.handshake(message, false, function(response) {
                assertEqual({
                    channel:    "/meta/handshake",
                    successful: false,
                    error:      "402:supportedConnectionTypes:Missing required parameter",
                    version:    "1.0",
                    supportedConnectionTypes: connectionTypes
                  }, response)
              })
            }})
          }})
      
          describe("with no matching supportedConnectionTypes", function() { with(this) {
            before(function() { with(this) {
              message.supportedConnectionTypes = ["iframe", "flash"]
            }})
      
            it("does not create a client", function() { with(this) {
              expect(engine, "createClient").exactly(0)
              server.handshake(message, false, function() {})
            }})
      
            it("returns an unsuccessful response", function() { with(this) {
              server.handshake(message, false, function(response) {
                assertEqual({
                    channel:    "/meta/handshake",
                    successful: false,
                    error:      "301:iframe,flash:Connection types not supported",
                    version:    "1.0",
                    supportedConnectionTypes: connectionTypes
                  }, response)
              })
            }})
          }})
      
          describe("with an error", function() { with(this) {
            before(function() { with(this) {
              message.error = "invalid"
            }})
      
            it("does not create a client", function() { with(this) {
              expect(engine, "createClient").exactly(0)
              server.handshake(message, false, function() {})
            }})
      
            it("returns an unsuccessful response", function() { with(this) {
              server.handshake(message, false, function(response) {
                assertEqual({
                    channel:    "/meta/handshake",
                    successful: false,
                    error:      "invalid",
                    version:    "1.0",
                    supportedConnectionTypes: connectionTypes
                  }, response)
              })
            }})
          }})
        }})
      }})
      �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/javascript/server/integration_spec.js�����������������������������������������������0000664�0000000�0000000�00000011162�13711043772�0022747�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������var fs    = require("fs"),
          http  = require("http"),
          https = require("https"),
          cert  = fs.readFileSync(__dirname + "/../../../examples/server.crt"),
          key   = fs.readFileSync(__dirname + "/../../../examples/server.key")
      
      var jstest = require("jstest").Test
      
      var NodeAdapter        = require("../../../src/adapters/node_adapter"),
          Client             = require("../../../src/protocol/client"),
          WebSocketTransport = require("../../../src/transport/web_socket")
      
      var IntegrationSteps = jstest.asyncSteps({
        server: function(ssl, callback) {
          this._adapter = new NodeAdapter({ mount: "/bayeux", timeout: 2 })
      
          this._adapter.addExtension({
            incoming: function(message, callback) {
              if (message.data) message.data.tagged = true
              callback(message)
            },
      
            outgoing: function(message, request, callback) {
              if (message.data) message.data.url = request.url;
              callback(message)
            }
          })
      
          this._secure = ssl
      
          this._http = ssl
                     ? https.createServer({ cert: cert, key: key })
                     : http.createServer()
      
          this._adapter.attach(this._http)
          var self = this
      
          this._http.listen(0, function() {
            self._port = self._http.address().port
            callback()
          })
        },
      
        stop: function(callback) {
          this._http.close()
          callback()
        },
      
        client: function(name, channels, callback) {
          var scheme          = this._secure ? "https" : "http"
          this._clients       = this._clients || {}
          this._inboxes       = this._inboxes || {}
          this._clients[name] = new Client(scheme + "://localhost:" + this._port  + "/bayeux", { ca: cert })
          this._inboxes[name] = {}
      
          var n = channels.length
          if (n === 0) return this._clients[name].connect(callback)
      
          for (var i = 0; i < n; i++)
            (function(channel) {
              var subscription = this._clients[name].subscribe(channel, function(message) {
                this._inboxes[name][channel] = this._inboxes[name][channel] || []
                this._inboxes[name][channel].push(message)
              }, this)
              subscription.callback(function() {
                n -= 1
                if (n === 0) callback()
              })
            }).call(this, channels[i]);
        },
      
        publish: function(name, channel, message, callback) {
          this._clients[name].publish(channel, message)
          setTimeout(callback, 100)
        },
      
        check_inbox: function(name, channel, messages, callback) {
          var inbox = this._inboxes[name][channel] || []
          this.assertEqual(messages, inbox)
          callback()
        }
      })
      
      jstest.describe("Server integration", function() { with(this) {
        include(IntegrationSteps)
      
        sharedExamplesFor("message bus", function() { with(this) {
          before(function() { with(this) {
            server(serverOptions.ssl)
            client("alice", [])
            client("bob", ["/foo"])
          }})
      
          after(function() { this.stop() })
      
          it("delivers a message between clients", function() { with(this) {
            publish("alice", "/foo", { hello: "world", extra: null })
            check_inbox("bob", "/foo", [{ hello: "world", extra: null, tagged: true, url: "/bayeux" }])
          }})
      
          it("does not deliver messages for unsubscribed channels", function() { with(this) {
            publish("alice", "/bar", { hello: "world" })
            check_inbox("bob", "/foo", [])
          }})
      
          it("delivers multiple messages", function() { with(this) {
            publish("alice", "/foo", { hello: "world" })
            publish("alice", "/foo", { hello: "world" })
            check_inbox("bob", "/foo", [{ hello: "world", tagged: true, url: "/bayeux" }, { hello: "world", tagged: true, url: "/bayeux" }])
          }})
      
          it("delivers multibyte strings", function() { with(this) {
            publish("alice", "/foo", { hello: "Apple = " })
            check_inbox("bob", "/foo", [{ hello: "Apple = ", tagged: true, url: "/bayeux" }])
          }})
        }})
      
        sharedExamplesFor("network transports", function() { with(this) {
          describe("with HTTP transport", function() { with(this) {
            before(function() { with(this) {
              stub(WebSocketTransport, "isUsable").yields([false])
            }})
      
            itShouldBehaveLike("message bus")
          }})
      
          describe("with WebSocket transport", function() { with(this) {
            before(function() { with(this) {
              stub(WebSocketTransport, "isUsable").yields([true])
            }})
      
            itShouldBehaveLike("message bus")
          }})
        }})
      
        describe("with HTTP server", function() { with(this) {
          before(function() { with(this) {
            this.serverOptions = { ssl: false }
          }})
      
          itShouldBehaveLike("network transports")
        }})
      
        describe("with HTTPS server", function() { with(this) {
          before(function() { with(this) {
            this.serverOptions = { ssl: true }
          }})
      
          itShouldBehaveLike("network transports")
        }})
      }})
      ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/javascript/server/publish_spec.js���������������������������������������������������0000664�0000000�0000000�00000006610�13711043772�0022074�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������var jstest = require("jstest").Test
      
      var Engine = require("../../../src/engines/proxy"),
          Server = require("../../../src/protocol/server")
      
      jstest.describe("Server publish", function() { with(this) {
        before(function() { with(this) {
          this.engine = {}
          stub(Engine, "get").returns(engine)
          this.server = new Server()
      
          this.message = { channel: "/some/channel", data: "publish" }
        }})
      
        describe("publishing a message", function() { with(this) {
          it("tells the engine to publish the message", function() { with(this) {
            expect(engine, "publish").given(message)
            server.process(message, false, function() {})
          }})
      
          it("returns a successful response", function() { with(this) {
            stub(engine, "publish")
            server.process(message, false, function(response) {
              assertEqual([
                { channel:    "/some/channel",
                  successful: true
                }
              ], response)
            })
          }})
      
          describe("with an invalid channel", function() { with(this) {
            before(function() { with(this) {
              message.channel = "channel"
            }})
      
            it("does not tell the engine to publish the message", function() { with(this) {
              expect(engine, "publish").exactly(0)
              server.process(message, false, function() {})
            }})
      
            it("returns an unsuccessful response", function() { with(this) {
              stub(engine, "publish")
              server.process(message, false, function(response) {
                assertEqual([
                  { channel:    "channel",
                    successful: false,
                    error:      "405:channel:Invalid channel"
                  }
                ], response)
              })
            }})
          }})
      
          describe("with no data", function() { with(this) {
            before(function() { with(this) {
              delete message.data
            }})
      
            it("does not tell the engine to publish the message", function() { with(this) {
              expect(engine, "publish").exactly(0)
              server.process(message, false, function() {})
            }})
      
            it("returns an unsuccessful response", function() { with(this) {
              stub(engine, "publish")
              server.process(message, false, function(response) {
                assertEqual([
                  { channel:    "/some/channel",
                    successful: false,
                    error:      "402:data:Missing required parameter"
                  }
                ], response)
              })
            }})
          }})
      
          describe("with an error", function() { with(this) {
            before(function() { with(this) {
              message.error = "invalid"
            }})
      
            it("does not tell the engine to publish the message", function() { with(this) {
              expect(engine, "publish").exactly(0)
              server.process(message, false, function() {})
            }})
      
            it("returns an unsuccessful response", function() { with(this) {
              stub(engine, "publish")
              server.process(message, false, function(response) {
                assertEqual([
                  { channel:    "/some/channel",
                    successful: false,
                    error:      "invalid"
                  }
                ], response)
              })
            }})
          }})
      
          describe("to an invalid channel", function() { with(this) {
            before(function() { with(this) {
              message.channel = "/invalid/*"
            }})
      
            it("does not tell the engine to publish the message", function() { with(this) {
              expect(engine, "publish").exactly(0)
              server.process(message, false, function() {})
            }})
          }})
        }})
      }})
      ������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/javascript/server/subscribe_spec.js�������������������������������������������������0000664�0000000�0000000�00000021123�13711043772�0022403�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������var jstest = require("jstest").Test
      
      var Engine = require("../../../src/engines/proxy"),
          Server = require("../../../src/protocol/server")
      
      jstest.describe("Server subscribe", function() { with(this) {
        before(function() { with(this) {
          this.engine = {}
          stub(Engine, "get").returns(engine)
          this.server = new Server()
        }})
      
        describe("#subscribe", function() { with(this) {
          before(function() { with(this) {
            this.clientId = "fakeclientid"
            this.message = { channel: "/meta/subscribe",
                             clientId: "fakeclientid",
                             subscription: "/foo" }
          }})
      
          describe("with valid parameters", function() { with(this) {
            before(function() { with(this) {
              expect(engine, "clientExists").given(clientId).yielding([true])
            }})
      
            it("subscribes the client to the channel", function() { with(this) {
              expect(engine, "subscribe").given(clientId, "/foo")
              server.subscribe(message, false, function() {})
            }})
      
            it("returns a successful response", function() { with(this) {
              stub(engine, "subscribe")
              server.subscribe(message, false, function(response) {
                assertEqual({
                    channel:      "/meta/subscribe",
                    successful:   true,
                    clientId:     clientId,
                    subscription: "/foo"
                  }, response)
              })
            }})
      
            describe("with a list of subscriptions", function() { with(this) {
              before(function() { with(this) {
                message.subscription = ["/foo", "/bar"]
              }})
      
              it("creates multiple subscriptions", function() { with(this) {
                expect(engine, "subscribe").given(clientId, "/foo")
                expect(engine, "subscribe").given(clientId, "/bar")
                server.subscribe(message, false, function() {})
              }})
      
              it("returns a successful response", function() { with(this) {
                stub(engine, "subscribe")
                server.subscribe(message, false, function(response) {
                  assertEqual({
                      channel:      "/meta/subscribe",
                      successful:   true,
                      clientId:     clientId,
                      subscription: ["/foo", "/bar"]
                    }, response)
                })
              }})
            }})
      
            describe("with a subscription pattern", function() { with(this) {
              before(function() { with(this) {
                message.subscription = "/foo/**"
              }})
      
              it("subscribes the client to the channel pattern", function() { with(this) {
                expect(engine, "subscribe").given(clientId, "/foo/**")
                server.subscribe(message, false, function() {})
              }})
      
              it("returns a successful response", function() { with(this) {
                stub(engine, "subscribe")
                server.subscribe(message, false, function(response) {
                  assertEqual({
                      channel:      "/meta/subscribe",
                      successful:   true,
                      clientId:     clientId,
                      subscription: "/foo/**"
                    }, response)
                })
              }})
            }})
          }})
      
          describe("with an unknown client", function() { with(this) {
            before(function() { with(this) {
              expect(engine, "clientExists").given(clientId).yielding([false])
            }})
      
            it("does not subscribe the client to the channel", function() { with(this) {
              expect(engine, "subscribe").exactly(0)
              server.subscribe(message, false, function() {})
            }})
      
            it("returns an unsuccessful response", function() { with(this) {
              server.subscribe(message, false, function(response) {
                assertEqual({
                    channel:      "/meta/subscribe",
                    successful:   false,
                    error:        "401:fakeclientid:Unknown client",
                    clientId:     clientId,
                    subscription: "/foo"
                  }, response)
              })
            }})
          }})
      
          describe("missing clientId", function() { with(this) {
            before(function() { with(this) {
              delete message.clientId
              expect(engine, "clientExists").given(undefined).yielding([false])
            }})
      
            it("does not subscribe the client to the channel", function() { with(this) {
              expect(engine, "subscribe").exactly(0)
              server.subscribe(message, false, function() {})
            }})
      
            it("returns an unsuccessful response", function() { with(this) {
              server.subscribe(message, false, function(response) {
                assertEqual({
                    channel:      "/meta/subscribe",
                    successful:   false,
                    error:        "402:clientId:Missing required parameter",
                    subscription: "/foo"
                  }, response)
              })
            }})
          }})
      
          describe("missing subscription", function() { with(this) {
            before(function() { with(this) {
              delete message.subscription
              expect(engine, "clientExists").given(clientId).yielding([true])
            }})
      
            it("does not subscribe the client to the channel", function() { with(this) {
              expect(engine, "subscribe").exactly(0)
              server.subscribe(message, false, function() {})
            }})
      
            it("returns an unsuccessful response", function() { with(this) {
              server.subscribe(message, false, function(response) {
                assertEqual({
                    channel:      "/meta/subscribe",
                    successful:   false,
                    error:        "402:subscription:Missing required parameter",
                    clientId:     clientId,
                    subscription: []
                  }, response)
              })
            }})
          }})
      
          describe("with an invalid channel", function() { with(this) {
            before(function() { with(this) {
              message.subscription = "foo"
              expect(engine, "clientExists").given(clientId).yielding([true])
            }})
      
            it("does not subscribe the client to the channel", function() { with(this) {
              expect(engine, "subscribe").exactly(0)
              server.subscribe(message, false, function() {})
            }})
      
            it("returns an unsuccessful response", function() { with(this) {
              server.subscribe(message, false, function(response) {
                assertEqual({
                    channel:      "/meta/subscribe",
                    successful:   false,
                    error:        "405:foo:Invalid channel",
                    clientId:     clientId,
                    subscription: "foo"
                  }, response)
              })
            }})
          }})
      
          describe("with a /meta/* channel", function() { with(this) {
            before(function() { with(this) {
              message.subscription = "/meta/foo"
              expect(engine, "clientExists").given(clientId).yielding([true])
            }})
      
            it("does not subscribe the client to the channel", function() { with(this) {
              expect(engine, "subscribe").exactly(0)
              server.subscribe(message, false, function() {})
            }})
      
            it("returns an unsuccessful response", function() { with(this) {
              server.subscribe(message, false, function(response) {
                assertEqual({
                    channel:      "/meta/subscribe",
                    successful:   false,
                    error:        "403:/meta/foo:Forbidden channel",
                    clientId:     clientId,
                    subscription: "/meta/foo"
                  }, response)
              })
            }})
      
            it("subscribes local clients to the channel", function() { with(this) {
              expect(engine, "subscribe").given(clientId, "/meta/foo")
              server.subscribe(message, true, function() {})
            }})
      
            it("returns a successful response for local clients", function() { with(this) {
              stub(engine, "subscribe")
              server.subscribe(message, true, function(response) {
                assertEqual({
                    channel:      "/meta/subscribe",
                    successful:   true,
                    clientId:     clientId,
                    subscription: "/meta/foo"
                  }, response)
              })
            }})
          }})
      
          describe("with an error", function() { with(this) {
            before(function() { with(this) {
              message.error = "invalid"
              expect(engine, "clientExists").given(clientId).yielding([true])
            }})
      
            it("does not subscribe the client to the channel", function() { with(this) {
              expect(engine, "subscribe").exactly(0)
              server.subscribe(message, false, function() {})
            }})
      
            it("returns an unsuccessful response", function(resume) { with(this) {
              server.subscribe(message, false, function(response) {
                resume(function() {
                  assertEqual({
                      channel:      "/meta/subscribe",
                      successful:   false,
                      error:        "invalid",
                      clientId:     clientId,
                      subscription: "/foo"
                    }, response)
                })
              })
            }})
          }})
        }})
      }})
      ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/javascript/server/unsubscribe_spec.js�����������������������������������������������0000664�0000000�0000000�00000021231�13711043772�0022746�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������var jstest = require("jstest").Test
      
      var Engine = require("../../../src/engines/proxy"),
          Server = require("../../../src/protocol/server")
      
      jstest.describe("Server unsubscribe", function() { with(this) {
        before(function() { with(this) {
          this.engine = {}
          stub(Engine, "get").returns(engine)
          this.server = new Server()
        }})
      
        describe("#unsubscribe", function() { with(this) {
          before(function() { with(this) {
            this.clientId = "fakeclientid"
            this.message = { channel: "/meta/unsubscribe",
                             clientId: "fakeclientid",
                             subscription: "/foo" }
          }})
      
          describe("with valid parameters", function() { with(this) {
            before(function() { with(this) {
              expect(engine, "clientExists").given(clientId).yielding([true])
            }})
      
            it("unsubscribes the client from the channel", function() { with(this) {
              expect(engine, "unsubscribe").given(clientId, "/foo")
              server.unsubscribe(message, false, function() {})
            }})
      
            it("returns a successful response", function() { with(this) {
              stub(engine, "unsubscribe")
              server.unsubscribe(message, false, function(response) {
                assertEqual({
                    channel:      "/meta/unsubscribe",
                    successful:   true,
                    clientId:     clientId,
                    subscription: "/foo"
                  }, response)
              })
            }})
      
            describe("with a list of subscriptions", function() { with(this) {
              before(function() { with(this) {
                message.subscription = ["/foo", "/bar"]
              }})
      
              it("destroys multiple subscriptions", function() { with(this) {
                expect(engine, "unsubscribe").given(clientId, "/foo")
                expect(engine, "unsubscribe").given(clientId, "/bar")
                server.unsubscribe(message, false, function() {})
              }})
      
              it("returns a successful response", function() { with(this) {
                stub(engine, "unsubscribe")
                server.unsubscribe(message, false, function(response) {
                  assertEqual({
                      channel:      "/meta/unsubscribe",
                      successful:   true,
                      clientId:     clientId,
                      subscription: ["/foo", "/bar"]
                    }, response)
                })
              }})
            }})
      
            describe("with a subscription pattern", function() { with(this) {
              before(function() { with(this) {
                message.subscription = "/foo/**"
              }})
      
              it("destroys the subscription to the channel pattern", function() { with(this) {
                expect(engine, "unsubscribe").given(clientId, "/foo/**")
                server.unsubscribe(message, false, function() {})
              }})
      
              it("returns a successful response", function() { with(this) {
                stub(engine, "unsubscribe")
                server.unsubscribe(message, false, function(response) {
                  assertEqual({
                      channel:      "/meta/unsubscribe",
                      successful:   true,
                      clientId:     clientId,
                      subscription: "/foo/**"
                    }, response)
                })
              }})
            }})
          }})
      
          describe("with an unknown client", function() { with(this) {
            before(function() { with(this) {
              expect(engine, "clientExists").given(clientId).yielding([false])
            }})
      
            it("does not unsubscribe the client from the channel", function() { with(this) {
              expect(engine, "unsubscribe").exactly(0)
              server.unsubscribe(message, false, function() {})
            }})
      
            it("returns an unsuccessful response", function() { with(this) {
              server.unsubscribe(message, false, function(response) {
                assertEqual({
                    channel:      "/meta/unsubscribe",
                    successful:   false,
                    error:        "401:fakeclientid:Unknown client",
                    clientId:     clientId,
                    subscription: "/foo"
                  }, response)
              })
            }})
          }})
      
          describe("missing clientId", function() { with(this) {
            before(function() { with(this) {
              delete message.clientId
              expect(engine, "clientExists").given(undefined).yielding([false])
            }})
      
            it("does not unsubscribe the client from the channel", function() { with(this) {
              expect(engine, "unsubscribe").exactly(0)
              server.unsubscribe(message, false, function() {})
            }})
      
            it("returns an unsuccessful response", function() { with(this) {
              server.unsubscribe(message, false, function(response) {
                assertEqual({
                    channel:      "/meta/unsubscribe",
                    successful:   false,
                    error:        "402:clientId:Missing required parameter",
                    subscription: "/foo"
                  }, response)
              })
            }})
          }})
      
          describe("missing subscription", function() { with(this) {
            before(function() { with(this) {
              delete message.subscription
              expect(engine, "clientExists").given(clientId).yielding([true])
            }})
      
            it("does not unsubscribe the client from the channel", function() { with(this) {
              expect(engine, "unsubscribe").exactly(0)
              server.unsubscribe(message, false, function() {})
            }})
      
            it("returns an unsuccessful response", function() { with(this) {
              server.unsubscribe(message, false, function(response) {
                assertEqual({
                    channel:      "/meta/unsubscribe",
                    successful:   false,
                    error:        "402:subscription:Missing required parameter",
                    clientId:     clientId,
                    subscription: []
                  }, response)
              })
            }})
          }})
      
          describe("with an invalid channel", function() { with(this) {
            before(function() { with(this) {
              message.subscription = "foo"
              expect(engine, "clientExists").given(clientId).yielding([true])
            }})
      
            it("does not unsubscribe the client from the channel", function() { with(this) {
              expect(engine, "unsubscribe").exactly(0)
              server.unsubscribe(message, false, function() {})
            }})
      
            it("returns an unsuccessful response", function() { with(this) {
              server.unsubscribe(message, false, function(response) {
                assertEqual({
                    channel:      "/meta/unsubscribe",
                    successful:   false,
                    error:        "405:foo:Invalid channel",
                    clientId:     clientId,
                    subscription: "foo"
                  }, response)
              })
            }})
          }})
      
          describe("with a /meta/* channel", function() { with(this) {
            before(function() { with(this) {
              message.subscription = "/meta/foo"
              expect(engine, "clientExists").given(clientId).yielding([true])
            }})
      
            it("does not unsubscribe the client from the channel", function() { with(this) {
              expect(engine, "unsubscribe").exactly(0)
              server.unsubscribe(message, false, function() {})
            }})
      
            it("returns an unsuccessful response", function() { with(this) {
              server.unsubscribe(message, false, function(response) {
                assertEqual({
                    channel:      "/meta/unsubscribe",
                    successful:   false,
                    error:        "403:/meta/foo:Forbidden channel",
                    clientId:     clientId,
                    subscription: "/meta/foo"
                  }, response)
              })
            }})
      
            it("unsubscribes local clients from the channel", function() { with(this) {
              expect(engine, "unsubscribe").given(clientId, "/meta/foo")
              server.unsubscribe(message, true, function() {})
            }})
      
            it("returns a successful response for local clients", function() { with(this) {
              stub(engine, "unsubscribe")
              server.unsubscribe(message, true, function(response) {
                assertEqual({
                    channel:      "/meta/unsubscribe",
                    successful:   true,
                    clientId:     clientId,
                    subscription: "/meta/foo"
                  }, response)
              })
            }})
          }})
      
          describe("with an error", function() { with(this) {
            before(function() { with(this) {
              message.error = "invalid"
              expect(engine, "clientExists").given(clientId).yielding([true])
            }})
      
            it("does not unsubscribe the client from the channel", function() { with(this) {
              expect(engine, "unsubscribe").exactly(0)
              server.unsubscribe(message, false, function() {})
            }})
      
            it("returns an unsuccessful response", function() { with(this) {
              server.unsubscribe(message, false, function(response) {
                assertEqual({
                    channel:      "/meta/unsubscribe",
                    successful:   false,
                    error:        "invalid",
                    clientId:     clientId,
                    subscription: "/foo"
                  }, response)
              })
            }})
          }})
        }})
      }})
      �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/javascript/server_spec.js�����������������������������������������������������������0000664�0000000�0000000�00000010127�13711043772�0020424�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������var jstest = require("jstest").Test
      
      var Engine = require("../../src/engines/proxy"),
          Server = require("../../src/protocol/server")
      
      jstest.describe("Server", function() { with(this) {
        before(function() { with(this) {
          this.engine = {}
          stub(Engine, "get").returns(engine)
          this.server = new Server()
        }})
      
        describe("#process", function() { with(this) {
          before(function() { with(this) {
            this.handshake   = { channel: "/meta/handshake",   data: "handshake" }
            this.connect     = { channel: "/meta/connect",     data: "connect" }
            this.disconnect  = { channel: "/meta/disconnect",  data: "disconnect" }
            this.subscribe   = { channel: "/meta/subscribe",   data: "subscribe" }
            this.unsubscribe = { channel: "/meta/unsubscribe", data: "unsubscribe" }
            this.publish     = { channel: "/some/channel",     data: "publish" }
      
            stub(engine, "interval", 0)
            stub(engine, "timeout", 60)
          }})
      
          it("returns an empty response for no messages", function() { with(this) {
            var response = null
            server.process([], false, function(r) { response = r })
            assertEqual( [], response )
          }})
      
          it("ignores invalid messages", function() { with(this) {
            var response = null
            server.process([{}, { channel: "invalid" }], false, function(r) { response = r })
            assertEqual([
              { successful: false,
                error:      "402:data:Missing required parameter"
              },
              { channel:    "invalid",
                successful: false,
                error:      "402:data:Missing required parameter"
              }
            ], response)
          }})
      
          it("rejects unknown meta channels", function() { with(this) {
            var response = null
            server.process([{ channel: "/meta/p" }], false, function(r) { response = r })
            assertEqual([
              { channel:    "/meta/p",
                successful: false,
                error:      "403:/meta/p:Forbidden channel"
              }
            ], response)
          }})
      
          it("routes single messages to appropriate handlers", function() { with(this) {
            expect(server, "handshake").given(handshake, false).yielding([{}])
            server.process(handshake, false, function() {})
          }})
      
          it("routes a list of messages to appropriate handlers", function() { with(this) {
            expect(server, "handshake").given(handshake, false).yielding([{}])
            expect(server, "connect").given(connect, false).yielding([{}])
            expect(server, "disconnect").given(disconnect, false).yielding([{}])
            expect(server, "subscribe").given(subscribe, false).yielding([{}])
            expect(server, "unsubscribe").given(unsubscribe, false).yielding([{}])
      
            expect(engine, "publish").given(handshake).exactly(0)
            expect(engine, "publish").given(connect).exactly(0)
            expect(engine, "publish").given(disconnect).exactly(0)
            expect(engine, "publish").given(subscribe).exactly(0)
            expect(engine, "publish").given(unsubscribe).exactly(0)
      
            expect(engine, "publish").given(publish)
      
            server.process([handshake, connect, disconnect, subscribe, unsubscribe, publish], false, function() {})
          }})
      
          describe("handshaking", function() { with(this) {
            before(function() { with(this) {
              expect(server, "handshake").given(handshake, false).yielding([{ channel: "/meta/handshake", successful: true }])
            }})
      
            it("returns the handshake response with advice", function() { with(this) {
              server.process(handshake, false, function(response) {
                assertEqual([
                    { channel: "/meta/handshake",
                      successful: true,
                      advice: { reconnect: "retry", interval: 0, timeout: 60000 }
                    }
                  ], response)
              })
            }})
          }})
      
          describe("connecting for messages", function() { with(this) {
            before(function() { with(this) {
              this.messages = [{ channel: "/a" }, { channel: "/b" }]
              expect(server, "connect").given(connect, false).yielding([messages])
            }})
      
            it("returns the new messages", function() { with(this) {
              server.process(connect, false, function(response) {
                assertEqual( messages, response )
              })
            }})
          }})
        }})
      }})
      �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/javascript/transport_spec.js��������������������������������������������������������0000664�0000000�0000000�00000012677�13711043772�0021166�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������var jstest = require("jstest").Test
      
      var Transport = require("../../src/transport"),
          Class     = require("../../src/util/class"),
          URI       = require("../../src/util/uri"),
          array     = require("../../src/util/array")
      
      jstest.describe("Transport", function() { with(this) {
        before(function() { with(this) {
          this.dispatcher = {
            endpoint:       URI.parse("http://example.com/"),
            endpoints:      {},
            maxRequestSize: 2048,
            headers:        {},
            proxy:          {},
            transports:     {},
            wsExtensions:   []
          }
          dispatcher.endpointFor = function() { return dispatcher.endpoint }
      
          this.websocket       = "websocket"
          this.SocketTransport = array.filter(Transport._transports, function(t) { return t[0] === websocket })[0][1]
          this.longPolling     = "long-polling"
          this.HttpTransport   = array.filter(Transport._transports, function(t) { return t[0] === longPolling })[0][1]
        }})
      
        describe("get", function() { with(this) {
          before(function() { with(this) {
            stub(HttpTransport, "isUsable").yields([false])
            stub(SocketTransport, "isUsable").yields([false])
          }})
      
          describe("when no transport is usable", function() { with(this) {
            it("raises an exception", function() { with(this) {
              assertThrows(Error, function() { Transport.get(dispatcher, [longPolling, websocket], []) })
            }})
          }})
      
          describe("when a less preferred transport is usable", function() { with(this) {
            before(function() { with(this) {
              stub(HttpTransport, "isUsable").yields([true])
            }})
      
            it("returns a transport of the usable type", function() { with(this) {
              Transport.get(dispatcher, [longPolling, websocket], [], function(transport) {
                assertKindOf( HttpTransport, transport )
              })
            }})
      
            it("raises an exception if the usable type is not requested", function() { with(this) {
              assertThrows(Error, function() { Transport.get(dispatcher, [websocket], []) })
            }})
      
            it("allows the usable type to be specifically selected", function() { with(this) {
              Transport.get(dispatcher, [longPolling], [], function(transport) {
                assertKindOf( HttpTransport, transport )
              })
            }})
          }})
      
          describe("when all transports are usable", function() { with(this) {
            before(function() { with(this) {
              stub(SocketTransport, "isUsable").yields([true])
              stub(HttpTransport, "isUsable").yields([true])
            }})
      
            it("returns the most preferred type", function() { with(this) {
              Transport.get(dispatcher, [longPolling, websocket], [], function(transport) {
                assertKindOf( SocketTransport, transport )
              })
            }})
      
            it("does not return disabled types", function() { with(this) {
              Transport.get(dispatcher, [longPolling, websocket], [websocket], function(transport) {
                assertKindOf( HttpTransport, transport )
              })
            }})
      
            it("allows types to be specifically selected", function() { with(this) {
              Transport.get(dispatcher, [websocket], [], function(transport) {
                assertKindOf( SocketTransport, transport )
              })
              Transport.get(dispatcher, [longPolling], [], function(transport) {
                assertKindOf( HttpTransport, transport )
              })
            }})
          }})
        }})
      
        describe("sendMessage", function() { with(this) {
          include(jstest.FakeClock)
          before(function() { this.clock.stub() })
          after(function() { this.clock.reset() })
      
          define("sendMessage", function(message) {
            this.transport.sendMessage(message)
          })
      
          describe("for batching transports", function() { with(this) {
            before(function() { with(this) {
              this.Transport = Class(Transport, { batching: true })
              this.transport = new Transport(dispatcher, dispatcher.endpoint)
            }})
      
            it("does not make an immediate request", function() { with(this) {
              expect(transport, "request").exactly(0)
              sendMessage({ batch: "me" })
            }})
      
            it("queues the message to be sent after a timeout", function() { with(this) {
              expect(transport, "request").given([{ batch: "me" }]).exactly(1)
              sendMessage({ batch: "me" })
              clock.tick(10)
            }})
      
            it("allows multiple messages to be batched together", function() { with(this) {
              expect(transport, "request").given([{ id: 1 }, { id: 2 }]).exactly(1)
              sendMessage({ id: 1 })
              sendMessage({ id: 2 })
              clock.tick(10)
            }})
      
            it("adds advice to connect messages sent with others", function() { with(this) {
              expect(transport, "request").given([{ channel: "/meta/connect", advice: { timeout: 0 }}, {}]).exactly(1)
              sendMessage({ channel: "/meta/connect" })
              sendMessage({})
              clock.tick(10)
            }})
      
            it("adds no advice to connect messages sent alone", function() { with(this) {
              expect(transport, "request").given([{ channel: "/meta/connect" }]).exactly(1)
              sendMessage({ channel: "/meta/connect" })
              clock.tick(10)
            }})
          }})
      
          describe("for non-batching transports", function() { with(this) {
            before(function() { with(this) {
              this.Transport = Class(Transport, { batching: false })
              this.transport = new Transport(dispatcher, dispatcher.endpoint)
            }})
      
            it("makes a request immediately", function() { with(this) {
              expect(transport, "request").given([{ no: "batch" }]).exactly(1)
              sendMessage({ no: "batch" })
              clock.tick(10)
            }})
          }})
        }})
      }})
      �����������������������������������������������������������������faye-1.4.0/spec/javascript/uri_spec.js��������������������������������������������������������������0000664�0000000�0000000�00000007121�13711043772�0017715�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������var jstest = require("jstest").Test
      
      var URI = require("../../src/util/uri")
      
      jstest.describe("URI", function() { with(this) {
        describe("parse", function() { with(this) {
          it("parses all the bits of a URI", function() { with(this) {
            assertEqual( {
                href:     "http://example.com:80/foo.html?foo=bar&hello=%2Fworld#cloud",
                protocol: "http:",
                host:     "example.com:80",
                hostname: "example.com",
                port:     "80",
                path:     "/foo.html?foo=bar&hello=%2Fworld",
                pathname: "/foo.html",
                search:   "?foo=bar&hello=%2Fworld",
                query:    { foo: "bar", hello: "/world" },
                hash:     "#cloud"
              }, URI.parse("http://example.com:80/foo.html?foo=bar&hello=%2Fworld#cloud") )
          }})
      
          it("parses a URI with no hash", function() { with(this) {
            assertEqual( {
                href:     "http://example.com:80/foo.html?foo=bar&hello=%2Fworld",
                protocol: "http:",
                host:     "example.com:80",
                hostname: "example.com",
                port:     "80",
                path:     "/foo.html?foo=bar&hello=%2Fworld",
                pathname: "/foo.html",
                search:   "?foo=bar&hello=%2Fworld",
                query:    { foo: "bar", hello: "/world" },
                hash:     ""
              }, URI.parse("http://example.com:80/foo.html?foo=bar&hello=%2Fworld") )
          }})
      
          it("parses a URI with no query", function() { with(this) {
            assertEqual( {
                href:     "http://example.com:80/foo.html#cloud",
                protocol: "http:",
                host:     "example.com:80",
                hostname: "example.com",
                port:     "80",
                path:     "/foo.html",
                pathname: "/foo.html",
                search:   "",
                query:    {},
                hash:     "#cloud"
              }, URI.parse("http://example.com:80/foo.html#cloud") )
          }})
      
          it("parses a URI with an encoded path", function() { with(this) {
            assertEqual( {
                href:     "http://example.com:80/fo%20o.html?foo=bar&hello=%2Fworld#cloud",
                protocol: "http:",
                host:     "example.com:80",
                hostname: "example.com",
                port:     "80",
                path:     "/fo%20o.html?foo=bar&hello=%2Fworld",
                pathname: "/fo%20o.html",
                search:   "?foo=bar&hello=%2Fworld",
                query:    { foo: "bar", hello: "/world" },
                hash:     "#cloud"
              }, URI.parse("http://example.com:80/fo%20o.html?foo=bar&hello=%2Fworld#cloud") )
          }})
      
          it("parses a URI with no path", function() { with(this) {
            assertEqual( {
                href:     "http://example.com:80/?foo=bar&hello=%2Fworld#cloud",
                protocol: "http:",
                host:     "example.com:80",
                hostname: "example.com",
                port:     "80",
                path:     "/?foo=bar&hello=%2Fworld",
                pathname: "/",
                search:   "?foo=bar&hello=%2Fworld",
                query:    { foo: "bar", hello: "/world" },
                hash:     "#cloud"
              }, URI.parse("http://example.com:80?foo=bar&hello=%2Fworld#cloud") )
          }})
      
          it("parses a URI with no port", function() { with(this) {
            assertEqual( {
                href:     "http://example.com/foo.html?foo=bar&hello=%2Fworld#cloud",
                protocol: "http:",
                host:     "example.com",
                hostname: "example.com",
                port:     "",
                path:     "/foo.html?foo=bar&hello=%2Fworld",
                pathname: "/foo.html",
                search:   "?foo=bar&hello=%2Fworld",
                query:    { foo: "bar", hello: "/world" },
                hash:     "#cloud"
              }, URI.parse("http://example.com/foo.html?foo=bar&hello=%2Fworld#cloud") )
          }})
        }})
      }})
      �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/javascript/util/��������������������������������������������������������������������0000775�0000000�0000000�00000000000�13711043772�0016522�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/javascript/util/copy_object_spec.js�������������������������������������������������0000664�0000000�0000000�00000001250�13711043772�0022370�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������var jstest = require("jstest").Test
      
      var copyObject = require("../../../src/util/copy_object")
      
      jstest.describe("copyObject", function() { with(this) {
        before(function() { with(this) {
          this.object = { foo: "bar", qux: 42, hey: null, obj: { bar: 67 }}
        }})
      
        it("returns an equal object", function() { with(this) {
          assertEqual( { foo: "bar", qux: 42, hey: null, obj: { bar: 67 }},
                       copyObject(object) )
        }})
      
        it("does not return the same object", function() { with(this) {
          assertNotSame( object, copyObject(object) )
        }})
      
        it("performs a deep clone", function() { with(this) {
          assertNotSame( object.obj, copyObject(object).obj )
        }})
      }})
      ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/javascript/util/random_spec.js������������������������������������������������������0000664�0000000�0000000�00000001142�13711043772�0021350�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������var jstest = require("jstest").Test,
          Range  = require("jstest").Range
      
      var random = require("../../../src/util/random")
      
      jstest.describe("random", function() { with(this) {
        if (typeof document !== "undefined") return
      
        it("returns a 160-bit random number in base 36", function() { with(this) {
          assertMatch( /^[a-z0-9]+$/, random() )
        }})
      
        it("always produces the same length of string", function() { with(this) {
          var ids = new Range(1,100).map(function() { return random().length })
          var expected = new Range(1,100).map(function() { return 31 })
          assertEqual( expected, ids )
        }})
      }})
      ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/phantom.js��������������������������������������������������������������������������0000664�0000000�0000000�00000000256�13711043772�0015406�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������phantom.injectJs('node_modules/jstest/jstest.js')
      
      var options  = { format: 'dot' },
          reporter = new JS.Test.Reporters.Headless(options)
      
      reporter.open('spec/index.html')
      ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/ruby/�������������������������������������������������������������������������������0000775�0000000�0000000�00000000000�13711043772�0014360�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/ruby/channel_spec.rb����������������������������������������������������������������0000664�0000000�0000000�00000001506�13711043772�0017331�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������require "spec_helper"
      
      describe Faye::Channel do
        describe :expand do
          it "returns all patterns that match a channel" do
            Faye::Channel.expand("/foo").should == [
                                 "/**", "/foo", "/*"]
      
            Faye::Channel.expand("/foo/bar").should == [
                                 "/**", "/foo/bar", "/foo/*", "/foo/**"]
      
            Faye::Channel.expand("/foo/bar/qux").should == [
                                 "/**", "/foo/bar/qux", "/foo/bar/*", "/foo/**", "/foo/bar/**"]
          end
        end
      
        describe Faye::Channel::Set do
          describe :subscribe do
            it "subscribes and unsubscribes without callback" do
              channels = Faye::Channel::Set.new
              channels.subscribe(["/foo/**"], nil)
              channels.keys.should == ["/foo/**"]
              channels.unsubscribe("/foo/**", nil).should == true
            end
          end
        end
      end
      ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/ruby/client_spec.rb�����������������������������������������������������������������0000664�0000000�0000000�00000055404�13711043772�0017205�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������require "spec_helper"
      
      describe Faye::Client do
        let :dispatcher do
          uri = URI("http://localhost/bayeux")
      
          dispatcher = double(:dispatcher,
                              :endpoint         => uri,
                              :connection_type  => "fake-transport",
                              :connection_types => ["fake-transport", "another-transport"],
                              :retry            => 5,
                              :select_transport => nil,
                              :send_message     => nil)
      
          class << dispatcher
            attr_accessor :client_id, :timeout
            include Faye::Publisher
          end
      
          Faye::Publisher.instance_method(:initialize).bind(dispatcher).call
          dispatcher.client_id = "fakeid"
          dispatcher
        end
      
        before do
          Faye::Dispatcher.stub(:new).and_return(dispatcher)
          EM.stub(:add_timer)
        end
      
        def stub_response(response)
          dispatcher.stub(:send_message) do |message|
            response["id"] = message["id"]
            @client.__send__(:receive_message, response)
          end
        end
      
        def create_client
          @client = Faye::Client.new("http://localhost/")
        end
      
        def create_connected_client
          create_client
          stub_response "channel"    => "/meta/handshake",
                        "successful" => true,
                        "version"    => "1.0",
                        "supportedConnectionTypes" => ["websocket"],
                        "clientId"   => "fakeid"
      
          @client.handshake
        end
      
        def subscribe(client, channel, callback = nil)
          stub_response "channel"      => "/meta/subscribe",
                        "successful"   => true,
                        "clientId"     => "fakeid",
                        "subscription" => channel
      
          @subs_called = 0
          callback ||= lambda { |m| @subs_called = 1 }
          @client.subscribe(channel, &callback)
        end
      
        describe :initialize do
          it "puts the client in the UNCONNECTED state" do
            client = Faye::Client.new("http://localhost/")
            client.instance_eval { @state }.should == Faye::Client::UNCONNECTED
          end
        end
      
        describe :handshake do
          before { create_client }
      
          it "creates a transport the server must support" do
            dispatcher.should_receive(:select_transport).with(["long-polling", "callback-polling", "in-process"])
            @client.handshake
          end
      
          it "sends a handshake message to the server" do
            dispatcher.should_receive(:send_message).with({
              "channel" => "/meta/handshake",
              "version" => "1.0",
              "supportedConnectionTypes" => ["fake-transport", "another-transport"],
              "id"      => instance_of(String)
            }, 72, {})
            @client.handshake
          end
      
          it "puts the client in the CONNECTING state" do
            @client.handshake
            @client.instance_eval { @state }.should == Faye::Client::CONNECTING
          end
      
          describe "with an outgoing extension installed" do
            before do
              extension = Class.new do
                def outgoing(message, callback)
                  message["ext"] = { "auth" => "password" }
                  callback.call(message)
                end
              end
              @client.add_extension(extension.new)
            end
      
            it "passes the handshake message through the extension" do
              dispatcher.should_receive(:send_message).with({
                "channel" => "/meta/handshake",
                "version" => "1.0",
                "supportedConnectionTypes" => ["fake-transport", "another-transport"],
                "id"      => instance_of(String),
                "ext"     => { "auth" => "password" }
              }, 72, {})
              @client.handshake
            end
          end
      
          describe "on successful response" do
            before do
              stub_response "channel"    => "/meta/handshake",
                            "successful" => true,
                            "version"    => "1.0",
                            "supportedConnectionTypes" => ["long-polling", "websocket"],
                            "clientId"   => "handshakeid"
            end
      
            it "stores the clientId" do
              @client.handshake
              dispatcher.client_id.should == "handshakeid"
            end
      
            it "puts the client in the CONNECTED state" do
              @client.handshake
              @client.instance_eval { @state }.should == Faye::Client::CONNECTED
            end
      
            it "registers any pre-existing subscriptions" do
              @client.should_receive(:subscribe).with([], true)
              @client.handshake
            end
      
            it "selects a new transport based on what the server supports" do
              dispatcher.should_receive(:select_transport).with(["long-polling", "websocket"])
              @client.handshake
            end
          end
      
          describe "on unsuccessful response" do
            before do
              stub_response "channel"    => "/meta/handshake",
                            "successful" => false,
                            "version"    => "1.0",
                            "supportedConnectionTypes" => ["websocket"]
            end
      
            it "schedules a retry" do
              EM.should_receive(:add_timer)
              @client.handshake
            end
      
            it "puts the client in the UNCONNECTED state" do
              @client.handshake
              @client.instance_eval { @state }.should == Faye::Client::UNCONNECTED
            end
          end
      
          describe "with existing subscriptions after a server restart" do
            before do
              create_connected_client
      
              @message = nil
              subscribe @client, "/messages/foo", lambda { |m| @message = m }
      
              @client.__send__(:receive_message, "advice" => { "reconnect" => "handshake" })
      
              stub_response "channel"    => "/meta/handshake",
                            "successful" => true,
                            "version"    => "1.0",
                            "supportedConnectionTypes" => ["websocket"],
                            "clientId"   => "reconnectid"
            end
      
            it "resends the subscriptions to the server" do
              dispatcher.should_receive(:send_message).with(hash_including("channel" => "/meta/handshake"), 72, {})
              dispatcher.should_receive(:send_message).with({
                "channel"      => "/meta/subscribe",
                "clientId"     => "reconnectid",
                "subscription" => "/messages/foo",
                "id"           => instance_of(String)
              }, 72, {})
              @client.handshake
            end
      
            it "retains the listeners for the subscriptions" do
              @client.handshake
              @client.__send__(:receive_message, "channel" => "/messages/foo", "data" => "ok")
              @message.should == "ok"
            end
          end
      
          describe "with a connected client" do
            before { create_connected_client }
      
            it "does not send a handshake message to the server" do
              dispatcher.should_not_receive(:send_message).with({
                "channel" => "/meta/handshake",
                "version" => "1.0",
                "supportedConnectionTypes" => ["fake-transport", "another-transport"],
                "id"      => instance_of(String)
              }, 72, {})
              @client.handshake
            end
          end
        end
      
        describe :connect do
          describe "with an unconnected client" do
            before do
              stub_response "channel"    => "/meta/handshake",
                            "successful" => true,
                            "version"    => "1.0",
                            "supportedConnectionTypes" => ["websocket"],
                            "clientId"   => "handshakeid"
      
              create_client
            end
      
            it "handshakes before connecting" do
              dispatcher.should_receive(:send_message).with({
                "channel"        => "/meta/connect",
                "clientId"       => "handshakeid",
                "connectionType" => "fake-transport",
                "id"             => instance_of(String)
              }, 72, {})
              @client.connect
            end
          end
      
          describe "with a connected client" do
            before { create_connected_client }
      
            it "sends a connect message to the server" do
              dispatcher.should_receive(:send_message).with({
                "channel"        => "/meta/connect",
                "clientId"       => "fakeid",
                "connectionType" => "fake-transport",
                "id"             => instance_of(String)
              }, 72, {})
              @client.connect
            end
      
            it "only opens one connect request at a time" do
              dispatcher.should_receive(:send_message).with({
                "channel"        => "/meta/connect",
                "clientId"       => "fakeid",
                "connectionType" => "fake-transport",
                "id"             => instance_of(String)
              }, 72, {}).
              exactly(1).
              and_return(nil) # override stub implementation
      
              @client.connect
              @client.connect
            end
          end
        end
      
        describe :disconnect do
          before { create_connected_client }
      
          it "sends a disconnect message to the server" do
            dispatcher.stub(:close)
            dispatcher.should_receive(:send_message).with({
              "channel"  => "/meta/disconnect",
              "clientId" => "fakeid",
              "id"       => instance_of(String)
            }, 72, {})
            @client.disconnect
          end
      
          it "puts the client in the DISCONNECTED state" do
            dispatcher.stub(:close)
            @client.disconnect
            @client.instance_eval { @state }.should == Faye::Client::DISCONNECTED
          end
      
          describe "on successful response" do
            before do
              stub_response "channel"    => "/meta/disconnect",
                            "successful" => true,
                            "clientId"   => "fakeid"
            end
      
            it "closes the dispatcher" do
              dispatcher.should_receive(:close)
              @client.disconnect
            end
          end
        end
      
        describe :subscribe do
          before do
            create_connected_client
            @subscribe_message = {
                "channel"      => "/meta/subscribe",
                "clientId"     => "fakeid",
                "subscription" => "/foo/*",
                "id"           => instance_of(String)
              }
          end
      
          describe "with no prior subscriptions" do
            it "sends a subscribe message to the server" do
              dispatcher.should_receive(:send_message).with(@subscribe_message, 72, {})
              @client.subscribe("/foo/*")
            end
      
            # The Bayeux spec says the server should accept a list of subscriptions
            # in one message but the cometD server doesn't actually support this
            describe "with an array of subscriptions" do
              it "sends multiple subscribe messages" do
                dispatcher.should_receive(:send_message).with({
                  "channel"      => "/meta/subscribe",
                  "clientId"     => "fakeid",
                  "subscription" => "/foo",
                  "id"           => instance_of(String)
                }, 72, {})
                dispatcher.should_receive(:send_message).with({
                  "channel"      => "/meta/subscribe",
                  "clientId"     => "fakeid",
                  "subscription" => "/bar",
                  "id"           => instance_of(String)
                }, 72, {})
                @client.subscribe(["/foo", "/bar"])
              end
      
              it "returns an array of subscriptions" do
                subs = @client.subscribe(["/foo", "/bar"])
                subs.size.should == 2
                subs.should be_all { |s| Faye::Subscription === s }
              end
            end
      
            describe "on successful response" do
              before do
                stub_response "channel"      => "/meta/subscribe",
                              "successful"   => true,
                              "clientId"     => "fakeid",
                              "subscription" => "/foo/*"
              end
      
              it "sets up a listener for the subscribed channel" do
                @message = nil
                @client.subscribe("/foo/*") { |m| @message = m }
                @client.__send__(:receive_message, "channel" => "/foo/bar", "data" => "hi")
                @message.should == "hi"
              end
      
              it "yields the channel when requested" do
                @message = nil
                @client.subscribe("/foo/*").with_channel { |c, m| @message = [c, m] }
                @client.__send__(:receive_message, "channel" => "/foo/bar", "data" => "hi")
                @message.should == ["/foo/bar", "hi"]
              end
      
              it "does not call the listener for non-matching channels" do
                @message = nil
                @client.subscribe("/foo/*") { |m| @message = m }
                @client.__send__(:receive_message, "channel" => "/bar", "data" => "hi")
                @message.should be_nil
              end
      
              it "activates the subscription" do
                active = false
                @client.subscribe("/foo/*").callback { active = true }
                active.should be true
              end
      
              describe "with an incoming extension installed" do
                before do
                  extension = Class.new do
                    def incoming(message, callback)
                      message["data"]["changed"] = true if message["data"]
                      callback.call(message)
                    end
                  end
                  @client.add_extension(extension.new)
                  @message = nil
                  @client.subscribe("/foo/*") { |m| @message = m }
                end
      
                it "passes delivered messages through the extension" do
                  @client.__send__(:receive_message, "channel" => "/foo/bar", "data" => { "hello" => "there" })
                  @message.should == { "hello" => "there", "changed" => true }
                end
              end
      
              describe "with an outgoing extension installed" do
                before do
                  extension = Class.new do
                    def outgoing(message, callback)
                      message["data"]["changed"] = true if message["data"]
                      callback.call(message)
                    end
                  end
                  @client.add_extension(extension.new)
                  @message = nil
                  @client.subscribe("/foo/*") { |m| @message = m }
                end
      
                it "leaves messages unchanged" do
                  @client.__send__(:receive_message, "channel" => "/foo/bar", "data" => { "hello" => "there" })
                  @message.should == { "hello" => "there" }
                end
              end
      
              describe "with an incoming extension that invalidates the response" do
                before do
                  extension = Class.new do
                    def incoming(message, callback)
                      message["successful"] = false if message["channel"] == "/meta/subscribe"
                      callback.call(message)
                    end
                  end
                  @client.add_extension(extension.new)
                end
      
                it "does not set up a listener for the subscribed channel" do
                  @message = nil
                  @client.subscribe("/foo/*") { |m| @message = m }
                  @client.__send__(:receive_message, "channel" => "/foo/bar", "data" => "hi")
                  @message.should be_nil
                end
      
                it "does not activate the subscription" do
                  active = false
                  @client.subscribe("/foo/*").callback { active = true }
                  active.should be false
                end
              end
            end
      
            describe "on unsuccessful response" do
              before do
                stub_response "channel"      => "/meta/subscribe",
                              "error"        => "403:/meta/foo:Forbidden channel",
                              "successful"   => false,
                              "clientId"     => "fakeid",
                              "subscription" => "/meta/foo"
              end
      
              it "does not set up a listener for the subscribed channel" do
                @message = nil
                @client.subscribe("/meta/foo") { |m| @message = m }
                @client.__send__(:receive_message, "channel" => "/meta/foo", "data" => "hi")
                @message.should be_nil
              end
      
              it "does not activate the subscription" do
                active = false
                @client.subscribe("/meta/foo").callback { active = true }
                active.should be false
              end
      
              it "reports the error through an errback" do
                error = nil
                @client.subscribe("/meta/foo").errback { |e| error = e }
                error.code.should == 403
                error.params.should == ["/meta/foo"]
                error.message.should == "Forbidden channel"
              end
            end
          end
      
          describe "with an existing subscription" do
            before do
              subscribe @client, "/foo/*"
            end
      
            it "does not send another subscribe message to the server" do
              dispatcher.should_not_receive(:send_message).with(@subscribe_message, 72, {})
              @client.subscribe("/foo/*")
            end
      
            it "sets up another listener on the channel" do
              @client.subscribe("/foo/*") { @subs_called += 1 }
              @client.__send__(:receive_message, "channel" => "/foo/bar", "data" => "hi")
              @subs_called.should == 2
            end
      
            it "activates the subscription" do
              active = false
              @client.subscribe("/foo/*").callback { active = true }
              active.should be true
            end
          end
        end
      
        describe :unsubscribe do
          before do
            create_connected_client
            @unsubscribe_message = {
                "channel"      => "/meta/unsubscribe",
                "clientId"     => "fakeid",
                "subscription" => "/foo/*",
                "id"           => instance_of(String)
              }
          end
      
          describe "with no subscriptions" do
            it "does not send an unsubscribe message to the server" do
              dispatcher.should_not_receive(:send_message).with(@unsubscribe_message, 72, {})
              @client.unsubscribe("/foo/*")
            end
          end
      
          describe "with a single subscription" do
            before do
              @message = nil
              @subscription = subscribe @client, "/foo/*", lambda { |m| @message = m }
            end
      
            it "sends an unsubscribe message to the server" do
              dispatcher.should_receive(:send_message).with(@unsubscribe_message, 72, {})
              @client.unsubscribe("/foo/*")
            end
      
            it "removes the listener from the channel" do
              @client.__send__(:receive_message, "channel" => "/foo/bar", "data" => "first")
              @client.unsubscribe("/foo/*", @subscription)
              @client.__send__(:receive_message, "channel" => "/foo/bar", "data" => "second")
              @message.should == "first"
            end
          end
      
          describe "with multiple subscriptions to the same channel" do
            before do
              @messages = []
              @hey = subscribe @client, "/foo/*", lambda { |m| @messages << ("hey " + m["text"]) }
              @bye = subscribe @client, "/foo/*", lambda { |m| @messages << ("bye " + m["text"]) }
            end
      
            it "removes one of the listeners from the channel" do
              @client.__send__(:receive_message, "channel" => "/foo/bar", "data" => { "text" => "you" })
              @client.unsubscribe("/foo/*", @hey)
              @client.__send__(:receive_message, "channel" => "/foo/bar", "data" => { "text" => "you" })
              @messages.should == ["hey you", "bye you", "bye you"]
            end
      
            it "does not send an unsubscribe message if one listener is removed" do
              dispatcher.should_not_receive(:send_message).with(@unsubscribe_message, 72, {})
              @client.unsubscribe("/foo/*", @bye)
            end
      
            it "sends an unsubscribe message if each listener is removed" do
              dispatcher.should_receive(:send_message).with(@unsubscribe_message, 72, {})
              @client.unsubscribe("/foo/*", @bye)
              @client.unsubscribe("/foo/*", @hey)
            end
      
            it "sends an unsubscribe message if all listeners are removed" do
              dispatcher.should_receive(:send_message).with(@unsubscribe_message, 72, {})
              @client.unsubscribe("/foo/*")
            end
          end
      
          describe "with multiple subscriptions to different channels" do
            before do
              subscribe @client, "/foo"
              subscribe @client, "/bar"
            end
      
            it "sends multiple unsubscribe messages if given an array" do
              dispatcher.should_receive(:send_message).with({
                "channel"      => "/meta/unsubscribe",
                "clientId"     => "fakeid",
                "subscription" => "/foo",
                "id"           => instance_of(String)
              }, 72, {})
              dispatcher.should_receive(:send_message).with({
                "channel"      => "/meta/unsubscribe",
                "clientId"     => "fakeid",
                "subscription" => "/bar",
                "id"           => instance_of(String)
              }, 72, {})
              @client.unsubscribe(["/foo", "/bar"])
            end
          end
        end
      
        describe :publish do
          before { create_connected_client }
      
          it "sends the message to the server with an ID" do
            dispatcher.should_receive(:send_message).with({
              "channel"  => "/messages/foo",
              "clientId" => "fakeid",
              "data"     => { "hello" => "world" },
              "id"       => instance_of(String)
            }, 72, {})
            @client.publish("/messages/foo", "hello" => "world")
          end
      
          it "throws an error when publishing to an invalid channel" do
            dispatcher.should_not_receive(:send_message).with(hash_including("channel" => "/messages/*"), 72, {})
            lambda { @client.publish("/messages/*", "hello" => "world") }.should raise_error
          end
      
          describe "on publish failure" do
            before do
              stub_response "channel"      => "/messages/foo",
                            "error"        => "407:/messages/foo:Failed to publish",
                            "successful"   => false,
                            "clientId"     => "fakeid"
            end
      
            it "should not be published" do
              published = false
              @client.publish("/messages/foo", "text" => "hi").callback { published = true }
              published.should be false
            end
      
            it "reports the error through an errback" do
              error = nil
              @client.publish("/messages/foo", "text" => "hi").errback { |e| error = e }
              error.code.should == 407
              error.params.should == ["/messages/foo"]
              error.message.should == "Failed to publish"
            end
          end
      
          describe "on receipt of the published message" do
            before do
              stub_response "channel"      => "/messages/foo",
                            "data"         => { "text" => "hi" },
                            "clientId"     => "fakeid"
            end
      
            it "does not trigger the callbacks" do
              published = false
              publication = @client.publish("/messages/foo", "text" => "hi")
              publication.callback { published = true }
              publication.errback { published = true }
              published.should be false
            end
          end
      
          describe "with an outgoing extension installed" do
            before do
              extension = Class.new do
                def outgoing(message, callback)
                  message["ext"] = { "auth" => "password" }
                  callback.call(message)
                end
              end
              @client.add_extension(extension.new)
            end
      
            it "passes messages through the extension" do
              dispatcher.should_receive(:send_message).with({
                "channel"  => "/messages/foo",
                "clientId" => "fakeid",
                "data"     => { "hello" => "world" },
                "id"       => instance_of(String),
                "ext"      => { "auth" => "password" }
              }, 72, {})
              @client.publish("/messages/foo", "hello" => "world")
            end
          end
      
          describe "with an incoming extension installed" do
            before do
              extension = Class.new do
                def incoming(message, callback)
                  message["ext"] = { "auth" => "password" }
                  callback.call(message)
                end
              end
              @client.add_extension(extension.new)
            end
      
            it "leaves the message unchanged" do
              dispatcher.should_receive(:send_message).with({
                "channel"  => "/messages/foo",
                "clientId" => "fakeid",
                "data"     => { "hello" => "world" },
                "id"       => instance_of(String)
              }, 72, {})
              @client.publish("/messages/foo", "hello" => "world")
            end
          end
        end
      end
      ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/ruby/dispatcher_spec.rb�������������������������������������������������������������0000664�0000000�0000000�00000031651�13711043772�0020053�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������require "spec_helper"
      
      class Scheduler < Faye::Scheduler
        class << self
          attr_accessor :instance
        end
      
        def initialize(*args)
          super
          Scheduler.instance = self
        end
      end
      
      describe Faye::Dispatcher do
        include RSpec::EM::FakeClock
      
        let(:client)    { double(:client, :trigger => nil) }
        let(:endpoint)  { "http://localhost/" }
        let(:transport) { double(:transport, :endpoint => URI.parse(endpoint), :connection_type => "long-polling") }
        let(:options)   { {} }
      
        before do
          allow(Faye::Transport).to receive(:get).and_yield(transport)
          @dispatcher = Faye::Dispatcher.new(client, endpoint, options)
          clock.stub
        end
      
        after do
          clock.reset
        end
      
        describe :endpoint_for do
          let(:options) { { :endpoints => { "websocket" => "http://sockets/" } } }
      
          it "returns the main endpoint for unspecified connection types" do
            expect(@dispatcher.endpoint_for("long-polling").to_s).to eq("http://localhost/")
          end
      
          it "returns an alternate endpoint where specified" do
            expect(@dispatcher.endpoint_for("websocket").to_s).to eq("http://sockets/")
          end
        end
      
        describe :select_transport do
          let(:connection_types) { ["long-polling", "callback-polling", "websocket"] }
      
          it "asks Transport to select one of the given transports" do
            expect(Faye::Transport).to receive(:get).with(@dispatcher, connection_types, []).and_yield(transport)
            @dispatcher.select_transport(connection_types)
          end
      
          it "asks Transport not to select disabled transports" do
            @dispatcher.disable("websocket")
            expect(Faye::Transport).to receive(:get).with(@dispatcher, connection_types, ["websocket"]).and_yield(transport)
            @dispatcher.select_transport(connection_types)
          end
      
          it "sets connection_type on the dispatcher" do
            allow(transport).to receive(:connection_type).and_return("transport-connection-type")
            @dispatcher.select_transport(connection_types)
            expect(@dispatcher.connection_type).to eq("transport-connection-type")
          end
      
          it "closes the existing transport if a new one is selected" do
            old_transport = double(:old_transport, :connection_type => "old-transport", :endpoint => URI.parse(endpoint))
            allow(Faye::Transport).to receive(:get).with(@dispatcher, ["long-polling"], []).and_yield(old_transport)
            @dispatcher.select_transport(["long-polling"])
      
            expect(old_transport).to receive(:close).exactly(1)
            @dispatcher.select_transport(connection_types)
          end
      
          it "does not close the existing transport if the same one is selected" do
            @dispatcher.select_transport(["long-polling"])
      
            expect(transport).to receive(:close).exactly(0)
            @dispatcher.select_transport(connection_types)
          end
        end
      
        describe :messaging do
          let(:message) { { 'id' => 1 } }
          let(:request) { double(:request) }
      
          let :req_promise do
            promise = EventMachine::DefaultDeferrable.new
            promise.succeed(request)
            promise
          end
      
          before do
            allow(transport).to receive(:close)
            allow(transport).to receive(:send_message).and_return(req_promise)
      
            @dispatcher.select_transport([])
          end
      
          describe :send_message do
            it "does not send a message to the transport if closed" do
              @dispatcher.close
              expect(transport).to receive(:send_message).exactly(0)
              @dispatcher.send_message(message, 25)
            end
      
            it "sends a message to the transport" do
              expect(transport).to receive(:send_message).with({ 'id' => 1 }).exactly(1).and_return(req_promise)
              @dispatcher.send_message(message, 25)
            end
      
            it "sends several different messages to the transport" do
              expect(transport).to receive(:send_message).with({ 'id' => 1 }).exactly(1).and_return(req_promise)
              expect(transport).to receive(:send_message).with({ 'id' => 2 }).exactly(1).and_return(req_promise)
              @dispatcher.send_message({ 'id' => 1 }, 25)
              @dispatcher.send_message({ 'id' => 2 }, 25)
            end
      
            it "does not resend a message if it's already being sent" do
              expect(transport).to receive(:send_message).with({ 'id' => 1 }).exactly(1).and_return(req_promise)
              @dispatcher.send_message(message, 25)
              @dispatcher.send_message(message, 25)
            end
      
            it "sets a timeout to fail the message" do
              @dispatcher.send_message(message, 25)
              expect(@dispatcher).to receive(:handle_error).with({ 'id' => 1 }).exactly(1)
              clock.tick(25)
            end
          end
      
          describe :handle_error do
            before do
              @dispatcher.send_message(message, 25)
            end
      
            it "does not try to resend messages immediately" do
              @dispatcher.handle_error(message)
              expect(transport).not_to receive(:send_message)
            end
      
            it "resends messages immediately if instructed" do
              expect(transport).to receive(:send_message).with({ 'id' => 1 }).exactly(1).and_return(req_promise)
              @dispatcher.handle_error(message, true)
            end
      
            it "resends a message automatically after a timeout on error" do
              @dispatcher.handle_error(message)
              expect(transport).to receive(:send_message).with({ 'id' => 1 }).exactly(1).and_return(req_promise)
              clock.tick(5.5)
            end
      
            it "does not resend a message if the dispatcher was closed while waiting" do
              @dispatcher.handle_error(message)
              expect(transport).not_to receive(:send_message)
              clock.tick(3.5)
              @dispatcher.close
              clock.tick(2)
            end
      
            it "aborts the request used to send the message" do
              expect(request).to receive(:close).exactly(1)
              @dispatcher.handle_error(message)
            end
      
            it "does not resend a message with an ID it does not recognize" do
              @dispatcher.handle_error({ 'id' => 2 })
              expect(transport).not_to receive(:send_message)
              clock.tick(5.5)
            end
      
            it "does not resend a message if it's waiting to resend" do
              @dispatcher.handle_error(message)
              expect(transport).not_to receive(:send_message)
              clock.tick(2.5)
              @dispatcher.send_message(message, 25)
            end
      
            it "does not schedule another resend if an error is reported while waiting to resend" do
              expect(transport).to receive(:send_message).with({ 'id' => 1 }).exactly(1)
              @dispatcher.handle_error(message)
              clock.tick(2.5)
              @dispatcher.handle_error(message)
              clock.tick(5.5)
            end
      
            it "does not schedule a resend if the number of attempts has been exhausted" do
              expect(transport).to receive(:send_message).with({ 'id' => 2 }).exactly(2).and_return(req_promise)
              @dispatcher.send_message({ 'id' => 2 }, 25, :attempts => 2)
              @dispatcher.handle_error({ 'id' => 2 }, true)
              @dispatcher.handle_error({ 'id' => 2 }, true)
            end
      
            it "does not count down attempts when an error is reported while waiting to resend" do
              @dispatcher.send_message({ 'id' => 2 }, 25, :attempts => 3)
              @dispatcher.handle_error({ 'id' => 2 })
              clock.tick(2.5)
              @dispatcher.handle_error({ 'id' => 2 }, true)
              clock.tick(2.5)
              expect(transport).to receive(:send_message).with({ 'id' => 2 }).exactly(1).and_return(req_promise)
              @dispatcher.handle_error({ 'id' => 2 }, true)
            end
      
            it "does not schedule a resend if the deadline has been reached" do
              @dispatcher.handle_response({ 'id' => 1, 'successful' => true })
              @dispatcher.send_message({ 'id' => 2 }, 25, :deadline => 60)
              expect(transport).to receive(:send_message).with({ 'id' => 2 }).exactly(2).and_return(req_promise)
              clock.tick(90)
            end
      
            it "emits the transport:down event via the client" do
              expect(client).to receive(:trigger).with("transport:down").exactly(1)
              @dispatcher.handle_error(message)
            end
      
            it "only emits transport:down once, when the first error is received" do
              @dispatcher.send_message({ 'id' => 2 }, 25)
              expect(client).to receive(:trigger).with("transport:down").exactly(1)
              @dispatcher.handle_error({ 'id' => 1 })
              @dispatcher.handle_error({ 'id' => 2 })
            end
      
            it "emits transport:down again if there was a message since the last event" do
              @dispatcher.send_message({ 'id' => 2 }, 25)
              expect(client).to receive(:trigger).with("transport:down").exactly(2)
              @dispatcher.handle_error({ 'id' => 1 })
              @dispatcher.handle_response({ 'id' => 3 })
              @dispatcher.handle_error({ 'id' => 2 })
            end
          end
      
          describe "with a scheduler" do
            let(:options) { { :scheduler => Scheduler } }
      
            before do
              @dispatcher.send_message(message, 25)
            end
      
            it "notifies the scheduler that the message failed" do
              expect(Scheduler.instance).to receive(:fail!).exactly(1)
              @dispatcher.handle_error(message)
            end
      
            it "asks the scheduler how long to wait before retrying" do
              expect(Scheduler.instance).to receive(:interval).exactly(1).and_return(1)
              @dispatcher.handle_error(message)
            end
      
            it "resends a message after the interval given by the scheduler" do
              allow(Scheduler.instance).to receive(:interval).and_return(3)
              @dispatcher.handle_error(message)
              expect(transport).to receive(:send_message).with({ 'id' => 1 }).exactly(1).and_return(req_promise)
              clock.tick(3.5)
            end
      
            it "asks the scheduler what the message timeout should be" do
              expect(Scheduler.instance).to receive(:timeout).exactly(1).and_return(25)
              @dispatcher.handle_error(message, true)
            end
      
            it "waits the specified amount of time to fail the message" do
              allow(Scheduler.instance).to receive(:timeout).and_return(3)
              @dispatcher.handle_error(message, true)
              expect(@dispatcher).to receive(:handle_error).with({ 'id' => 1 }).exactly(1)
              clock.tick(3)
            end
      
            it "asks the scheduler whether the message is deliverable" do
              expect(Scheduler.instance).to receive(:deliverable?).and_return(true)
              @dispatcher.handle_error(message, true)
            end
      
            it "resends the message if it's deliverable" do
              allow(Scheduler.instance).to receive(:deliverable?).and_return(true)
              expect(transport).to receive(:send_message).with({ 'id' => 1 }).exactly(1)
              @dispatcher.handle_error(message, true)
            end
      
            it "does not resend the message if it's not deliverable" do
              allow(Scheduler.instance).to receive(:deliverable?).and_return(false)
              expect(transport).not_to receive(:send_message)
              @dispatcher.handle_error(message, true)
            end
      
            it "notifies the scheduler that the message is being sent" do
              expect(Scheduler.instance).to receive(:send!).exactly(1)
              @dispatcher.handle_error(message, true)
            end
      
            it "notifies the scheduler to abort of it's not deliverable" do
              allow(Scheduler.instance).to receive(:deliverable?).and_return(false)
              expect(Scheduler.instance).to receive(:abort!).exactly(1)
              @dispatcher.handle_error(message, true)
            end
          end
      
          describe :handle_response do
            before do
              @dispatcher.send_message(message, 25)
            end
      
            it "clears the timeout to resend the message if successful=true" do
              expect(@dispatcher).to receive(:handle_error).exactly(0)
              @dispatcher.handle_response({ 'id' => 1, 'successful' => true })
              clock.tick(25)
            end
      
            it "clears the timeout to resend the message if successful=false" do
              expect(@dispatcher).to receive(:handle_error).exactly(0)
              @dispatcher.handle_response({ 'id' => 1, 'successful' => false })
              clock.tick(25)
            end
      
            it "leaves the timeout to resend the message if successful is missing" do
              expect(@dispatcher).to receive(:handle_error).with({ 'id' => 1 }).exactly(1)
              @dispatcher.handle_response(message)
              clock.tick(25)
            end
      
            it "emits the message as an event" do
              expect(@dispatcher).to receive(:trigger).with(:message, { 'id' => 3 }).exactly(1)
              @dispatcher.handle_response({ 'id' => 3 })
            end
      
            it "emits the transport:up event via the client" do
              expect(client).to receive(:trigger).with("transport:up").exactly(1)
              @dispatcher.handle_response(message)
            end
      
            it "only emits transport:up once, when the first message is received" do
              expect(client).to receive(:trigger).with("transport:up").exactly(1)
              @dispatcher.handle_response({ 'id' => 1 })
              @dispatcher.handle_response({ 'id' => 2 })
            end
      
            it "emits transport:up again if there was an error since the last event" do
              expect(client).to receive(:trigger).with("transport:up").exactly(2)
              @dispatcher.handle_response({ 'id' => 2 })
              @dispatcher.handle_error({ 'id' => 1 })
              @dispatcher.handle_response({ 'id' => 3 })
            end
      
            it "handles id collisions from another client" do
              expect(client).to receive(:trigger).with("transport:down").exactly(1)
              @dispatcher.handle_response({ 'id' => 1 })
              @dispatcher.handle_error({ 'id' => 1 })
            end
          end
        end
      end
      ���������������������������������������������������������������������������������������faye-1.4.0/spec/ruby/encoding_helper.rb�������������������������������������������������������������0000664�0000000�0000000�00000000225�13711043772�0020031�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������module EncodingHelper
        def encode(string)
          return string unless string.respond_to?(:force_encoding)
          string.force_encoding("UTF-8")
        end
      end
      ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/ruby/engine/������������������������������������������������������������������������0000775�0000000�0000000�00000000000�13711043772�0015625�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/ruby/engine/memory_spec.rb����������������������������������������������������������0000664�0000000�0000000�00000000235�13711043772�0020474�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������require "spec_helper"
      
      describe Faye::Engine::Memory do
        let(:engine_opts)  { { :type => Faye::Engine::Memory } }
        it_should_behave_like "faye engine"
      end
      �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/ruby/engine_examples.rb�������������������������������������������������������������0000664�0000000�0000000�00000026747�13711043772�0020070�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# encoding=utf-8
      
      root = File.expand_path('../../..', __FILE__)
      
      require 'rspec/em'
      
      require root + '/spec/ruby/encoding_helper'
      
      require root + '/lib/faye/mixins/deferrable'
      require root + '/lib/faye/mixins/logging'
      require root + '/lib/faye/mixins/publisher'
      require root + '/lib/faye/mixins/timeouts'
      
      require root + '/lib/faye/protocol/channel'
      require root + '/lib/faye/protocol/grammar'
      require root + '/lib/faye/engines/proxy'
      
      EngineSteps = RSpec::EM.async_steps do
        def disconnect_engine(&resume)
          engine.disconnect
          resume.call
        end
      
        def create_client(name, &resume)
          @inboxes ||= {}
          @clients ||= {}
          engine.create_client do |client_id|
            @clients[name] = client_id
            @inboxes[name] ||= []
            resume.call
          end
        end
      
        def connect(name, engine, &resume)
          engine.connect(@clients[name]) do |m|
            m.each do |message|
              message.delete("id")
              @inboxes[name] << message
            end
          end
          EM.add_timer(0.1, &resume)
        end
      
        def destroy_client(name, &resume)
          engine.destroy_client(@clients[name], &resume)
        end
      
        def check_client_id(name, pattern, &resume)
          @clients[name].should =~ pattern
          resume.call
        end
      
        def check_num_clients(n, &resume)
          ids = Set.new
          @clients.each { |name,id| ids.add(id) }
          ids.size.should == n
          resume.call
        end
      
        def check_client_exists(name, exists, &resume)
          engine.client_exists(@clients[name]) do |actual|
            actual.should == exists
            resume.call
          end
        end
      
        def subscribe(name, channel, &resume)
          engine.subscribe(@clients[name], channel, &resume)
        end
      
        def unsubscribe(name, channel, &resume)
          engine.unsubscribe(@clients[name], channel, &resume)
        end
      
        def publish(messages, &resume)
          messages = [messages].flatten
          messages.each do |message|
            message = { "id" => Faye::Engine.random }.merge(message)
            engine.publish(message)
          end
          EM.add_timer(0.1, &resume)
        end
      
        def publish_by(name, message, &resume)
          message = { "clientId" => @clients[name], "id" => Faye::Engine.random }.merge(message)
          engine.publish(message)
          EM.add_timer(0.1, &resume)
        end
      
        def ping(name, &resume)
          engine.ping(@clients[name])
          resume.call
        end
      
        def clock_tick(time, &resume)
          EM.add_timer(time, &resume)
        end
      
        def expect_non_exclusive_event(name, event, args, engine, &resume)
          params  = [@clients[name]] + args
          handler = lambda { |*a| }
      
          # we don't care if the event is called for other clients
          filter = lambda do |*args|
            handler.call(*args) if args[0] == params[0]
          end
      
          engine.bind(event, &filter)
          handler.should_receive(:call).with(*params)
          resume.call
        end
      
        def expect_event(name, event, args, &resume)
          params  = [@clients[name]] + args
          handler = lambda { |*a| }
          engine.bind(event, &handler)
          handler.should_receive(:call).with(*params)
          resume.call
        end
      
        def expect_no_event(name, event, args, &resume)
          params  = [@clients[name]] + args
          handler = lambda { |*a| }
          engine.bind(event, &handler)
          handler.should_not_receive(:call).with(*params)
          resume.call
        end
      
        def expect_message(name, messages, &resume)
          @inboxes[name].should == messages
          resume.call
        end
      
        def expect_no_message(name, &resume)
          @inboxes[name].should == []
          resume.call
        end
      
        def check_different_messages(a, b, &resume)
          @inboxes[a].first.should_not be_equal(@inboxes[b].first)
          resume.call
        end
      end
      
      shared_examples_for "faye engine" do
        include EncodingHelper
        include EngineSteps
      
        def create_engine
          opts = options.merge(engine_opts)
          Faye::Engine::Proxy.new(opts)
        end
      
        let(:options) { { :timeout => 1 } }
        let(:engine) { create_engine }
      
        before do
          Faye::Engine.ensure_reactor_running!
          create_client :alice
          create_client :bob
          create_client :carol
        end
      
        describe :create_client do
          it "returns a client id" do
            create_client :dave
            check_client_id :dave, /^[a-z0-9]+$/
          end
      
          it "returns a different id every time" do
            1.upto(7) { |i| create_client "client#{ i }" }
            check_num_clients 10
          end
      
          it "publishes an event" do
            engine.should_receive(:trigger).with(:handshake, match(/^[a-z0-9]+$/)).exactly(4)
            create_client :dave
          end
      
          describe :gc do
            let(:options) { { :timeout => 0.3, :gc => 0.2 } }
      
            it "doesn't prematurely remove a client after creation" do
              clock_tick 0.25
              check_client_exists :alice, true
            end
          end
        end
      
        describe :client_exists do
          it "returns true if the client id exists" do
            check_client_exists :alice, true
          end
      
          it "returns false if the client id does not exist" do
            check_client_exists :anything, false
          end
        end
      
        describe :ping do
          let(:options) { { :timeout => 0.3, :gc => 0.08 } }
      
          it "removes a client if it does not ping often enough" do
            clock_tick 0.7
            check_client_exists :alice, false
          end
      
          it "prolongs the life of a client" do
            clock_tick 0.45
            ping :alice
            clock_tick 0.45
            check_client_exists :alice, true
            clock_tick 0.45
            check_client_exists :alice, false
          end
        end
      
        describe :destroy_client do
          it "removes the given client" do
            destroy_client :alice
            check_client_exists :alice, false
          end
      
          it "publishes an event" do
            expect_event :alice, :disconnect, []
            destroy_client :alice
          end
      
          describe "when the client has subscriptions" do
            before do
              @message = { "channel" => "/messages/foo", "data" => "ok" }
              subscribe :alice, "/messages/foo"
            end
      
            it "stops the client receiving messages" do
              connect :alice, engine
              destroy_client :alice
              publish @message
              expect_no_message :alice
            end
      
            it "publishes an event" do
              expect_event :alice, :disconnect, []
              destroy_client :alice
            end
          end
        end
      
        describe :subscribe do
          it "publishes an event" do
            expect_event :alice, :subscribe, ["/messages/foo"]
            subscribe :alice, "/messages/foo"
          end
      
          describe "when the client is subscribed to the channel" do
            before { subscribe :alice, "/messages/foo" }
      
            it "does not publish an event" do
              expect_no_event :alice, :subscribe, ["/messages/foo"]
              subscribe :alice, "/messages/foo"
            end
          end
        end
      
        describe :unsubscribe do
          before { subscribe :alice, "/messages/bar" }
      
          it "does not publish an event" do
            expect_no_event :alice, :unsubscribe, ["/messages/foo"]
            unsubscribe :alice, "/messages/foo"
          end
      
          describe "when the client is subscribed to the channel" do
            before { subscribe :alice, "/messages/foo" }
      
            it "publishes an event" do
              expect_event :alice, :unsubscribe, ["/messages/foo"]
              unsubscribe :alice, "/messages/foo"
            end
          end
        end
      
        describe :publish do
          before do
            @message = { "channel" => "/messages/foo", "data" => "ok", "blank" => nil }
            connect :alice, engine
            connect :bob,   engine
            connect :carol, engine
          end
      
          describe "with no subscriptions" do
            it "delivers no messages" do
              publish @message
              expect_no_message :alice
              expect_no_message :bob
              expect_no_message :carol
            end
      
            it "publishes a :publish event with a clientId" do
              expect_event :bob, :publish, ["/messages/foo", "ok"]
              publish_by :bob, @message
            end
      
            it "publishes a :publish event with no clientId" do
              expect_event nil, :publish, ["/messages/foo", "ok"]
              publish @message
            end
          end
      
          describe "with a subscriber" do
            before { subscribe :alice, "/messages/foo" }
      
            it "delivers messages to the subscribed client" do
              publish @message
              expect_message :alice, [@message]
            end
      
            it "delivers multibyte messages correctly" do
              @message["data"] = encode "Apple = "
              publish @message
              expect_message :alice, [@message]
            end
      
            it "publishes a :publish event" do
              expect_event :bob, :publish, ["/messages/foo", "ok"]
              publish_by :bob, @message
            end
          end
      
          describe "with a subscriber that is removed" do
            before do
              subscribe :alice, "/messages/foo"
              unsubscribe :alice, "/messages/foo"
            end
      
            it "does not deliver messages to unsubscribed clients" do
              publish @message
              expect_no_message :alice
              expect_no_message :bob
              expect_no_message :carol
            end
      
            it "publishes a :publish event" do
              expect_event :bob, :publish, ["/messages/foo", "ok"]
              publish_by :bob, @message
            end
          end
      
          describe "with multiple subscribers" do
            before do
              subscribe :alice, "/messages/foo"
              subscribe :bob,   "/messages/bar"
              subscribe :carol, "/messages/foo"
            end
      
            it "delivers messages to the subscribed clients" do
              publish @message
              expect_message    :alice, [@message]
              expect_no_message :bob
              expect_message    :carol, [@message]
            end
          end
      
          describe "with a single wildcard" do
            before do
              subscribe :alice, "/messages/*"
              subscribe :bob,   "/messages/bar"
              subscribe :carol, "/*"
            end
      
            it "delivers messages to matching subscriptions" do
              publish @message
              expect_message    :alice, [@message]
              expect_no_message :bob
              expect_no_message :carol
            end
          end
      
          describe "with a double wildcard" do
            before do
              subscribe :alice, "/messages/**"
              subscribe :bob,   "/messages/bar"
              subscribe :carol, "/**"
            end
      
            it "delivers messages to matching subscriptions" do
              publish @message
              expect_message    :alice, [@message]
              expect_no_message :bob
              expect_message    :carol, [@message]
            end
      
            it "delivers a unique copy of the message to each client" do
              publish @message
              check_different_messages :alice, :carol
            end
          end
      
          describe "with multiple matching subscriptions for the same client" do
            before do
              subscribe :alice, "/messages/foo"
              subscribe :alice, "/messages/*"
            end
      
            it "delivers each message once to each client" do
              publish @message
              expect_message :alice, [@message]
            end
      
            it "delivers the message as many times as it is published" do
              publish [@message, @message]
              expect_message :alice, [@message, @message]
            end
          end
        end
      end
      
      shared_examples_for "distributed engine" do
        include EngineSteps
      
        def create_engine
          opts = options.merge(engine_opts)
          Faye::Engine::Proxy.new(opts)
        end
      
        let(:options) { {} }
        let(:left)  { create_engine }
        let(:right) { create_engine }
      
        alias :engine :left
      
        before do
          Faye::Engine.ensure_reactor_running!
          create_client :alice
          create_client :bob
      
          connect :alice, left
        end
      
        describe :publish do
          before do
            subscribe :alice, "/foo"
            publish "channel" => "/foo", "data" => "first"
          end
      
          it "only delivers each message once" do
            expect_message :alice, ["channel" => "/foo", "data" => "first"]
            publish "channel" => "/foo", "data" => "second"
            connect :alice, right
            expect_message :alice, [{ "channel" => "/foo", "data" => "first" }, { "channel" => "/foo", "data" => "second" }]
          end
        end
      
        describe :gc do
          let(:options) { { :timeout => 0.3, :gc => 0.08 } }
      
          it "calls close in each engine when a client is removed" do
            expect_non_exclusive_event :alice, :close, [], left
            expect_non_exclusive_event :alice, :close, [], right
      
            clock_tick 0.7
          end
        end
      end
      �������������������������faye-1.4.0/spec/ruby/faye_spec.rb�������������������������������������������������������������������0000664�0000000�0000000�00000001507�13711043772�0016646�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������require "spec_helper"
      
      describe Faye do
        describe :random do
          it "returns a 160-bit random number in base 36" do
            Faye.random.should =~ /^[a-z0-9]+$/
          end
      
          it "always produces the same length of string" do
            ids = (1..100).map { Faye.random }
            ids.should be_all { |id| id.size == 31 }
          end
        end
      
        describe :copy_obect do
          let(:object) { { "foo" => "bar", "qux" => 42, "hey" => nil, "obj" => { "bar" => 67 } } }
      
          it "returns an equal object" do
            Faye.copy_object(object).should == { "foo" => "bar", "qux" => 42, "hey" => nil, "obj" => { "bar" => 67 }}
          end
      
          it "does not return the same object" do
            Faye.copy_object(object).should_not be_equal(object)
          end
      
          it "performs a deep clone" do
            Faye.copy_object(object)["obj"].should_not be_equal(object["obj"])
          end
        end
      end
      �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/ruby/grammar_spec.rb����������������������������������������������������������������0000664�0000000�0000000�00000003757�13711043772�0017361�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������require "spec_helper"
      
      describe Faye::Grammar do
        describe :CHANNEL_NAME do
          it "matches valid channel names" do
            Faye::Grammar::CHANNEL_NAME.should =~ "/fo_o/$@()bar"
          end
      
          it "does not match channel patterns" do
            Faye::Grammar::CHANNEL_NAME.should_not =~ "/foo/**"
          end
      
          it "does not match invalid channel names" do
            Faye::Grammar::CHANNEL_NAME.should_not =~ "foo/$@()bar"
            Faye::Grammar::CHANNEL_NAME.should_not =~ "/foo/$@()bar/"
            Faye::Grammar::CHANNEL_NAME.should_not =~ "/fo o/$@()bar"
          end
        end
      
        describe :CHANNEL_PATTERN do
          it "does not match channel names" do
            Faye::Grammar::CHANNEL_PATTERN.should_not =~ "/fo_o/$@()bar"
          end
      
          it "matches valid channel patterns" do
            Faye::Grammar::CHANNEL_PATTERN.should =~ "/foo/**"
            Faye::Grammar::CHANNEL_PATTERN.should =~ "/foo/*"
          end
      
          it "does not match invalid channel patterns" do
            Faye::Grammar::CHANNEL_PATTERN.should_not =~ "/foo/**/*"
          end
        end
      
        describe :ERROR do
          it "matches an error with an argument" do
            Faye::Grammar::ERROR.should =~ "402:xj3sjdsjdsjad:Unknown Client ID"
          end
      
          it "matches an error with many arguments" do
            Faye::Grammar::ERROR.should =~ "403:xj3sjdsjdsjad,/foo/bar:Subscription denied"
          end
      
          it "matches an error with no arguments" do
            Faye::Grammar::ERROR.should =~ "402::Unknown Client ID"
          end
      
          it "does not match an error with no code" do
            Faye::Grammar::ERROR.should_not =~ ":xj3sjdsjdsjad:Unknown Client ID"
          end
      
          it "does not match an error with an invalid code" do
            Faye::Grammar::ERROR.should_not =~ "40:xj3sjdsjdsjad:Unknown Client ID"
          end
        end
      
        describe :VERSION do
          it "matches a version number" do
            Faye::Grammar::VERSION.should =~ "9"
            Faye::Grammar::VERSION.should =~ "9.0.a-delta1"
          end
      
          it "does not match invalid version numbers" do
            Faye::Grammar::VERSION.should_not =~ "9.0.a-delta1."
            Faye::Grammar::VERSION.should_not =~ ""
          end
        end
      end
      �����������������faye-1.4.0/spec/ruby/publisher_spec.rb��������������������������������������������������������������0000664�0000000�0000000�00000001127�13711043772�0017715�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������require "spec_helper"
      
      describe Faye::Publisher do
        let(:publisher) { Class.new { include Faye::Publisher }.new }
      
        describe "with subscribers that remove themselves" do
          before do
            @called_a = false
            @called_b = false
      
            handler = lambda do
              @called_a = true
              publisher.unbind(:event, &handler)
            end
      
            publisher.bind(:event, &handler)
            publisher.bind(:event) { @called_b = true }
          end
      
          it "successfully calls all the callbacks" do
            publisher.trigger(:event)
            @called_a.should == true
            @called_b.should == true
          end
        end
      end
      �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/ruby/rack_adapter_spec.rb�����������������������������������������������������������0000664�0000000�0000000�00000023346�13711043772�0020347�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������require "spec_helper"
      
      describe Faye::RackAdapter do
        include Rack::Test::Methods
      
        let(:adapter)  { Faye::RackAdapter.new(settings) { |ra| @yielded = ra } }
        let(:app)      { ServerProxy.new(adapter) }
        let(:settings) { { :mount => "/bayeux", :timeout => 30 } }
        let(:server)   { double "server" }
      
        after { app.stop }
      
        let(:content_type) { last_response["Content-Type"] }
        let(:json)         { MultiJson.load(body) }
        let(:body)         { last_response.body }
        let(:status)       { last_response.status.to_i }
      
        def headers(name)
          last_response[name]
        end
      
        before do
          Faye::Server.should_receive(:new).with(settings).and_return server
          adapter.stub(:get_client).and_return double("client")
        end
      
        describe "monitoring configuration" do
          it "should be possible by providing a block to initializer" do
            @yielded.should be_instance_of(Faye::RackAdapter)
          end
        end
      
        describe "OPTIONS requests" do
          describe "with origin specified" do
            before { header "Origin", "http://example.com" }
      
            it "returns a matching cross-origin access control header" do
              options "/bayeux"
              headers("Access-Control-Allow-Origin").should == "http://example.com"
              headers("Access-Control-Allow-Credentials").should == "true"
              headers("Access-Control-Allow-Headers").should == "Accept, Authorization, Content-Type, Pragma, X-Requested-With"
              headers("Access-Control-Allow-Methods").should == "POST, GET"
              headers("Access-Control-Max-Age").should == "86400"
            end
          end
      
          describe "with referer specified" do
            before { header "Referer", "http://example.com" }
      
            it "returns a matching cross-origin access control header" do
              options "/bayeux"
              headers("Access-Control-Allow-Origin").should == "http://example.com"
              headers("Access-Control-Allow-Credentials").should == "true"
              headers("Access-Control-Allow-Headers").should == "Accept, Authorization, Content-Type, Pragma, X-Requested-With"
              headers("Access-Control-Allow-Methods").should == "POST, GET"
              headers("Access-Control-Max-Age").should == "86400"
            end
          end
      
          describe "with no origin specified" do
            it "returns a wildcard cross-origin access control header" do
              options "/bayeux"
              headers("Access-Control-Allow-Origin").should == "*"
              headers("Access-Control-Allow-Credentials").should == "true"
              headers("Access-Control-Allow-Headers").should == "Accept, Authorization, Content-Type, Pragma, X-Requested-With"
              headers("Access-Control-Allow-Methods").should == "POST, GET"
              headers("Access-Control-Max-Age").should == "86400"
            end
          end
        end
      
        describe "POST requests" do
          describe "with cross-origin access control" do
            shared_examples_for "cross-origin request" do
              before do
                header "Origin", "http://example.com"
              end
      
              it "returns a matching cross-origin access control header" do
                server.stub(:process).and_yield []
                post "/bayeux", :message => '[]'
                headers("Access-Control-Allow-Origin").should == "http://example.com"
              end
      
              it "forwards the message param onto the server" do
                server.should_receive(:process).with({ "channel" => "/plain" }, instance_of(Rack::Request)).and_yield []
                post "/bayeux", "message=%7B%22channel%22%3A%22%2Fplain%22%7D"
              end
      
              it "returns the server's response as JSON" do
                server.stub(:process).and_yield ["channel" => "/meta/handshake"]
                post "/bayeux", "message=%5B%5D"
                status.should == 200
                content_type.should == "application/json; charset=utf-8"
                headers("Content-Length").should == "31"
                json.should == ["channel" => "/meta/handshake"]
              end
      
              it "returns a 400 response if malformed JSON is given" do
                server.should_not_receive(:process)
                post "/bayeux", "message=%7B%5B"
                status.should == 400
                content_type.should == "text/plain; charset=utf-8"
              end
      
              it "returns a 400 response if primitive JSON is given" do
                server.should_not_receive(:process)
                post "/bayeux", "message=1"
                status.should == 400
                content_type.should == "text/plain; charset=utf-8"
              end
      
              it "returns a 404 if the path is not matched" do
                server.should_not_receive(:process)
                post "/blaf", 'message=%5B%5D'
                status.should == 404
                content_type.should == "text/plain; charset=utf-8"
              end
            end
      
            describe "with text/plain" do
              before { header "Content-Type", "text/plain" }
              it_should_behave_like "cross-origin request"
            end
      
            describe "with application/xml" do
              before { header "Content-Type", "application/xml" }
              it_should_behave_like "cross-origin request"
            end
          end
      
          describe "with application/json" do
            before do
              header "Content-Type", "application/json"
            end
      
            it "does not return an access control header" do
              server.stub(:process).and_yield []
              post "/bayeux", :message => '[]'
              headers("Access-Control-Allow-Origin").should be_nil
            end
      
            it "forwards the POST body onto the server" do
              server.should_receive(:process).with({ "channel" => "/foo" }, instance_of(Rack::Request)).and_yield []
              post "/bayeux", '{ "channel":"/foo" }'
            end
      
            it "returns the server's response as JSON" do
              server.stub(:process).and_yield ["channel" => "/meta/handshake"]
              post "/bayeux", '[]'
              status.should == 200
              content_type.should == "application/json; charset=utf-8"
                headers("Content-Length").should == "31"
              json.should == ["channel" => "/meta/handshake"]
            end
      
            it "returns a 400 response if malformed JSON is given" do
              server.should_not_receive(:process)
              post "/bayeux", "[ }"
              status.should == 400
              content_type.should == "text/plain; charset=utf-8"
            end
      
            it "returns a 404 if the path is not matched" do
              server.should_not_receive(:process)
              post "/blaf", "[]"
              status.should == 404
              content_type.should == "text/plain; charset=utf-8"
            end
          end
      
          describe "with no content type" do
            it "forwards the message param onto the server" do
              server.should_receive(:process).with({ "channel" => "/foo" }, instance_of(Rack::Request)).and_yield []
              post "/bayeux", :message => '{ "channel":"/foo" }'
            end
      
            it "returns the server's response as JSON" do
              server.stub(:process).and_yield ["channel" => "/meta/handshake"]
              post "/bayeux", :message => '[]'
              status.should == 200
              content_type.should == "application/json; charset=utf-8"
              headers("Content-Length").should == "31"
              json.should == ["channel" => "/meta/handshake"]
            end
      
            it "returns a 400 response if malformed JSON is given" do
              server.should_not_receive(:process)
              post "/bayeux", :message => "[ }"
              status.should == 400
              content_type.should == "text/plain; charset=utf-8"
            end
      
            it "returns a 404 if the path is not matched" do
              server.should_not_receive(:process)
              post "/blaf", :message => "[]"
              status.should == 404
              content_type.should == "text/plain; charset=utf-8"
            end
          end
        end
      
        describe "GET requests" do
          let(:params) { { :message => '{ "channel":"/foo" }', :jsonp => "callback" } }
      
          describe "with valid params" do
            it "forwards the message param onto the server" do
              server.should_receive(:process).with({ "channel" => "/foo" }, instance_of(Rack::Request)).and_yield []
              get "/bayeux", params
            end
      
            it "returns the server's response as JavaScript" do
              server.stub(:process).and_yield ["channel" => "/meta/handshake"]
              get "/bayeux", params
              status.should == 200
              content_type.should == "text/javascript; charset=utf-8"
              headers("Content-Length").should == "46"
              body.should == '/**/callback([{"channel":"/meta/handshake"}]);'
            end
      
            it "does not let the client cache the response" do
              server.stub(:process).and_yield ["channel" => "/meta/handshake"]
              get "/bayeux", params
              headers("Cache-Control").should == "no-cache, no-store"
            end
          end
      
          describe "with an unknown path" do
            it "returns a 404" do
              server.should_not_receive(:process)
              get "/blah", params
              status.should == 404
              content_type.should == "text/plain; charset=utf-8"
            end
          end
      
          describe "missing jsonp" do
            before do
              params.delete(:jsonp)
            end
      
            it "returns the server's response using the default callback" do
              server.stub(:process).and_yield ["channel" => "/meta/handshake"]
              get "/bayeux", params
              status.should == 200
              content_type.should == "text/javascript; charset=utf-8"
              headers("Content-Length").should == "51"
              body.should == '/**/jsonpcallback([{"channel":"/meta/handshake"}]);'
            end
          end
      
          shared_examples_for "bad GET request" do
            it "does not call the server" do
              server.should_not_receive(:process)
              get "/bayeux", params
            end
      
            it "returns a 400 response" do
              get "/bayeux", params
              status.should == 400
              content_type.should == "text/plain; charset=utf-8"
            end
          end
      
          describe "with malformed JSON" do
            before { params[:message] = "[ }" }
            it_should_behave_like "bad GET request"
          end
      
          describe "with a callback that's not a JS identifier" do
            before { params[:jsonp] = "42" }
            it_should_behave_like "bad GET request"
          end
      
          describe "missing message" do
            before { params.delete(:message) }
            it_should_behave_like "bad GET request"
          end
        end
      end
      ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/ruby/server/������������������������������������������������������������������������0000775�0000000�0000000�00000000000�13711043772�0015666�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/ruby/server/connect_spec.rb���������������������������������������������������������0000664�0000000�0000000�00000011364�13711043772�0020663�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������require "spec_helper"
      
      describe "server connect" do
        let(:engine) { double "engine" }
        let(:server) { Faye::Server.new }
      
        before do
          Faye::Engine.stub(:get).and_return engine
        end
      
        describe :connect do
          let(:client_id) { "fakeclientid" }
          let(:message) { { "channel" => "/meta/connect",
                            "clientId" => "fakeclientid",
                            "connectionType" => "long-polling"
                        } }
      
          describe "with valid paramters" do
            before do
              message["advice"] = { "timeout" => 60 }
              engine.should_receive(:client_exists).with(client_id).and_yield true
            end
      
            it "connects to the engine to wait for new messages" do
              engine.should_receive(:connect).with(client_id, { "timeout" => 60 })
              server.connect(message) {}
            end
      
            it "returns a successful response and any queued messages" do
              engine.stub(:connect).and_yield([{ "channel" => "/x", "data" => "hello" }])
              server.connect(message) do |response|
                response.should == [
                  { "channel"    => "/meta/connect",
                    "successful" => true,
                    "clientId"   => client_id
                  }, {
                    "channel" => "/x",
                    "data"    => "hello"
                  }
                ]
              end
            end
      
            describe "with a message id" do
              before { message["id"] = "foo" }
      
              it "returns the same id" do
                engine.stub(:connect)
                server.connect(message) do |response|
                  response.should == {
                    "channel"    => "/meta/connect",
                    "successful" => true,
                    "clientId"   => client_id,
                    "id"         => "foo"
                  }
                end
              end
            end
          end
      
          describe "with an unknown client" do
            before do
              engine.should_receive(:client_exists).with(client_id).and_yield false
            end
      
            it "does not connect to the engine" do
              engine.should_not_receive(:connect)
              server.connect(message) {}
            end
      
            it "returns an unsuccessful response" do
              server.connect(message) do |response|
                response.should == {
                  "channel"    => "/meta/connect",
                  "successful" => false,
                  "error"      => "401:fakeclientid:Unknown client"
                }
              end
            end
          end
      
          describe "missing clientId" do
            before do
              message.delete("clientId")
              engine.should_receive(:client_exists).with(nil).and_yield false
            end
      
            it "does not connect to the engine" do
              engine.should_not_receive(:connect)
              server.connect(message) {}
            end
      
            it "returns an unsuccessful response" do
              server.connect(message) do |response|
                response.should == {
                  "channel"    => "/meta/connect",
                  "successful" => false,
                  "error"      => "402:clientId:Missing required parameter"
                }
              end
            end
          end
      
          describe "missing connectionType" do
            before do
              message.delete("connectionType")
              engine.should_receive(:client_exists).with(client_id).and_yield true
            end
      
            it "does not connect to the engine" do
              engine.should_not_receive(:connect)
              server.connect(message) {}
            end
      
            it "returns an unsuccessful response" do
              server.connect(message) do |response|
                response.should == {
                  "channel"    => "/meta/connect",
                  "successful" => false,
                  "error"      => "402:connectionType:Missing required parameter"
                }
              end
            end
          end
      
          describe "with an unknown connectionType" do
            before do
              message["connectionType"] = "flash"
              engine.should_receive(:client_exists).with(client_id).and_yield true
            end
      
            it "does not connect to the engine" do
              engine.should_not_receive(:connect)
              server.connect(message) {}
            end
      
            it "returns an unsuccessful response" do
              server.connect(message) do |response|
                response.should == {
                  "channel"    => "/meta/connect",
                  "successful" => false,
                  "error"      => "301:flash:Connection types not supported"
                }
              end
            end
          end
      
          describe "with an error" do
            before do
              message["error"] = "invalid"
              engine.should_receive(:client_exists).with(client_id).and_yield true
            end
      
            it "does not connect to the engine" do
              engine.should_not_receive(:connect)
              server.connect(message) {}
            end
      
            it "returns an unsuccessful response" do
              server.connect(message) do |response|
                response.should == {
                  "channel"    => "/meta/connect",
                  "successful" => false,
                  "error"      => "invalid"
                }
              end
            end
          end
        end
      end
      ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/ruby/server/disconnect_spec.rb������������������������������������������������������0000664�0000000�0000000�00000006351�13711043772�0021363�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������require "spec_helper"
      
      describe "server disconnect" do
        let(:engine) { double "engine" }
        let(:server) { Faye::Server.new }
      
        before do
          Faye::Engine.stub(:get).and_return engine
        end
      
        describe :disconnect do
          let(:client_id) { "fakeclientid" }
          let(:message) { { "channel" => "/meta/disconnect",
                            "clientId" => "fakeclientid"
                        } }
      
          describe "with valid parameters" do
            before do
              engine.should_receive(:client_exists).with(client_id).and_yield true
            end
      
            it "destroys the client" do
              engine.should_receive(:destroy_client).with(client_id)
              server.disconnect(message) {}
            end
      
            it "returns a successful response" do
              engine.stub(:destroy_client)
              server.disconnect(message) do |response|
                response.should == {
                  "channel"    => "/meta/disconnect",
                  "successful" => true,
                  "clientId"   => client_id
                }
              end
            end
      
            describe "with a message id" do
              before { message["id"] = "foo" }
      
              it "returns the same id" do
                engine.stub(:destroy_client)
                server.disconnect(message) do |response|
                  response.should == {
                    "channel"    => "/meta/disconnect",
                    "successful" => true,
                    "clientId"   => client_id,
                    "id"         => "foo"
                  }
                end
              end
            end
          end
      
          describe "with an unknown client" do
            before do
              engine.should_receive(:client_exists).with(client_id).and_yield false
            end
      
            it "does not destroy the client" do
              engine.should_not_receive(:destroy_client)
              server.disconnect(message) {}
            end
      
            it "returns an unsuccessful response" do
              server.disconnect(message) do |response|
                response.should == {
                  "channel"    => "/meta/disconnect",
                  "successful" => false,
                  "error"      => "401:fakeclientid:Unknown client"
                }
              end
            end
          end
      
          describe "missing clientId" do
            before do
              message.delete("clientId")
              engine.should_receive(:client_exists).with(nil).and_yield false
            end
      
            it "does not destroy the client" do
              engine.should_not_receive(:destroy_client)
              server.disconnect(message) {}
            end
      
            it "returns an unsuccessful response" do
              server.disconnect(message) do |response|
                response.should == {
                  "channel"    => "/meta/disconnect",
                  "successful" => false,
                  "error"      => "402:clientId:Missing required parameter"
                }
              end
            end
          end
      
          describe "with an error" do
            before do
              message["error"] = "invalid"
              engine.should_receive(:client_exists).with(client_id).and_yield true
            end
      
            it "does not destroy the client" do
              engine.should_not_receive(:destroy_client)
              server.disconnect(message) {}
            end
      
            it "returns an unsuccessful response" do
              server.disconnect(message) do |response|
                response.should == {
                  "channel"    => "/meta/disconnect",
                  "successful" => false,
                  "error"      => "invalid"
                }
              end
            end
          end
        end
      end
      ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/ruby/server/extensions_spec.rb������������������������������������������������������0000664�0000000�0000000�00000006112�13711043772�0021424�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������require "spec_helper"
      
      describe "server extensions" do
        let(:engine) do
          engine = double "engine"
          engine.stub(:interval).and_return(0)
          engine.stub(:timeout).and_return(60)
          engine
        end
      
        let(:server)  { Faye::Server.new }
        let(:message) { { "channel" => "/foo", "data" => "hello" } }
      
        before do
          Faye::Engine.stub(:get).and_return engine
        end
      
        describe "with an incoming extension installed" do
          before do
            extension = Class.new do
              def incoming(message, callback)
                message["ext"] = { "auth" => "password" }
                callback.call(message)
              end
            end
            server.add_extension(extension.new)
          end
      
          it "passes incoming messages through the extension" do
            engine.should_receive(:publish).with({ "channel" => "/foo", "data" => "hello", "ext" => { "auth" => "password" }})
            server.process(message, false) {}
          end
      
          it "does not pass outgoing messages through the extension" do
            server.stub(:handshake).and_yield(message)
            engine.stub(:publish)
            response = nil
            server.process({ "channel" => "/meta/handshake" }, false) { |r| response = r }
            response.should == [{ "channel" => "/foo", "data" => "hello" }]
          end
        end
      
        describe "with subscription auth installed" do
          before do
            extension = Class.new do
              def incoming(message, callback)
                if message["channel"] == "/meta/subscribe" and !message["auth"]
                  message["error"] = "Invalid auth"
                end
                callback.call(message)
              end
            end
            server.add_extension(extension.new)
          end
      
          it "does not subscribe using the intended channel" do
            message = {
              "channel" => "/meta/subscribe",
              "clientId" => "fakeclientid",
              "subscription" => "/foo"
            }
            engine.stub(:client_exists).and_yield(true)
            engine.should_not_receive(:subscribe)
            server.process(message, false) {}
          end
      
          it "does not subscribe using an extended channel" do
            message = {
              "channel" => "/meta/subscribe/x",
              "clientId" => "fakeclientid",
              "subscription" => "/foo"
            }
            engine.stub(:client_exists).and_yield(true)
            engine.should_not_receive(:subscribe)
            server.process(message, false) {}
          end
        end
      
        describe "with an outgoing extension installed" do
          before do
            extension = Class.new do
              def outgoing(message, callback)
                message["ext"] = { "auth" => "password" }
                callback.call(message)
              end
            end
            server.add_extension(extension.new)
          end
      
          it "does not pass incoming messages through the extension" do
            engine.should_receive(:publish).with({ "channel" => "/foo", "data" => "hello" })
            server.process(message, false) {}
          end
      
          it "passes outgoing messages through the extension" do
            server.stub(:handshake).and_yield(message)
            engine.stub(:publish)
            response = nil
            server.process({ "channel" => "/meta/handshake" }, false) { |r| response = r }
            response.should == [{ "channel" => "/foo", "data" => "hello", "ext" => { "auth" => "password" }}]
          end
        end
      end
      ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/ruby/server/handshake_spec.rb�������������������������������������������������������0000664�0000000�0000000�00000010357�13711043772�0021161�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������require "spec_helper"
      
      describe "server handshake" do
        let(:engine) { double "engine" }
        let(:server) { Faye::Server.new }
      
        let :connection_types do
          ["long-polling","cross-origin-long-polling","callback-polling","websocket","eventsource","in-process"]
        end
      
        before do
          Faye::Engine.stub(:get).and_return engine
        end
      
        describe :handshake do
          let(:message) { { "channel" => "/meta/handshake",
                            "version" => "1.0",
                            "supportedConnectionTypes" => ["long-polling"]
                        } }
      
          describe "with valid parameters" do
            it "creates a client" do
              engine.should_receive(:create_client)
              server.handshake(message) {}
            end
      
            it "returns a successful response" do
              engine.stub(:create_client).and_yield "clientid"
              server.handshake(message) do |response|
                response.should == {
                  "channel"    => "/meta/handshake",
                  "successful" => true,
                  "version"    => "1.0",
                  "supportedConnectionTypes" => connection_types,
                  "clientId"   => "clientid"
                }
              end
            end
      
            describe "with a message id" do
              before { message["id"] = "foo" }
      
              it "returns the same id" do
                engine.stub(:create_client).and_yield "clientid"
                server.handshake(message) do |response|
                  response.should == {
                    "channel"    => "/meta/handshake",
                    "successful" => true,
                    "version"    => "1.0",
                    "supportedConnectionTypes" => connection_types,
                    "clientId"   => "clientid",
                    "id"         => "foo"
                  }
                end
              end
            end
          end
      
          describe "missing version" do
            before { message.delete "version" }
      
            it "does not create a client" do
              engine.should_not_receive(:create_client)
              server.handshake(message) {}
            end
      
            it "returns an unsuccessful response" do
              server.handshake(message) do |response|
                response.should == {
                  "channel"    => "/meta/handshake",
                  "successful" => false,
                  "error"      => "402:version:Missing required parameter",
                  "version"    => "1.0",
                  "supportedConnectionTypes" => connection_types
                }
              end
            end
          end
      
          describe "missing supportedConnectionTypes" do
            before { message.delete "supportedConnectionTypes" }
      
            it "does not create a client" do
              engine.should_not_receive(:create_client)
              server.handshake(message) {}
            end
      
            it "returns an unsuccessful response" do
              server.handshake(message) do |response|
                response.should == {
                  "channel"    => "/meta/handshake",
                  "successful" => false,
                  "error"      => "402:supportedConnectionTypes:Missing required parameter",
                  "version"    => "1.0",
                  "supportedConnectionTypes" => connection_types
                }
              end
            end
          end
      
          describe "with no matching supportedConnectionTypes" do
            before { message["supportedConnectionTypes"] = ["iframe", "flash"] }
      
            it "does not create a client" do
              engine.should_not_receive(:create_client)
              server.handshake(message) {}
            end
      
            it "returns an unsuccessful response" do
              server.handshake(message) do |response|
                response.should == {
                  "channel"    => "/meta/handshake",
                  "successful" => false,
                  "error"      => "301:iframe,flash:Connection types not supported",
                  "version"    => "1.0",
                  "supportedConnectionTypes" => connection_types
                }
              end
            end
          end
      
          describe "with an error" do
            before { message["error"] = "invalid" }
      
            it "does not createa a client" do
              engine.should_not_receive(:create_client)
              server.handshake(message) {}
            end
      
            it "returns an unsuccessful response" do
              server.handshake(message) do |response|
                response.should == {
                  "channel"    => "/meta/handshake",
                  "successful" => false,
                  "error"      => "invalid",
                  "version"    => "1.0",
                  "supportedConnectionTypes" => connection_types
                }
              end
            end
          end
        end
      end
      ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/ruby/server/integration_spec.rb�����������������������������������������������������0000664�0000000�0000000�00000006463�13711043772�0021561�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# encoding=utf-8
      
      require "spec_helper"
      
      IntegrationSteps = RSpec::EM.async_steps do
        class Tagger
          def incoming(message, callback)
            message["data"]["tagged"] = true if message["data"]
            callback.call(message)
          end
      
          def outgoing(message, request, callback)
            message["data"]["url"] = request.path_info if message["data"]
            callback.call(message)
          end
        end
      
        def server(port, &callback)
          @faye = Faye::RackAdapter.new(:mount => "/bayeux", :timeout => 25)
          @faye.add_extension(Tagger.new)
      
          @server = ServerProxy::App.new(@faye)
          @port   = port
      
          @server.listen(@port)
          EM.next_tick(&callback)
        end
      
        def stop(&callback)
          @server.stop
          EM.next_tick(&callback)
        end
      
        def client(name, channels, &callback)
          @clients       ||= {}
          @inboxes       ||= {}
          @clients[name]   = Faye::Client.new("http://0.0.0.0:#{ @port }/bayeux")
          @inboxes[name]   = {}
      
          n = channels.size
          return @clients[name].connect(&callback) if n.zero?
      
          channels.each do |channel|
            subscription = @clients[name].subscribe(channel) do |message|
              @inboxes[name][channel] ||= []
              @inboxes[name][channel] << message
            end
            subscription.callback do
              n -= 1
              callback.call if n.zero?
            end
          end
        end
      
        def publish(name, channel, message, &callback)
          @clients[name].publish(channel, message)
          EM.add_timer(0.1, &callback)
        end
      
        def check_inbox(name, channel, messages, &callback)
          inbox = @inboxes[name][channel] || []
          inbox.should == messages
          callback.call
        end
      end
      
      describe "server integration" do
        next if RUBY_PLATFORM =~ /java/
      
        include IntegrationSteps
        include EncodingHelper
      
        before do
          server 4180
          client :alice, []
          client :bob,   ["/foo"]
        end
      
        after { stop }
      
        shared_examples_for "message bus" do
          it "delivers a message between clients" do
            publish :alice, "/foo", { "hello" => "world", "extra" => nil }
            check_inbox :bob, "/foo", [{ "hello" => "world", "extra" => nil, "tagged" => true, "url" => "/bayeux" }]
          end
      
          it "does not deliver messages for unsubscribed channels" do
            publish :alice, "/bar", { "hello" => "world" }
            check_inbox :bob, "/foo", []
          end
      
          it "delivers multiple messages" do
            publish :alice, "/foo", { "hello" => "world" }
            publish :alice, "/foo", { "hello" => "world" }
            check_inbox :bob, "/foo", [{ "hello" => "world", "tagged" => true, "url" => "/bayeux" }, { "hello" => "world", "tagged" => true, "url" => "/bayeux" }]
          end
      
          it "delivers multibyte strings" do
            publish :alice, "/foo", { "hello" => encode("Apple = "), "tagged" => true, "url" => "/bayeux" }
            check_inbox :bob, "/foo", [{ "hello" => encode("Apple = "), "tagged" => true, "url" => "/bayeux" }]
          end
        end
      
        shared_examples_for "network transports" do
          describe "with HTTP transport" do
            before do
              Faye::Transport::WebSocket.stub(:usable?).and_yield(false)
            end
      
            it_should_behave_like "message bus"
          end
      
          describe "with WebSocket transport" do
            before do
              Faye::Transport::WebSocket.stub(:usable?).and_yield(true)
            end
      
            it_should_behave_like "message bus"
          end
        end
      
        describe "with HTTP server" do
          let(:server_options) { { :ssl => false } }
          it_should_behave_like "network transports"
        end
      end
      �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/ruby/server/publish_spec.rb���������������������������������������������������������0000664�0000000�0000000�00000005347�13711043772�0020704�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������require "spec_helper"
      
      describe "server publish" do
        let(:engine)  { double "engine" }
        let(:server)  { Faye::Server.new }
        let(:message) { { "channel" => "/some/channel", "data" => "publish" } }
      
        before do
          Faye::Engine.stub(:get).and_return engine
        end
      
        describe "publishing a message" do
          it "tells the engine to publish the message" do
            engine.should_receive(:publish).with(message)
            server.process(message, false) {}
          end
      
          it "returns a successful response" do
            engine.stub(:publish)
            server.process(message, false) do |response|
              response.should == [
                { "channel"     => "/some/channel",
                  "successful"  => true
                }
              ]
            end
          end
      
          describe "with an invalid channel" do
            before { message["channel"] = "channel" }
      
            it "does not tell the engine to publish the message" do
              engine.should_not_receive(:publish)
              server.process(message, false) {}
            end
      
            it "returns an unsuccessful response" do
              engine.stub(:publish)
              server.process(message, false) do |response|
                response.should == [
                  { "channel"     => "channel",
                    "successful"  => false,
                    "error"       => "405:channel:Invalid channel"
                  }
                ]
              end
            end
          end
      
          describe "with no data" do
            before { message.delete("data") }
      
            it "does not tell the engine to publish the message" do
              engine.should_not_receive(:publish)
              server.process(message, false) {}
            end
      
            it "returns an unsuccessful response" do
              engine.stub(:publish)
              server.process(message, false) do |response|
                response.should == [
                  { "channel"     => "/some/channel",
                    "successful"  => false,
                    "error"       => "402:data:Missing required parameter"
                  }
                ]
              end
            end
          end
      
      
          describe "with an error" do
            before { message["error"] = "invalid" }
      
            it "does not tell the engine to publish the message" do
              engine.should_not_receive(:publish)
              server.process(message, false) {}
            end
      
            it "returns an unsuccessful response" do
              engine.stub(:publish)
              server.process(message, false) do |response|
                response.should == [
                  { "channel"     => "/some/channel",
                    "successful"  => false,
                    "error"       => "invalid"
                  }
                ]
              end
            end
          end
      
          describe "to an invalid channel" do
            before { message["channel"] = "/invalid/*" }
      
            it "does not tell the engine to publish the message" do
              engine.should_not_receive(:publish)
              server.process(message, false) {}
            end
          end
        end
      end
      �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/ruby/server/subscribe_spec.rb�������������������������������������������������������0000664�0000000�0000000�00000016407�13711043772�0021216�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������require "spec_helper"
      
      describe "server subscribe" do
        let(:engine) { double "engine" }
        let(:server) { Faye::Server.new }
      
        before do
          Faye::Engine.stub(:get).and_return engine
        end
      
        describe :subscribe do
          let(:client_id) { "fakeclientid" }
          let(:message) { { "channel" => "/meta/subscribe",
                            "clientId" => "fakeclientid",
                            "subscription" => "/foo"
                        } }
      
          describe "with valid parameters" do
            before do
              engine.should_receive(:client_exists).with(client_id).and_yield true
            end
      
            it "subscribes the client to the channel" do
              engine.should_receive(:subscribe).with(client_id, "/foo")
              server.subscribe(message) {}
            end
      
            it "returns a successful response" do
              engine.stub(:subscribe)
              server.subscribe(message) do |response|
                response.should == {
                  "channel"      => "/meta/subscribe",
                  "successful"   => true,
                  "clientId"     => client_id,
                  "subscription" => "/foo"
                }
              end
            end
      
            describe "with a list of subscriptions" do
              before do
                message["subscription"] = ["/foo", "/bar"]
              end
      
              it "creates multiple subscriptions" do
                engine.should_receive(:subscribe).with(client_id, "/foo")
                engine.should_receive(:subscribe).with(client_id, "/bar")
                server.subscribe(message) {}
              end
      
              it "returns a successful response" do
                engine.stub(:subscribe)
                server.subscribe(message) do |response|
                  response.should == {
                    "channel"      => "/meta/subscribe",
                    "successful"   => true,
                    "clientId"     => client_id,
                    "subscription" => ["/foo", "/bar"]
                  }
                end
              end
            end
      
            describe "with a subscription pattern" do
              before do
                message["subscription"] = "/foo/**"
              end
      
              it "subscribes the client to the channel pattern" do
                engine.should_receive(:subscribe).with(client_id, "/foo/**")
                server.subscribe(message) {}
              end
      
              it "returns a successful response" do
                engine.stub(:subscribe)
                server.subscribe(message) do |response|
                  response.should == {
                    "channel"      => "/meta/subscribe",
                    "successful"   => true,
                    "clientId"     => client_id,
                    "subscription" => "/foo/**"
                  }
                end
              end
            end
          end
      
          describe "with an unknown client" do
            before do
              engine.should_receive(:client_exists).with(client_id).and_yield false
            end
      
            it "does not subscribe the client to the channel" do
              engine.should_not_receive(:subscribe)
              server.subscribe(message) {}
            end
      
            it "returns an unsuccessful response" do
              server.subscribe(message) do |response|
                response.should == {
                  "channel"      => "/meta/subscribe",
                  "successful"   => false,
                  "error"        => "401:fakeclientid:Unknown client",
                  "clientId"     => client_id,
                  "subscription" => "/foo"
                }
              end
            end
          end
      
          describe "missing clientId" do
            before do
              message.delete("clientId")
              engine.should_receive(:client_exists).with(nil).and_yield false
            end
      
            it "does not subscribe the client to the channel" do
              engine.should_not_receive(:subscribe)
              server.subscribe(message) {}
            end
      
            it "returns an unsuccessful response" do
              server.subscribe(message) do |response|
                response.should == {
                  "channel"      => "/meta/subscribe",
                  "successful"   => false,
                  "error"        => "402:clientId:Missing required parameter",
                  "subscription" => "/foo"
                }
              end
            end
          end
      
          describe "missing subscription" do
            before do
              message.delete("subscription")
              engine.should_receive(:client_exists).with(client_id).and_yield true
            end
      
            it "does not subscribe the client to the channel" do
              engine.should_not_receive(:subscribe)
              server.subscribe(message) {}
            end
      
            it "returns an unsuccessful response" do
              server.subscribe(message) do |response|
                response.should == {
                  "channel"      => "/meta/subscribe",
                  "successful"   => false,
                  "error"        => "402:subscription:Missing required parameter",
                  "clientId"     => client_id,
                  "subscription" => []
                }
              end
            end
          end
      
          describe "with an invalid channel" do
            before do
              message["subscription"] = "foo"
              engine.should_receive(:client_exists).with(client_id).and_yield true
            end
      
            it "does not subscribe the client to the channel" do
              engine.should_not_receive(:subscribe)
              server.subscribe(message) {}
            end
      
            it "returns an unsuccessful response" do
              server.subscribe(message) do |response|
                response.should == {
                  "channel"      => "/meta/subscribe",
                  "successful"   => false,
                  "error"        => "405:foo:Invalid channel",
                  "clientId"     => client_id,
                  "subscription" => "foo"
                }
              end
            end
          end
      
          describe "with a /meta/* channel" do
            before do
              message["subscription"] = "/meta/foo"
              engine.should_receive(:client_exists).with(client_id).and_yield true
            end
      
            it "does not subscribe the client to the channel" do
              engine.should_not_receive(:subscribe)
              server.subscribe(message) {}
            end
      
            it "returns an unsuccessful response" do
              server.subscribe(message) do |response|
                response.should == {
                  "channel"      => "/meta/subscribe",
                  "successful"   => false,
                  "error"        => "403:/meta/foo:Forbidden channel",
                  "clientId"     => client_id,
                  "subscription" => "/meta/foo"
                }
              end
            end
      
            it "subscribes local clients to the channel" do
              engine.should_receive(:subscribe).with(client_id, "/meta/foo")
              server.subscribe(message, true) {}
            end
      
            it "returns a successful response for local clients" do
              engine.stub(:subscribe)
              server.subscribe(message, true) do |response|
                response.should == {
                  "channel"      => "/meta/subscribe",
                  "successful"   => true,
                  "clientId"     => client_id,
                  "subscription" => "/meta/foo"
                }
              end
            end
          end
      
          describe "with an error" do
            before do
              message["error"] = "invalid"
              engine.should_receive(:client_exists).with(client_id).and_yield true
            end
      
            it "does not subscribe the client to the channel" do
              engine.should_not_receive(:subscribe)
              server.subscribe(message) {}
            end
      
            it "returns an unsuccessful response" do
              server.subscribe(message) do |response|
                response.should == {
                  "channel"      => "/meta/subscribe",
                  "successful"   => false,
                  "error"        => "invalid",
                  "clientId"     => client_id,
                  "subscription" => "/foo"
                }
              end
            end
          end
        end
      end
      ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/ruby/server/unsubscribe_spec.rb�����������������������������������������������������0000664�0000000�0000000�00000016614�13711043772�0021561�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������require "spec_helper"
      
      describe "server unsubscribe" do
        let(:engine) { double "engine" }
        let(:server) { Faye::Server.new }
      
        before do
          Faye::Engine.stub(:get).and_return engine
        end
      
        describe :unsubscribe do
          let(:client_id) { "fakeclientid" }
          let(:message) { { "channel" => "/meta/unsubscribe",
                            "clientId" => "fakeclientid",
                            "subscription" => "/foo"
                        } }
      
          describe "with valid parameters" do
            before do
              engine.should_receive(:client_exists).with(client_id).and_yield true
            end
      
            it "unsubscribes the client from the channel" do
              engine.should_receive(:unsubscribe).with(client_id, "/foo")
              server.unsubscribe(message) {}
            end
      
            it "returns a successful response" do
              engine.stub(:unsubscribe)
              server.unsubscribe(message) do |response|
                response.should == {
                  "channel"      => "/meta/unsubscribe",
                  "successful"   => true,
                  "clientId"     => client_id,
                  "subscription" => "/foo"
                }
              end
            end
      
            describe "with a list of subscriptions" do
              before do
                message["subscription"] = ["/foo", "/bar"]
              end
      
              it "destroys multiple subscriptions" do
                engine.should_receive(:unsubscribe).with(client_id, "/foo")
                engine.should_receive(:unsubscribe).with(client_id, "/bar")
                server.unsubscribe(message) {}
              end
      
              it "returns a successful response" do
                engine.stub(:unsubscribe)
                server.unsubscribe(message) do |response|
                  response.should == {
                    "channel"      => "/meta/unsubscribe",
                    "successful"   => true,
                    "clientId"     => client_id,
                    "subscription" => ["/foo", "/bar"]
                  }
                end
              end
            end
      
            describe "with a subscription pattern" do
              before do
                message["subscription"] = "/foo/**"
              end
      
              it "destroys the subscription to the channel pattern" do
                engine.should_receive(:unsubscribe).with(client_id, "/foo/**")
                server.unsubscribe(message) {}
              end
      
              it "returns a successful response" do
                engine.stub(:unsubscribe)
                server.unsubscribe(message) do |response|
                  response.should == {
                    "channel"      => "/meta/unsubscribe",
                    "successful"   => true,
                    "clientId"     => client_id,
                    "subscription" => "/foo/**"
                  }
                end
              end
            end
          end
      
          describe "with an unknown client" do
            before do
              engine.should_receive(:client_exists).with(client_id).and_yield false
            end
      
            it "does not unsubscribe the client from the channel" do
              engine.should_not_receive(:unsubscribe)
              server.unsubscribe(message) {}
            end
      
            it "returns an unsuccessful response" do
              server.unsubscribe(message) do |response|
                response.should == {
                  "channel"      => "/meta/unsubscribe",
                  "successful"   => false,
                  "error"        => "401:fakeclientid:Unknown client",
                  "clientId"     => client_id,
                  "subscription" => "/foo"
                }
              end
            end
          end
      
          describe "missing clientId" do
            before do
              message.delete("clientId")
              engine.should_receive(:client_exists).with(nil).and_yield false
            end
      
            it "does not unsubscribe the client from the channel" do
              engine.should_not_receive(:unsubscribe)
              server.unsubscribe(message) {}
            end
      
            it "returns an unsuccessful response" do
              server.unsubscribe(message) do |response|
                response.should == {
                  "channel"      => "/meta/unsubscribe",
                  "successful"   => false,
                  "error"        => "402:clientId:Missing required parameter",
                  "subscription" => "/foo"
                }
              end
            end
          end
      
          describe "missing subscription" do
            before do
              message.delete("subscription")
              engine.should_receive(:client_exists).with(client_id).and_yield true
            end
      
            it "does not unsubscribe the client from the channel" do
              engine.should_not_receive(:unsubscribe)
              server.unsubscribe(message) {}
            end
      
            it "returns an unsuccessful response" do
              server.unsubscribe(message) do |response|
                response.should == {
                  "channel"      => "/meta/unsubscribe",
                  "successful"   => false,
                  "error"        => "402:subscription:Missing required parameter",
                  "clientId"     => client_id,
                  "subscription" => []
                }
              end
            end
          end
      
          describe "with an invalid channel" do
            before do
              message["subscription"] = "foo"
              engine.should_receive(:client_exists).with(client_id).and_yield true
            end
      
            it "does not unsubscribe the client from the channel" do
              engine.should_not_receive(:unsubscribe)
              server.unsubscribe(message) {}
            end
      
            it "returns an unsuccessful response" do
              server.unsubscribe(message) do |response|
                response.should == {
                  "channel"      => "/meta/unsubscribe",
                  "successful"   => false,
                  "error"        => "405:foo:Invalid channel",
                  "clientId"     => client_id,
                  "subscription" => "foo"
                }
              end
            end
          end
      
          describe "with a /meta/* channel" do
            before do
              message["subscription"] = "/meta/foo"
              engine.should_receive(:client_exists).with(client_id).and_yield true
            end
      
            it "does not unsubscribe the client from the channel" do
              engine.should_not_receive(:unsubscribe)
              server.unsubscribe(message) {}
            end
      
            it "returns an unsuccessful response" do
              server.unsubscribe(message) do |response|
                response.should == {
                  "channel"      => "/meta/unsubscribe",
                  "successful"   => false,
                  "error"        => "403:/meta/foo:Forbidden channel",
                  "clientId"     => client_id,
                  "subscription" => "/meta/foo"
                }
              end
            end
      
            it "unsubscribes local clients from the channel" do
              engine.should_receive(:unsubscribe).with(client_id, "/meta/foo")
              server.unsubscribe(message, true) {}
            end
      
            it "returns a successful response for local clients" do
              engine.stub(:unsubscribe)
              server.unsubscribe(message, true) do |response|
                response.should == {
                  "channel"      => "/meta/unsubscribe",
                  "successful"   => true,
                  "clientId"     => client_id,
                  "subscription" => "/meta/foo"
                }
              end
            end
          end
      
          describe "with an error" do
            before do
              message["error"] = "invalid"
              engine.should_receive(:client_exists).with(client_id).and_yield true
            end
      
            it "does not unsubscribe the client from the channel" do
              engine.should_not_receive(:unsubscribe)
              server.unsubscribe(message) {}
            end
      
            it "returns an unsuccessful response" do
              server.unsubscribe(message) do |response|
                response.should == {
                  "channel"      => "/meta/unsubscribe",
                  "successful"   => false,
                  "error"        => "invalid",
                  "clientId"     => client_id,
                  "subscription" => "/foo"
                }
              end
            end
          end
        end
      end
      ��������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/ruby/server_proxy.rb����������������������������������������������������������������0000664�0000000�0000000�00000001517�13711043772�0017460�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������class ServerProxy < Rack::Proxy
        HOST = 'localhost'
        PORT = '4180'
      
        class App
          def initialize(app)
            @app = app
          end
      
          def listen(port)
            events = Puma::Events.new($stdout, $stderr)
            binder = Puma::Binder.new(events)
            binder.parse(["tcp://0.0.0.0:#{ PORT }"], self)
      
            @server = Puma::Server.new(self, events)
            @server.binder = binder
            @thread = @server.run
          rescue => e
          end
      
          def stop
            @server.stop
            @thread.join
          end
      
          def call(env)
            @app.call(env)
          end
      
          def log(message)
          end
        end
      
        def initialize(rack_app)
          @app = App.new(rack_app)
          @app.listen(PORT)
        end
      
        def stop
          @app.stop
        end
      
        def rewrite_env(env)
          env['HTTP_HOST'] = HOST
          env['SERVER_PORT'] = PORT
          env[Faye::RackAdapter::HTTP_X_NO_CONTENT_LENGTH] = '1'
          env
        end
      end
      ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/ruby/server_spec.rb�����������������������������������������������������������������0000664�0000000�0000000�00000007221�13711043772�0017227�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������require "spec_helper"
      
      describe Faye::Server do
        let(:engine) { double "engine" }
        let(:server) { Faye::Server.new }
      
        before do
          Faye::Engine.stub(:get).and_return engine
        end
      
        describe :process do
          let(:handshake)   { { "channel" => "/meta/handshake",   "data" => "handshake"   } }
          let(:connect)     { { "channel" => "/meta/connect",     "data" => "connect"     } }
          let(:disconnect)  { { "channel" => "/meta/disconnect",  "data" => "disconnect"  } }
          let(:subscribe)   { { "channel" => "/meta/subscribe",   "data" => "subscribe"   } }
          let(:unsubscribe) { { "channel" => "/meta/unsubscribe", "data" => "unsubscribe" } }
          let(:publish)     { { "channel" => "/some/channel",     "data" => "publish"     } }
      
          before do
            engine.stub(:interval).and_return(0)
            engine.stub(:timeout).and_return(60)
          end
      
          it "returns an empty response for no messages" do
            response = nil
            server.process([], false) { |r| response = r }
            response.should == []
          end
      
          it "ignores invalid messages" do
            response = nil
            server.process([{}, { "channel" => "invalid" }], false) { |r| response = r }
            response.should == [
              { "successful"  => false,
                "error"       => "402:data:Missing required parameter"
              },
              { "channel"     => "invalid",
                "successful"  => false,
                "error"       => "402:data:Missing required parameter"
              }
            ]
          end
      
          it "rejects unknown meta channels" do
            response = nil
            server.process([{ "channel" => "/meta/p" }], false) { |r| response = r }
            response.should == [
              { "channel"     => "/meta/p",
                "successful"  => false,
                "error"       => "403:/meta/p:Forbidden channel"
              }
            ]
          end
      
          it "routes single messages to appropriate handlers" do
            server.should_receive(:handshake).with(handshake, false)
            server.process(handshake, false) {}
          end
      
          it "routes a list of messages to appropriate handlers" do
            server.should_receive(:handshake).with(handshake, false)
            server.should_receive(:connect).with(connect, false)
            server.should_receive(:disconnect).with(disconnect, false)
            server.should_receive(:subscribe).with(subscribe, false)
            server.should_receive(:unsubscribe).with(unsubscribe, false)
      
            engine.should_not_receive(:publish).with(handshake)
            engine.should_not_receive(:publish).with(connect)
            engine.should_not_receive(:publish).with(disconnect)
            engine.should_not_receive(:publish).with(subscribe)
            engine.should_not_receive(:publish).with(unsubscribe)
            engine.should_receive(:publish).with(publish)
      
            server.process([handshake, connect, disconnect, subscribe, unsubscribe, publish], false)
          end
      
          describe "handshaking" do
            before do
              response = { "channel" => "/meta/handshake", "successful" => true }
              server.should_receive(:handshake).with(handshake, false).and_yield(response)
            end
      
            it "returns the handshake response with advice" do
              server.process(handshake, false) do |response|
                response.should == [
                  { "channel" => "/meta/handshake",
                    "successful" => true,
                    "advice" => { "reconnect" => "retry", "interval" => 0, "timeout" => 60000 }
                  }
                ]
              end
            end
          end
      
          describe "connecting for messages" do
            let(:messages) { [{ "channel" => "/a" }, { "channel" => "/b" }] }
      
            before do
              server.should_receive(:connect).with(connect, false).and_yield(messages)
            end
      
            it "returns the new messages" do
              server.process(connect, false) { |r| r.should == messages }
            end
          end
        end
      end
      �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/ruby/transport_spec.rb��������������������������������������������������������������0000664�0000000�0000000�00000011345�13711043772�0017757�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������require "spec_helper"
      
      describe Faye::Transport do
        before do
          Faye.ensure_reactor_running!
        end
      
        let :dispatcher do
          double(:dispatcher, :endpoint_for     => URI.parse("http://example.com/"),
                              :endpoint         => URI.parse("http://example.com/"),
                              :max_request_size => 2048,
                              :cookies          => CookieJar::Jar.new,
                              :headers          => {},
                              :proxy            => {},
                              :tls              => { :verify_peer => true },
                              :transports       => {},
                              :ws_extensions    => [])
        end
      
        describe :get do
          before do
            Faye::Transport::Local.stub(:usable?).and_yield(false)
            Faye::Transport::Http.stub(:usable?).and_yield(false)
          end
      
          def get_transport(connection_types)
            transport = nil
            Faye::Transport.get(dispatcher, connection_types, []) { |t| transport = t }
            transport
          end
      
          let(:transport)       { get_transport ["long-polling", "in-process"] }
          let(:local_transport) { get_transport ["in-process"] }
          let(:http_transport)  { get_transport ["long-polling"] }
      
          describe "when no transport is usable" do
            it "raises an exception" do
              lambda { transport }.should raise_error
            end
          end
      
          describe "when a less preferred transport is usable" do
            before do
              Faye::Transport::Http.stub(:usable?).and_yield(true)
            end
      
            it "returns a transport of the usable type" do
              transport.should be_kind_of(Faye::Transport::Http)
            end
      
            it "rasies an exception of the usable type is not requested" do
              lambda { local_transport }.should raise_error
            end
      
            it "allows the usable type to be specifically selected" do
              http_transport.should be_kind_of(Faye::Transport::Http)
            end
          end
      
          describe "when all transports are usable" do
            before do
              Faye::Transport::Local.stub(:usable?).and_yield(true)
              Faye::Transport::Http.stub(:usable?).and_yield(true)
            end
      
            it "returns the most preferred type" do
              transport.should be_kind_of(Faye::Transport::Local)
            end
      
            it "does not return disabled types" do
              Faye::Transport.get dispatcher, ["long-polling", "in-process"], ["in-process"] do |t|
                t.should be_kind_of(Faye::Transport::Http)
              end
            end
      
            it "allows types to be specifically selected" do
              local_transport.should be_kind_of(Faye::Transport::Local)
              http_transport.should be_kind_of(Faye::Transport::Http)
            end
          end
        end
      
        describe :send_message do
          include RSpec::EM::FakeClock
          before { clock.stub }
          after { clock.reset }
      
          before do
            dispatcher.stub(:client_id).and_return("abc123")
          end
      
          def send_message(message)
            @transport.send_message(message)
          end
      
          describe "for batching transports" do
            before do
              transport_klass = Class.new(Faye::Transport) do
                def batching?
                  true
                end
              end
              @transport = transport_klass.new(dispatcher, dispatcher.endpoint)
            end
      
            it "does not make an immediate request" do
              @transport.should_not_receive(:request)
              send_message({ "batch" => "me" })
            end
      
            it "queues the message to be sent after a timeout" do
              @transport.should_receive(:request).with([{ "batch" => "me" }]).once
              send_message({ "batch" => "me" })
              clock.tick(0.01)
            end
      
            it "allows multiple messages to be batched together" do
              @transport.should_receive(:request).with([{ "id" => 1 }, { "id" => 2 }]).once
              send_message({ "id" => 1 })
              send_message({ "id" => 2 })
              clock.tick(0.01)
            end
      
            it "adds advice to connect messages sent with others" do
              @transport.should_receive(:request).with([{ "channel" => "/meta/connect", "advice" => { "timeout" => 0 }}, {}]).once
              send_message({ "channel" => "/meta/connect" })
              send_message({})
              clock.tick(0.01)
            end
      
            it "adds no advice to connect messages sent alone" do
              @transport.should_receive(:request).with([{ "channel" => "/meta/connect" }]).once
              send_message({ "channel" => "/meta/connect" })
              clock.tick(0.01)
            end
          end
      
          describe "for non-batching transports" do
            before do
              transport_klass = Class.new(Faye::Transport) do
                def batching?
                  false
                end
              end
              @transport = transport_klass.new(dispatcher, dispatcher.endpoint)
            end
      
            it "makes a request immediately" do
              @transport.should_receive(:request).with([{ "no" => "batch" }]).once
              send_message({ "no" => "batch" })
              clock.tick(0.01)
            end
          end
        end
      end
      �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/spec/spec_helper.rb����������������������������������������������������������������������0000664�0000000�0000000�00000000464�13711043772�0016221�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'rubygems'
      require 'bundler/setup'
      require 'rack/proxy'
      require 'rack/test'
      require 'rspec/em'
      
      require 'puma'
      require 'puma/binder'
      require 'puma/events'
      
      require File.expand_path('../../lib/faye', __FILE__)
      
      require 'ruby/encoding_helper'
      require 'ruby/server_proxy'
      require 'ruby/engine_examples'
      ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/�������������������������������������������������������������������������������������0000775�0000000�0000000�00000000000�13711043772�0013234�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/adapters/����������������������������������������������������������������������������0000775�0000000�0000000�00000000000�13711043772�0015037�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/adapters/content_types.js������������������������������������������������������������0000664�0000000�0000000�00000000343�13711043772�0020273�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������module.exports = {
        TYPE_JSON:    { 'Content-Type': 'application/json; charset=utf-8' },
        TYPE_SCRIPT:  { 'Content-Type': 'text/javascript; charset=utf-8' },
        TYPE_TEXT:    { 'Content-Type': 'text/plain; charset=utf-8' }
      };
      ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/adapters/node_adapter.js�������������������������������������������������������������0000664�0000000�0000000�00000024210�13711043772�0020021�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var Buffer = require('safe-buffer').Buffer,
          path        = require('path'),
          querystring = require('querystring'),
          url         = require('url'),
          WebSocket   = require('faye-websocket'),
          EventSource = WebSocket.EventSource;
      
      var constants       = require('../util/constants'),
          assign          = require('../util/assign'),
          idFromMessages  = require('../util/id_from_messages'),
          toJSON          = require('../util/to_json'),
          validateOptions = require('../util/validate_options'),
          Class           = require('../util/class'),
          Logging         = require('../mixins/logging'),
          Publisher       = require('../mixins/publisher'),
          Client          = require('../protocol/client'),
          Server          = require('../protocol/server'),
          contenttypes    = require('./content_types'),
          StaticServer    = require('./static_server');
      
      var NodeAdapter = Class({ className: 'NodeAdapter',
        DEFAULT_ENDPOINT: '/bayeux',
        SCRIPT_PATH:      'faye-browser-min.js',
      
        VALID_JSONP_CALLBACK: /^[a-z_\$][a-z0-9_\$]*(\.[a-z_\$][a-z0-9_\$]*)*$/i,
      
        initialize: function(options) {
          this._options = options || {};
          validateOptions(this._options, ['engine', 'mount', 'ping', 'timeout', 'extensions', 'websocketExtensions']);
      
          this._extensions = [];
          this._endpoint   = this._options.mount || this.DEFAULT_ENDPOINT;
          this._endpointRe = new RegExp('^' + this._endpoint.replace(/\/$/, '') + '(/[^/]*)*(\\.[^\\.]+)?$');
          this._server     = Server.create(this._options);
      
          this._static = new StaticServer(path.join(__dirname, '..', '..', 'client'), /\.(?:js|map)$/);
          this._static.map(path.basename(this._endpoint) + '.js', this.SCRIPT_PATH);
          this._static.map('client.js', this.SCRIPT_PATH);
      
          var extensions = this._options.extensions,
              websocketExtensions = this._options.websocketExtensions,
              i, n;
      
          if (extensions) {
            extensions = [].concat(extensions);
            for (i = 0, n = extensions.length; i < n; i++)
              this.addExtension(extensions[i]);
          }
      
          if (websocketExtensions) {
            websocketExtensions = [].concat(websocketExtensions);
            for (i = 0, n = websocketExtensions.length; i < n; i++)
              this.addWebsocketExtension(websocketExtensions[i]);
          }
        },
      
        listen: function() {
          throw new Error('The listen() method is deprecated - use the attach() method to bind Faye to an http.Server');
        },
      
        addExtension: function(extension) {
          return this._server.addExtension(extension);
        },
      
        removeExtension: function(extension) {
          return this._server.removeExtension(extension);
        },
      
        addWebsocketExtension: function(extension) {
          this._extensions.push(extension);
        },
      
        close: function() {
          return this._server.close();
        },
      
        getClient: function() {
          return this._client = this._client || new Client(this._server);
        },
      
        attach: function(httpServer) {
          this._overrideListeners(httpServer, 'request', 'handle');
          this._overrideListeners(httpServer, 'upgrade', 'handleUpgrade');
        },
      
        _overrideListeners: function(httpServer, event, method) {
          var listeners = httpServer.listeners(event),
              self      = this;
      
          httpServer.removeAllListeners(event);
      
          httpServer.on(event, function(request) {
            if (self.check(request)) return self[method].apply(self, arguments);
      
            for (var i = 0, n = listeners.length; i < n; i++)
              listeners[i].apply(this, arguments);
          });
        },
      
        check: function(request) {
          var path = url.parse(request.url, true).pathname;
          return !!this._endpointRe.test(path);
        },
      
        handle: function(request, response) {
          var requestUrl    = url.parse(request.url, true),
              requestMethod = request.method,
              self          = this;
      
          request.originalUrl = request.url;
      
          request.on('error', function(error) { self._returnError(response, error) });
          response.on('error', function(error) { self._returnError(null, error) });
      
          if (this._static.test(requestUrl.pathname))
            return this._static.call(request, response);
      
          // http://groups.google.com/group/faye-users/browse_thread/thread/4a01bb7d25d3636a
          if (requestMethod === 'OPTIONS' || request.headers['access-control-request-method'] === 'POST')
            return this._handleOptions(request, response);
      
          if (EventSource.isEventSource(request))
            return this.handleEventSource(request, response);
      
          if (requestMethod === 'GET')
            return this._callWithParams(request, response, requestUrl.query);
      
          if (requestMethod === 'POST')
            return this._concatStream(request, function(data) {
              var type   = (request.headers['content-type'] || '').split(';')[0],
                  params = (type === 'application/json')
                         ? { message: data }
                         : querystring.parse(data);
      
              request.body = data;
              this._callWithParams(request, response, params);
            }, this);
      
          this._returnError(response, { message: 'Unrecognized request type' });
        },
      
        _callWithParams: function(request, response, params) {
          if (!params.message)
            return this._returnError(response, { message: 'Received request with no message: ' + this._formatRequest(request) });
      
          try {
            this.debug('Received message via HTTP ' + request.method + ': ?', params.message);
      
            var message = this._parseJSON(params.message),
                jsonp   = params.jsonp || constants.JSONP_CALLBACK,
                isGet   = (request.method === 'GET'),
                type    = isGet ? contenttypes.TYPE_SCRIPT : contenttypes.TYPE_JSON,
                headers = assign({}, type),
                origin  = request.headers.origin;
      
            if (!this.VALID_JSONP_CALLBACK.test(jsonp))
              return this._returnError(response, { message: 'Invalid JSON-P callback: ' + jsonp });
      
            headers['Cache-Control'] = 'no-cache, no-store';
            headers['X-Content-Type-Options'] = 'nosniff';
      
            if (origin) {
              headers['Access-Control-Allow-Credentials'] = 'true';
              headers['Access-Control-Allow-Origin'] = origin;
            }
      
            this._server.process(message, request, function(replies) {
              var body = toJSON(replies);
      
              if (isGet) {
                body = '/**/' + jsonp + '(' + this._jsonpEscape(body) + ');';
                headers['Content-Disposition'] = 'attachment; filename=f.txt';
              }
      
              headers['Content-Length'] = Buffer.from(body, 'utf8').length.toString();
      
              this.debug('HTTP response: ?', body);
              response.writeHead(200, headers);
              response.end(body);
            }, this);
          } catch (error) {
            this._returnError(response, error);
          }
        },
      
        _jsonpEscape: function(json) {
          return json.replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029');
        },
      
        handleUpgrade: function(request, socket, head) {
          var options  = { extensions: this._extensions, ping: this._options.ping },
              ws       = new WebSocket(request, socket, head, [], options),
              clientId = null,
              self     = this;
      
          request.originalUrl = request.url;
      
          ws.onmessage = function(event) {
            try {
              self.debug('Received message via WebSocket[' + ws.version + ']: ?', event.data);
      
              var message = self._parseJSON(event.data),
                  cid     = idFromMessages(message);
      
              if (clientId && cid && cid !== clientId) self._server.closeSocket(clientId, false);
              self._server.openSocket(cid, ws, request);
              if (cid) clientId = cid;
      
              self._server.process(message, request, function(replies) {
                if (ws) ws.send(toJSON(replies));
              });
            } catch (error) {
              console.log(error.stack);
              self.error(error.message + '\nBacktrace:\n' + error.stack);
            }
          };
      
          ws.onclose = function(event) {
            self._server.closeSocket(clientId);
            ws = null;
          };
        },
      
        handleEventSource: function(request, response) {
          var es       = new EventSource(request, response, { ping: this._options.ping }),
              clientId = es.url.split('/').pop(),
              self     = this;
      
          this.debug('Opened EventSource connection for ?', clientId);
          this._server.openSocket(clientId, es, request);
      
          es.onclose = function(event) {
            self._server.closeSocket(clientId);
            es = null;
          };
        },
      
        _handleOptions: function(request, response) {
          var origin = request.headers.origin || request.headers.referer;
          var headers = {
            'Access-Control-Allow-Credentials': 'true',
            'Access-Control-Allow-Headers':     'Accept, Authorization, Content-Type, Pragma, X-Requested-With',
            'Access-Control-Allow-Methods':     'POST, GET',
            'Access-Control-Allow-Origin':      origin || '*',
            'Access-Control-Max-Age':           '86400'
          };
      
          response.writeHead(200, headers);
          response.end('');
        },
      
        _concatStream: function(stream, callback, context) {
          var chunks = [],
              length = 0;
      
          stream.on('data', function(chunk) {
            chunks.push(chunk);
            length += chunk.length;
          });
      
          stream.on('end', function() {
            var buffer = Buffer.alloc(length),
                offset = 0;
      
            for (var i = 0, n = chunks.length; i < n; i++) {
              chunks[i].copy(buffer, offset);
              offset += chunks[i].length;
            }
            callback.call(context, buffer.toString('utf8'));
          });
        },
      
        _parseJSON: function(json) {
          var data = JSON.parse(json);
          if (typeof data === 'object') return data;
          throw new SyntaxError('JSON messages must contain an object or array');
        },
      
        _formatRequest: function(request) {
          var method = request.method.toUpperCase(),
              string = 'curl -X ' + method;
      
          string += " 'http://" + request.headers.host + request.url + "'";
          if (method === 'POST') {
            string += " -H 'Content-Type: " + request.headers['content-type'] + "'";
            string += " -d '" + request.body + "'";
          }
          return string;
        },
      
        _returnError: function(response, error) {
          var message = error.message;
          if (error.stack) message += '\nBacktrace:\n' + error.stack;
          this.error(message);
      
          if (!response) return;
      
          response.writeHead(400, contenttypes.TYPE_TEXT);
          response.end('Bad request');
        }
      });
      
      for (var method in Publisher) (function(method) {
        NodeAdapter.prototype[method] = function() {
          return this._server._engine[method].apply(this._server._engine, arguments);
        };
      })(method);
      
      assign(NodeAdapter.prototype, Logging);
      
      module.exports = NodeAdapter;
      ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/adapters/static_server.js������������������������������������������������������������0000664�0000000�0000000�00000003777�13711043772�0020270�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var crypto = require('crypto'),
          fs     = require('fs'),
          path   = require('path'),
          url    = require('url');
      
      var Class        = require('../util/class'),
          assign       = require('../util/assign'),
          contenttypes = require('./content_types');
      
      var StaticServer = Class({
        initialize: function(directory, pathRegex) {
          this._directory = directory;
          this._pathRegex = pathRegex;
          this._pathMap   = {};
          this._index     = {};
        },
      
        map: function(requestPath, filename) {
          this._pathMap[requestPath] = filename;
        },
      
        test: function(pathname) {
          return this._pathRegex.test(pathname);
        },
      
        call: function(request, response) {
          var pathname = url.parse(request.url, true).pathname,
              filename = path.basename(pathname);
      
          filename = this._pathMap[filename] || filename;
          this._index[filename] = this._index[filename] || {};
      
          var cache    = this._index[filename],
              fullpath = path.join(this._directory, filename);
      
          try {
            cache.content = cache.content || fs.readFileSync(fullpath);
            cache.digest  = cache.digest  || crypto.createHash('sha1').update(cache.content).digest('hex');
            cache.mtime   = cache.mtime   || fs.statSync(fullpath).mtime;
          } catch (error) {
            response.writeHead(404, {});
            return response.end();
          }
      
          var type = /\.js$/.test(pathname) ? 'TYPE_SCRIPT' : 'TYPE_JSON',
              ims  = request.headers['if-modified-since'];
      
          var headers = {
            'ETag':          cache.digest,
            'Last-Modified': cache.mtime.toGMTString()
          };
      
          if (request.headers['if-none-match'] === cache.digest) {
            response.writeHead(304, headers);
            response.end();
          }
          else if (ims && cache.mtime <= new Date(ims)) {
            response.writeHead(304, headers);
            response.end();
          }
          else {
            headers['Content-Length'] = cache.content.length;
            assign(headers, contenttypes[type]);
            response.writeHead(200, headers);
            response.end(cache.content);
          }
        }
      });
      
      module.exports = StaticServer;
      �faye-1.4.0/src/engines/�����������������������������������������������������������������������������0000775�0000000�0000000�00000000000�13711043772�0014664�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/engines/connection.js����������������������������������������������������������������0000664�0000000�0000000�00000003006�13711043772�0017360�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var Class      = require('../util/class'),
          assign     = require('../util/assign'),
          Deferrable = require('../mixins/deferrable'),
          Timeouts   = require('../mixins/timeouts');
      
      var Connection = Class({
        initialize: function(engine, id, options) {
          this._engine  = engine;
          this._id      = id;
          this._options = options;
          this._inbox   = [];
        },
      
        deliver: function(message) {
          delete message.clientId;
          if (this.socket) return this.socket.send(message);
          this._inbox.push(message);
          this._beginDeliveryTimeout();
        },
      
        connect: function(options, callback, context) {
          options = options || {};
          var timeout = (options.timeout !== undefined) ? options.timeout / 1000 : this._engine.timeout;
      
          this.setDeferredStatus('unknown');
          this.callback(callback, context);
      
          this._beginDeliveryTimeout();
          this._beginConnectionTimeout(timeout);
        },
      
        flush: function() {
          this.removeTimeout('connection');
          this.removeTimeout('delivery');
      
          this.setDeferredStatus('succeeded', this._inbox);
          this._inbox = [];
      
          if (!this.socket) this._engine.closeConnection(this._id);
        },
      
        _beginDeliveryTimeout: function() {
          if (this._inbox.length === 0) return;
          this.addTimeout('delivery', this._engine.MAX_DELAY, this.flush, this);
        },
      
        _beginConnectionTimeout: function(timeout) {
          this.addTimeout('connection', timeout, this.flush, this);
        }
      });
      
      assign(Connection.prototype, Deferrable);
      assign(Connection.prototype, Timeouts);
      
      module.exports = Connection;
      ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/engines/memory.js��������������������������������������������������������������������0000664�0000000�0000000�00000010070�13711043772�0016530�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var copyObject = require('../util/copy_object'),
          assign     = require('../util/assign'),
          Namespace  = require('../util/namespace'),
          Set        = require('../util/set'),
          Timeouts   = require('../mixins/timeouts');
      
      var Memory = function(server, options) {
        this._server    = server;
        this._options   = options || {};
        this.reset();
      };
      
      Memory.create = function(server, options) {
        return new Memory(server, options);
      };
      
      Memory.prototype = {
        disconnect: function() {
          this.reset();
          this.removeAllTimeouts();
        },
      
        reset: function() {
          this._namespace = new Namespace();
          this._clients   = {};
          this._channels  = {};
          this._messages  = {};
        },
      
        createClient: function(callback, context) {
          var clientId = this._namespace.generate();
          this._server.debug('Created new client ?', clientId);
          this.ping(clientId);
          this._server.trigger('handshake', clientId);
          callback.call(context, clientId);
        },
      
        destroyClient: function(clientId, callback, context) {
          if (!this._namespace.exists(clientId)) return;
          var clients = this._clients;
      
          if (clients[clientId])
            clients[clientId].forEach(function(channel) { this.unsubscribe(clientId, channel) }, this);
      
          this.removeTimeout(clientId);
          this._namespace.release(clientId);
          delete this._messages[clientId];
          this._server.debug('Destroyed client ?', clientId);
          this._server.trigger('disconnect', clientId);
          this._server.trigger('close', clientId);
          if (callback) callback.call(context);
        },
      
        clientExists: function(clientId, callback, context) {
          callback.call(context, this._namespace.exists(clientId));
        },
      
        ping: function(clientId) {
          var timeout = this._server.timeout;
          if (typeof timeout !== 'number') return;
      
          this._server.debug('Ping ?, ?', clientId, timeout);
          this.removeTimeout(clientId);
          this.addTimeout(clientId, 2 * timeout, function() {
            this.destroyClient(clientId);
          }, this);
        },
      
        subscribe: function(clientId, channel, callback, context) {
          var clients = this._clients, channels = this._channels;
      
          clients[clientId] = clients[clientId] || new Set();
          var trigger = clients[clientId].add(channel);
      
          channels[channel] = channels[channel] || new Set();
          channels[channel].add(clientId);
      
          this._server.debug('Subscribed client ? to channel ?', clientId, channel);
          if (trigger) this._server.trigger('subscribe', clientId, channel);
          if (callback) callback.call(context, true);
        },
      
        unsubscribe: function(clientId, channel, callback, context) {
          var clients  = this._clients,
              channels = this._channels,
              trigger  = false;
      
          if (clients[clientId]) {
            trigger = clients[clientId].remove(channel);
            if (clients[clientId].isEmpty()) delete clients[clientId];
          }
      
          if (channels[channel]) {
            channels[channel].remove(clientId);
            if (channels[channel].isEmpty()) delete channels[channel];
          }
      
          this._server.debug('Unsubscribed client ? from channel ?', clientId, channel);
          if (trigger) this._server.trigger('unsubscribe', clientId, channel);
          if (callback) callback.call(context, true);
        },
      
        publish: function(message, channels) {
          this._server.debug('Publishing message ?', message);
      
          var messages = this._messages,
              clients  = new Set(),
              subs;
      
          for (var i = 0, n = channels.length; i < n; i++) {
            subs = this._channels[channels[i]];
            if (!subs) continue;
            subs.forEach(clients.add, clients);
          }
      
          clients.forEach(function(clientId) {
            this._server.debug('Queueing for client ?: ?', clientId, message);
            messages[clientId] = messages[clientId] || [];
            messages[clientId].push(copyObject(message));
            this.emptyQueue(clientId);
          }, this);
      
          this._server.trigger('publish', message.clientId, message.channel, message.data);
        },
      
        emptyQueue: function(clientId) {
          if (!this._server.hasConnection(clientId)) return;
          this._server.deliver(clientId, this._messages[clientId]);
          delete this._messages[clientId];
        }
      };
      
      assign(Memory.prototype, Timeouts);
      
      module.exports = Memory;
      ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/engines/proxy.js���������������������������������������������������������������������0000664�0000000�0000000�00000007103�13711043772�0016404�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var asap       = require('asap'),
          assign     = require('../util/assign'),
          random     = require('../util/random'),
          Class      = require('../util/class'),
          Promise    = require('../util/promise'),
          Logging    = require('../mixins/logging'),
          Publisher  = require('../mixins/publisher'),
          Channel    = require('../protocol/channel'),
          Connection = require('./connection'),
          Memory     = require('./memory');
      
      var Proxy = assign(Class({ className: 'Engine.Proxy',
        MAX_DELAY:  0,
        INTERVAL:   0,
        TIMEOUT:    60,
      
        initialize: function(options) {
          this._options     = options || {};
          this._connections = {};
          this.interval     = this._options.interval || this.INTERVAL;
          this.timeout      = this._options.timeout  || this.TIMEOUT;
      
          var engineClass = this._options.type || Memory;
          this._engine    = engineClass.create(this, this._options);
      
          this.bind('close', function(clientId) {
            var self = this;
            asap(function() { self.flushConnection(clientId) });
          }, this);
      
          this.debug('Created new engine: ?', this._options);
        },
      
        connect: function(clientId, options, callback, context) {
          this.debug('Accepting connection from ?', clientId);
          this._engine.ping(clientId);
          var conn = this.connection(clientId, true);
          conn.connect(options, callback, context);
          this._engine.emptyQueue(clientId);
        },
      
        hasConnection: function(clientId) {
          return this._connections.hasOwnProperty(clientId);
        },
      
        connection: function(clientId, create) {
          var conn = this._connections[clientId];
          if (conn || !create) return conn;
          this._connections[clientId] = new Connection(this, clientId);
          this.trigger('connection:open', clientId);
          return this._connections[clientId];
        },
      
        closeConnection: function(clientId) {
          this.debug('Closing connection for ?', clientId);
          var conn = this._connections[clientId];
          if (!conn) return;
          if (conn.socket) conn.socket.close();
          this.trigger('connection:close', clientId);
          delete this._connections[clientId];
        },
      
        openSocket: function(clientId, socket) {
          var conn = this.connection(clientId, true);
          conn.socket = socket;
        },
      
        deliver: function(clientId, messages) {
          if (!messages || messages.length === 0) return false;
      
          var conn = this.connection(clientId, false);
          if (!conn) return false;
      
          for (var i = 0, n = messages.length; i < n; i++) {
            conn.deliver(messages[i]);
          }
          return true;
        },
      
        generateId: function() {
          return random();
        },
      
        flushConnection: function(clientId, close) {
          if (!clientId) return;
          this.debug('Flushing connection for ?', clientId);
          var conn = this.connection(clientId, false);
          if (!conn) return;
          if (close === false) conn.socket = null;
          conn.flush();
          this.closeConnection(clientId);
        },
      
        close: function() {
          for (var clientId in this._connections) this.flushConnection(clientId);
          this._engine.disconnect();
        },
      
        disconnect: function() {
          if (this._engine.disconnect) return this._engine.disconnect();
        },
      
        publish: function(message) {
          var channels = Channel.expand(message.channel);
          return this._engine.publish(message, channels);
        }
      }), {
        get: function(options) {
          return new Proxy(options);
        }
      });
      
      var METHODS = ['createClient', 'clientExists', 'destroyClient', 'ping', 'subscribe', 'unsubscribe'];
      
      METHODS.forEach(function(method) {
        Proxy.prototype[method] = function() {
          return this._engine[method].apply(this._engine, arguments);
        };
      });
      
      assign(Proxy.prototype, Publisher);
      assign(Proxy.prototype, Logging);
      
      module.exports = Proxy;
      �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/faye_browser.js����������������������������������������������������������������������0000664�0000000�0000000�00000000447�13711043772�0016266�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var constants = require('./util/constants'),
          Logging   = require('./mixins/logging');
      
      var Faye = {
        VERSION:    constants.VERSION,
      
        Client:     require('./protocol/client'),
        Scheduler:  require('./protocol/scheduler')
      };
      
      Logging.wrapper = Faye;
      
      module.exports = Faye;
      �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/faye_node.js�������������������������������������������������������������������������0000664�0000000�0000000�00000000541�13711043772�0015523�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var constants = require('./util/constants'),
          Logging   = require('./mixins/logging');
      
      var Faye = {
        VERSION:      constants.VERSION,
      
        Client:       require('./protocol/client'),
        Scheduler:    require('./protocol/scheduler'),
        NodeAdapter:  require('./adapters/node_adapter')
      };
      
      Logging.wrapper = Faye;
      
      module.exports = Faye;
      ���������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/mixins/������������������������������������������������������������������������������0000775�0000000�0000000�00000000000�13711043772�0014543�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/mixins/deferrable.js�����������������������������������������������������������������0000664�0000000�0000000�00000002174�13711043772�0017200�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var Promise   = require('../util/promise');
      
      module.exports = {
        then: function(callback, errback) {
          var self = this;
          if (!this._promise)
            this._promise = new Promise(function(resolve, reject) {
              self._resolve = resolve;
              self._reject  = reject;
            });
      
          if (arguments.length === 0)
            return this._promise;
          else
            return this._promise.then(callback, errback);
        },
      
        callback: function(callback, context) {
          return this.then(function(value) { callback.call(context, value) });
        },
      
        errback: function(callback, context) {
          return this.then(null, function(reason) { callback.call(context, reason) });
        },
      
        timeout: function(seconds, message) {
          this.then();
          var self = this;
          this._timer = global.setTimeout(function() {
            self._reject(message);
          }, seconds * 1000);
        },
      
        setDeferredStatus: function(status, value) {
          if (this._timer) global.clearTimeout(this._timer);
      
          this.then();
      
          if (status === 'succeeded')
            this._resolve(value);
          else if (status === 'failed')
            this._reject(value);
          else
            delete this._promise;
        }
      };
      ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/mixins/logging.js��������������������������������������������������������������������0000664�0000000�0000000�00000002006�13711043772�0016525�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var toJSON = require('../util/to_json');
      
      var Logging = {
        LOG_LEVELS: {
          fatal:  4,
          error:  3,
          warn:   2,
          info:   1,
          debug:  0
        },
      
        writeLog: function(messageArgs, level) {
          var logger = Logging.logger || (Logging.wrapper || Logging).logger;
          if (!logger) return;
      
          var args   = Array.prototype.slice.apply(messageArgs),
              banner = '[Faye',
              klass  = this.className,
      
              message = args.shift().replace(/\?/g, function() {
                try {
                  return toJSON(args.shift());
                } catch (error) {
                  return '[Object]';
                }
              });
      
          if (klass) banner += '.' + klass;
          banner += '] ';
      
          if (typeof logger[level] === 'function')
            logger[level](banner + message);
          else if (typeof logger === 'function')
            logger(banner + message);
        }
      };
      
      for (var key in Logging.LOG_LEVELS)
        (function(level) {
          Logging[level] = function() {
            this.writeLog(arguments, level);
          };
        })(key);
      
      module.exports = Logging;
      ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/mixins/publisher.js������������������������������������������������������������������0000664�0000000�0000000�00000002065�13711043772�0017101�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var assign       = require('../util/assign'),
          EventEmitter = require('../util/event_emitter');
      
      var Publisher = {
        countListeners: function(eventType) {
          return this.listeners(eventType).length;
        },
      
        bind: function(eventType, listener, context) {
          var slice   = Array.prototype.slice,
              handler = function() { listener.apply(context, slice.call(arguments)) };
      
          this._listeners = this._listeners || [];
          this._listeners.push([eventType, listener, context, handler]);
          return this.on(eventType, handler);
        },
      
        unbind: function(eventType, listener, context) {
          this._listeners = this._listeners || [];
          var n = this._listeners.length, tuple;
      
          while (n--) {
            tuple = this._listeners[n];
            if (tuple[0] !== eventType) continue;
            if (listener && (tuple[1] !== listener || tuple[2] !== context)) continue;
            this._listeners.splice(n, 1);
            this.removeListener(eventType, tuple[3]);
          }
        }
      };
      
      assign(Publisher, EventEmitter.prototype);
      Publisher.trigger = Publisher.emit;
      
      module.exports = Publisher;
      ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/mixins/timeouts.js�������������������������������������������������������������������0000664�0000000�0000000�00000001322�13711043772�0016750�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      module.exports = {
        addTimeout: function(name, delay, callback, context) {
          this._timeouts = this._timeouts || {};
          if (this._timeouts.hasOwnProperty(name)) return;
          var self = this;
          this._timeouts[name] = global.setTimeout(function() {
            delete self._timeouts[name];
            callback.call(context);
          }, 1000 * delay);
        },
      
        removeTimeout: function(name) {
          this._timeouts = this._timeouts || {};
          var timeout = this._timeouts[name];
          if (!timeout) return;
          global.clearTimeout(timeout);
          delete this._timeouts[name];
        },
      
        removeAllTimeouts: function() {
          this._timeouts = this._timeouts || {};
          for (var name in this._timeouts) this.removeTimeout(name);
        }
      };
      ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/protocol/����������������������������������������������������������������������������0000775�0000000�0000000�00000000000�13711043772�0015075�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/protocol/channel.js������������������������������������������������������������������0000664�0000000�0000000�00000006051�13711043772�0017045�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var Class     = require('../util/class'),
          assign    = require('../util/assign'),
          Publisher = require('../mixins/publisher'),
          Grammar   = require('./grammar');
      
      var Channel = Class({
        initialize: function(name) {
          this.id = this.name = name;
        },
      
        push: function(message) {
          this.trigger('message', message);
        },
      
        isUnused: function() {
          return this.countListeners('message') === 0;
        }
      });
      
      assign(Channel.prototype, Publisher);
      
      assign(Channel, {
        HANDSHAKE:    '/meta/handshake',
        CONNECT:      '/meta/connect',
        SUBSCRIBE:    '/meta/subscribe',
        UNSUBSCRIBE:  '/meta/unsubscribe',
        DISCONNECT:   '/meta/disconnect',
      
        META:         'meta',
        SERVICE:      'service',
      
        expand: function(name) {
          var segments = this.parse(name),
              channels = ['/**', name];
      
          var copy = segments.slice();
          copy[copy.length - 1] = '*';
          channels.push(this.unparse(copy));
      
          for (var i = 1, n = segments.length; i < n; i++) {
            copy = segments.slice(0, i);
            copy.push('**');
            channels.push(this.unparse(copy));
          }
      
          return channels;
        },
      
        isValid: function(name) {
          return Grammar.CHANNEL_NAME.test(name) ||
                 Grammar.CHANNEL_PATTERN.test(name);
        },
      
        parse: function(name) {
          if (!this.isValid(name)) return null;
          return name.split('/').slice(1);
        },
      
        unparse: function(segments) {
          return '/' + segments.join('/');
        },
      
        isMeta: function(name) {
          var segments = this.parse(name);
          return segments ? (segments[0] === this.META) : null;
        },
      
        isService: function(name) {
          var segments = this.parse(name);
          return segments ? (segments[0] === this.SERVICE) : null;
        },
      
        isSubscribable: function(name) {
          if (!this.isValid(name)) return null;
          return !this.isMeta(name) && !this.isService(name);
        },
      
        Set: Class({
          initialize: function() {
            this._channels = {};
          },
      
          getKeys: function() {
            var keys = [];
            for (var key in this._channels) keys.push(key);
            return keys;
          },
      
          remove: function(name) {
            delete this._channels[name];
          },
      
          hasSubscription: function(name) {
            return this._channels.hasOwnProperty(name);
          },
      
          subscribe: function(names, subscription) {
            var name;
            for (var i = 0, n = names.length; i < n; i++) {
              name = names[i];
              var channel = this._channels[name] = this._channels[name] || new Channel(name);
              channel.bind('message', subscription);
            }
          },
      
          unsubscribe: function(name, subscription) {
            var channel = this._channels[name];
            if (!channel) return false;
            channel.unbind('message', subscription);
      
            if (channel.isUnused()) {
              this.remove(name);
              return true;
            } else {
              return false;
            }
          },
      
          distributeMessage: function(message) {
            var channels = Channel.expand(message.channel);
      
            for (var i = 0, n = channels.length; i < n; i++) {
              var channel = this._channels[channels[i]];
              if (channel) channel.trigger('message', message);
            }
          }
        })
      });
      
      module.exports = Channel;
      ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/protocol/client.js�������������������������������������������������������������������0000664�0000000�0000000�00000033612�13711043772�0016716�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var asap            = require('asap'),
          Class           = require('../util/class'),
          Promise         = require('../util/promise'),
          array           = require('../util/array'),
          browser         = require('../util/browser'),
          constants       = require('../util/constants'),
          assign          = require('../util/assign'),
          validateOptions = require('../util/validate_options'),
          Deferrable      = require('../mixins/deferrable'),
          Logging         = require('../mixins/logging'),
          Publisher       = require('../mixins/publisher'),
          Channel         = require('./channel'),
          Dispatcher      = require('./dispatcher'),
          Error           = require('./error'),
          Extensible      = require('./extensible'),
          Publication     = require('./publication'),
          Subscription    = require('./subscription');
      
      var Client = Class({ className: 'Client',
        UNCONNECTED:  1,
        CONNECTING:   2,
        CONNECTED:    3,
        DISCONNECTED: 4,
      
        HANDSHAKE: 'handshake',
        RETRY:     'retry',
        NONE:      'none',
      
        CONNECTION_TIMEOUT: 60,
      
        DEFAULT_ENDPOINT: '/bayeux',
        INTERVAL:         0,
      
        initialize: function(endpoint, options) {
          this.info('New client created for ?', endpoint);
          options = options || {};
      
          validateOptions(options, ['interval', 'timeout', 'endpoints', 'proxy', 'retry', 'scheduler', 'websocketExtensions', 'tls', 'ca']);
      
          this._channels   = new Channel.Set();
          this._dispatcher = Dispatcher.create(this, endpoint || this.DEFAULT_ENDPOINT, options);
      
          this._messageId = 0;
          this._state     = this.UNCONNECTED;
      
          this._responseCallbacks = {};
      
          this._advice = {
            reconnect: this.RETRY,
            interval:  1000 * (options.interval || this.INTERVAL),
            timeout:   1000 * (options.timeout  || this.CONNECTION_TIMEOUT)
          };
          this._dispatcher.timeout = this._advice.timeout / 1000;
      
          this._dispatcher.bind('message', this._receiveMessage, this);
      
          if (browser.Event && global.onbeforeunload !== undefined)
            browser.Event.on(global, 'beforeunload', function() {
              if (array.indexOf(this._dispatcher._disabled, 'autodisconnect') < 0)
                this.disconnect();
            }, this);
        },
      
        addWebsocketExtension: function(extension) {
          return this._dispatcher.addWebsocketExtension(extension);
        },
      
        disable: function(feature) {
          return this._dispatcher.disable(feature);
        },
      
        setHeader: function(name, value) {
          return this._dispatcher.setHeader(name, value);
        },
      
        // Request
        // MUST include:  * channel
        //                * version
        //                * supportedConnectionTypes
        // MAY include:   * minimumVersion
        //                * ext
        //                * id
        //
        // Success Response                             Failed Response
        // MUST include:  * channel                     MUST include:  * channel
        //                * version                                    * successful
        //                * supportedConnectionTypes                   * error
        //                * clientId                    MAY include:   * supportedConnectionTypes
        //                * successful                                 * advice
        // MAY include:   * minimumVersion                             * version
        //                * advice                                     * minimumVersion
        //                * ext                                        * ext
        //                * id                                         * id
        //                * authSuccessful
        handshake: function(callback, context) {
          if (this._advice.reconnect === this.NONE) return;
          if (this._state !== this.UNCONNECTED) return;
      
          this._state = this.CONNECTING;
          var self = this;
      
          this.info('Initiating handshake with ?', this._dispatcher.endpoint.href);
          this._dispatcher.selectTransport(constants.MANDATORY_CONNECTION_TYPES);
      
          this._sendMessage({
            channel:                  Channel.HANDSHAKE,
            version:                  constants.BAYEUX_VERSION,
            supportedConnectionTypes: this._dispatcher.getConnectionTypes()
      
          }, {}, function(response) {
      
            if (response.successful) {
              this._state = this.CONNECTED;
              this._dispatcher.clientId  = response.clientId;
      
              this._dispatcher.selectTransport(response.supportedConnectionTypes);
      
              this.info('Handshake successful: ?', this._dispatcher.clientId);
      
              this.subscribe(this._channels.getKeys(), true);
              if (callback) asap(function() { callback.call(context) });
      
            } else {
              this.info('Handshake unsuccessful');
              global.setTimeout(function() { self.handshake(callback, context) }, this._dispatcher.retry * 1000);
              this._state = this.UNCONNECTED;
            }
          }, this);
        },
      
        // Request                              Response
        // MUST include:  * channel             MUST include:  * channel
        //                * clientId                           * successful
        //                * connectionType                     * clientId
        // MAY include:   * ext                 MAY include:   * error
        //                * id                                 * advice
        //                                                     * ext
        //                                                     * id
        //                                                     * timestamp
        connect: function(callback, context) {
          if (this._advice.reconnect === this.NONE) return;
          if (this._state === this.DISCONNECTED) return;
      
          if (this._state === this.UNCONNECTED)
            return this.handshake(function() { this.connect(callback, context) }, this);
      
          this.callback(callback, context);
          if (this._state !== this.CONNECTED) return;
      
          this.info('Calling deferred actions for ?', this._dispatcher.clientId);
          this.setDeferredStatus('succeeded');
          this.setDeferredStatus('unknown');
      
          if (this._connectRequest) return;
          this._connectRequest = true;
      
          this.info('Initiating connection for ?', this._dispatcher.clientId);
      
          this._sendMessage({
            channel:        Channel.CONNECT,
            clientId:       this._dispatcher.clientId,
            connectionType: this._dispatcher.connectionType
      
          }, {}, this._cycleConnection, this);
        },
      
        // Request                              Response
        // MUST include:  * channel             MUST include:  * channel
        //                * clientId                           * successful
        // MAY include:   * ext                                * clientId
        //                * id                  MAY include:   * error
        //                                                     * ext
        //                                                     * id
        disconnect: function() {
          if (this._state !== this.CONNECTED) return;
          this._state = this.DISCONNECTED;
      
          this.info('Disconnecting ?', this._dispatcher.clientId);
          var promise = new Publication();
      
          this._sendMessage({
            channel:  Channel.DISCONNECT,
            clientId: this._dispatcher.clientId
      
          }, {}, function(response) {
            if (response.successful) {
              this._dispatcher.close();
              promise.setDeferredStatus('succeeded');
            } else {
              promise.setDeferredStatus('failed', Error.parse(response.error));
            }
          }, this);
      
          this.info('Clearing channel listeners for ?', this._dispatcher.clientId);
          this._channels = new Channel.Set();
      
          return promise;
        },
      
        // Request                              Response
        // MUST include:  * channel             MUST include:  * channel
        //                * clientId                           * successful
        //                * subscription                       * clientId
        // MAY include:   * ext                                * subscription
        //                * id                  MAY include:   * error
        //                                                     * advice
        //                                                     * ext
        //                                                     * id
        //                                                     * timestamp
        subscribe: function(channel, callback, context) {
          if (channel instanceof Array)
            return array.map(channel, function(c) {
              return this.subscribe(c, callback, context);
            }, this);
      
          var subscription = new Subscription(this, channel, callback, context),
              force        = (callback === true),
              hasSubscribe = this._channels.hasSubscription(channel);
      
          if (hasSubscribe && !force) {
            this._channels.subscribe([channel], subscription);
            subscription.setDeferredStatus('succeeded');
            return subscription;
          }
      
          this.connect(function() {
            this.info('Client ? attempting to subscribe to ?', this._dispatcher.clientId, channel);
            if (!force) this._channels.subscribe([channel], subscription);
      
            this._sendMessage({
              channel:      Channel.SUBSCRIBE,
              clientId:     this._dispatcher.clientId,
              subscription: channel
      
            }, {}, function(response) {
              if (!response.successful) {
                subscription.setDeferredStatus('failed', Error.parse(response.error));
                return this._channels.unsubscribe(channel, subscription);
              }
      
              var channels = [].concat(response.subscription);
              this.info('Subscription acknowledged for ? to ?', this._dispatcher.clientId, channels);
              subscription.setDeferredStatus('succeeded');
            }, this);
          }, this);
      
          return subscription;
        },
      
        // Request                              Response
        // MUST include:  * channel             MUST include:  * channel
        //                * clientId                           * successful
        //                * subscription                       * clientId
        // MAY include:   * ext                                * subscription
        //                * id                  MAY include:   * error
        //                                                     * advice
        //                                                     * ext
        //                                                     * id
        //                                                     * timestamp
        unsubscribe: function(channel, subscription) {
          if (channel instanceof Array)
            return array.map(channel, function(c) {
              return this.unsubscribe(c, subscription);
            }, this);
      
          var dead = this._channels.unsubscribe(channel, subscription);
          if (!dead) return;
      
          this.connect(function() {
            this.info('Client ? attempting to unsubscribe from ?', this._dispatcher.clientId, channel);
      
            this._sendMessage({
              channel:      Channel.UNSUBSCRIBE,
              clientId:     this._dispatcher.clientId,
              subscription: channel
      
            }, {}, function(response) {
              if (!response.successful) return;
      
              var channels = [].concat(response.subscription);
              this.info('Unsubscription acknowledged for ? from ?', this._dispatcher.clientId, channels);
            }, this);
          }, this);
        },
      
        // Request                              Response
        // MUST include:  * channel             MUST include:  * channel
        //                * data                               * successful
        // MAY include:   * clientId            MAY include:   * id
        //                * id                                 * error
        //                * ext                                * ext
        publish: function(channel, data, options) {
          validateOptions(options || {}, ['attempts', 'deadline']);
          var publication = new Publication();
      
          this.connect(function() {
            this.info('Client ? queueing published message to ?: ?', this._dispatcher.clientId, channel, data);
      
            this._sendMessage({
              channel:  channel,
              data:     data,
              clientId: this._dispatcher.clientId
      
            }, options, function(response) {
              if (response.successful)
                publication.setDeferredStatus('succeeded');
              else
                publication.setDeferredStatus('failed', Error.parse(response.error));
            }, this);
          }, this);
      
          return publication;
        },
      
        _sendMessage: function(message, options, callback, context) {
          message.id = this._generateMessageId();
      
          var timeout = this._advice.timeout
                      ? 1.2 * this._advice.timeout / 1000
                      : 1.2 * this._dispatcher.retry;
      
          this.pipeThroughExtensions('outgoing', message, null, function(message) {
            if (!message) return;
            if (callback) this._responseCallbacks[message.id] = [callback, context];
            this._dispatcher.sendMessage(message, timeout, options || {});
          }, this);
        },
      
        _generateMessageId: function() {
          this._messageId += 1;
          if (this._messageId >= Math.pow(2,32)) this._messageId = 0;
          return this._messageId.toString(36);
        },
      
        _receiveMessage: function(message) {
          var id = message.id, callback;
      
          if (message.successful !== undefined) {
            callback = this._responseCallbacks[id];
            delete this._responseCallbacks[id];
          }
      
          this.pipeThroughExtensions('incoming', message, null, function(message) {
            if (!message) return;
            if (message.advice) this._handleAdvice(message.advice);
            this._deliverMessage(message);
            if (callback) callback[0].call(callback[1], message);
          }, this);
        },
      
        _handleAdvice: function(advice) {
          assign(this._advice, advice);
          this._dispatcher.timeout = this._advice.timeout / 1000;
      
          if (this._advice.reconnect === this.HANDSHAKE && this._state !== this.DISCONNECTED) {
            this._state = this.UNCONNECTED;
            this._dispatcher.clientId = null;
            this._cycleConnection();
          }
        },
      
        _deliverMessage: function(message) {
          if (!message.channel || message.data === undefined) return;
          this.info('Client ? calling listeners for ? with ?', this._dispatcher.clientId, message.channel, message.data);
          this._channels.distributeMessage(message);
        },
      
        _cycleConnection: function() {
          if (this._connectRequest) {
            this._connectRequest = null;
            this.info('Closed connection for ?', this._dispatcher.clientId);
          }
          var self = this;
          global.setTimeout(function() { self.connect() }, this._advice.interval);
        }
      });
      
      assign(Client.prototype, Deferrable);
      assign(Client.prototype, Publisher);
      assign(Client.prototype, Logging);
      assign(Client.prototype, Extensible);
      
      module.exports = Client;
      ����������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/protocol/dispatcher.js���������������������������������������������������������������0000664�0000000�0000000�00000012104�13711043772�0017557�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var Class     = require('../util/class'),
          URI       = require('../util/uri'),
          cookies   = require('../util/cookies'),
          assign    = require('../util/assign'),
          Logging   = require('../mixins/logging'),
          Publisher = require('../mixins/publisher'),
          Transport = require('../transport'),
          Scheduler = require('./scheduler');
      
      var Dispatcher = Class({ className: 'Dispatcher',
        MAX_REQUEST_SIZE: 2048,
        DEFAULT_RETRY:    5,
      
        UP:   1,
        DOWN: 2,
      
        initialize: function(client, endpoint, options) {
          this._client     = client;
          this.endpoint    = URI.parse(endpoint);
          this._alternates = options.endpoints || {};
      
          this.cookies      = cookies.CookieJar && new cookies.CookieJar();
          this._disabled    = [];
          this._envelopes   = {};
          this.headers      = {};
          this.retry        = options.retry || this.DEFAULT_RETRY;
          this._scheduler   = options.scheduler || Scheduler;
          this._state       = 0;
          this.transports   = {};
          this.wsExtensions = [];
      
          this.proxy = options.proxy || {};
          if (typeof this._proxy === 'string') this._proxy = { origin: this._proxy };
      
          var exts = options.websocketExtensions;
          if (exts) {
            exts = [].concat(exts);
            for (var i = 0, n = exts.length; i < n; i++)
              this.addWebsocketExtension(exts[i]);
          }
      
          this.tls = options.tls || {};
          this.tls.ca = this.tls.ca || options.ca;
      
          for (var type in this._alternates)
            this._alternates[type] = URI.parse(this._alternates[type]);
      
          this.maxRequestSize = this.MAX_REQUEST_SIZE;
        },
      
        endpointFor: function(connectionType) {
          return this._alternates[connectionType] || this.endpoint;
        },
      
        addWebsocketExtension: function(extension) {
          this.wsExtensions.push(extension);
        },
      
        disable: function(feature) {
          this._disabled.push(feature);
          Transport.disable(feature);
        },
      
        setHeader: function(name, value) {
          this.headers[name] = value;
        },
      
        close: function() {
          var transport = this._transport;
          delete this._transport;
          if (transport) transport.close();
        },
      
        getConnectionTypes: function() {
          return Transport.getConnectionTypes();
        },
      
        selectTransport: function(transportTypes) {
          Transport.get(this, transportTypes, this._disabled, function(transport) {
            this.debug('Selected ? transport for ?', transport.connectionType, transport.endpoint.href);
      
            if (transport === this._transport) return;
            if (this._transport) this._transport.close();
      
            this._transport = transport;
            this.connectionType = transport.connectionType;
          }, this);
        },
      
        sendMessage: function(message, timeout, options) {
          options = options || {};
      
          var id       = message.id,
              attempts = options.attempts,
              deadline = options.deadline && new Date().getTime() + (options.deadline * 1000),
              envelope = this._envelopes[id],
              scheduler;
      
          if (!envelope) {
            scheduler = new this._scheduler(message, { timeout: timeout, interval: this.retry, attempts: attempts, deadline: deadline });
            envelope  = this._envelopes[id] = { message: message, scheduler: scheduler };
          }
      
          this._sendEnvelope(envelope);
        },
      
        _sendEnvelope: function(envelope) {
          if (!this._transport) return;
          if (envelope.request || envelope.timer) return;
      
          var message   = envelope.message,
              scheduler = envelope.scheduler,
              self      = this;
      
          if (!scheduler.isDeliverable()) {
            scheduler.abort();
            delete this._envelopes[message.id];
            return;
          }
      
          envelope.timer = global.setTimeout(function() {
            self.handleError(message);
          }, scheduler.getTimeout() * 1000);
      
          scheduler.send();
          envelope.request = this._transport.sendMessage(message);
        },
      
        handleResponse: function(reply) {
          var envelope = this._envelopes[reply.id];
      
          if (reply.successful !== undefined && envelope) {
            envelope.scheduler.succeed();
            delete this._envelopes[reply.id];
            global.clearTimeout(envelope.timer);
          }
      
          this.trigger('message', reply);
      
          if (this._state === this.UP) return;
          this._state = this.UP;
          this._client.trigger('transport:up');
        },
      
        handleError: function(message, immediate) {
          var envelope = this._envelopes[message.id],
              request  = envelope && envelope.request,
              self     = this;
      
          if (!request) return;
      
          request.then(function(req) {
            if (req && req.abort) req.abort();
          });
      
          var scheduler = envelope.scheduler;
          scheduler.fail();
      
          global.clearTimeout(envelope.timer);
          envelope.request = envelope.timer = null;
      
          if (immediate) {
            this._sendEnvelope(envelope);
          } else {
            envelope.timer = global.setTimeout(function() {
              envelope.timer = null;
              self._sendEnvelope(envelope);
            }, scheduler.getInterval() * 1000);
          }
      
          if (this._state === this.DOWN) return;
          this._state = this.DOWN;
          this._client.trigger('transport:down');
        }
      });
      
      Dispatcher.create = function(client, endpoint, options) {
        return new Dispatcher(client, endpoint, options);
      };
      
      assign(Dispatcher.prototype, Publisher);
      assign(Dispatcher.prototype, Logging);
      
      module.exports = Dispatcher;
      ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/protocol/error.js��������������������������������������������������������������������0000664�0000000�0000000�00000003054�13711043772�0016566�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var Class   = require('../util/class'),
          Grammar = require('./grammar');
      
      var Error = Class({
        initialize: function(code, params, message) {
          this.code    = code;
          this.params  = Array.prototype.slice.call(params);
          this.message = message;
        },
      
        toString: function() {
          return this.code + ':' +
                 this.params.join(',') + ':' +
                 this.message;
        }
      });
      
      Error.parse = function(message) {
        message = message || '';
        if (!Grammar.ERROR.test(message)) return new Error(null, [], message);
      
        var parts   = message.split(':'),
            code    = parseInt(parts[0]),
            params  = parts[1].split(','),
            message = parts[2];
      
        return new Error(code, params, message);
      };
      
      // http://code.google.com/p/cometd/wiki/BayeuxCodes
      var errors = {
        versionMismatch:  [300, 'Version mismatch'],
        conntypeMismatch: [301, 'Connection types not supported'],
        extMismatch:      [302, 'Extension mismatch'],
        badRequest:       [400, 'Bad request'],
        clientUnknown:    [401, 'Unknown client'],
        parameterMissing: [402, 'Missing required parameter'],
        channelForbidden: [403, 'Forbidden channel'],
        channelUnknown:   [404, 'Unknown channel'],
        channelInvalid:   [405, 'Invalid channel'],
        extUnknown:       [406, 'Unknown extension'],
        publishFailed:    [407, 'Failed to publish'],
        serverError:      [500, 'Internal server error']
      };
      
      for (var name in errors)
        (function(name) {
          Error[name] = function() {
            return new Error(errors[name][0], arguments, errors[name][1]).toString();
          };
        })(name);
      
      module.exports = Error;
      ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/protocol/extensible.js���������������������������������������������������������������0000664�0000000�0000000�00000002474�13711043772�0017604�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var assign  = require('../util/assign'),
          Logging = require('../mixins/logging');
      
      var Extensible = {
        addExtension: function(extension) {
          this._extensions = this._extensions || [];
          this._extensions.push(extension);
          if (extension.added) extension.added(this);
        },
      
        removeExtension: function(extension) {
          if (!this._extensions) return;
          var i = this._extensions.length;
          while (i--) {
            if (this._extensions[i] !== extension) continue;
            this._extensions.splice(i,1);
            if (extension.removed) extension.removed(this);
          }
        },
      
        pipeThroughExtensions: function(stage, message, request, callback, context) {
          this.debug('Passing through ? extensions: ?', stage, message);
      
          if (!this._extensions) return callback.call(context, message);
          var extensions = this._extensions.slice();
      
          var pipe = function(message) {
            if (!message) return callback.call(context, message);
      
            var extension = extensions.shift();
            if (!extension) return callback.call(context, message);
      
            var fn = extension[stage];
            if (!fn) return pipe(message);
      
            if (fn.length >= 3) extension[stage](message, request, pipe);
            else                extension[stage](message, pipe);
          };
          pipe(message);
        }
      };
      
      assign(Extensible, Logging);
      
      module.exports = Extensible;
      ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/protocol/grammar.js������������������������������������������������������������������0000664�0000000�0000000�00000001244�13711043772�0017062�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      module.exports = {
        CHANNEL_NAME:     /^\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+(\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+)*$/,
        CHANNEL_PATTERN:  /^(\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+)*\/\*{1,2}$/,
        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])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*)$/,
        VERSION:          /^([0-9])+(\.(([a-z]|[A-Z])|[0-9])(((([a-z]|[A-Z])|[0-9])|\-|\_))*)*$/
      };
      ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/protocol/publication.js��������������������������������������������������������������0000664�0000000�0000000�00000000221�13711043772�0017737�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var Class      = require('../util/class'),
          Deferrable = require('../mixins/deferrable');
      
      module.exports = Class(Deferrable);
      �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/protocol/scheduler.js����������������������������������������������������������������0000664�0000000�0000000�00000001533�13711043772�0017413�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var assign = require('../util/assign');
      
      var Scheduler = function(message, options) {
        this.message  = message;
        this.options  = options;
        this.attempts = 0;
      };
      
      assign(Scheduler.prototype, {
        getTimeout: function() {
          return this.options.timeout;
        },
      
        getInterval: function() {
          return this.options.interval;
        },
      
        isDeliverable: function() {
          var attempts = this.options.attempts,
              made     = this.attempts,
              deadline = this.options.deadline,
              now      = new Date().getTime();
      
          if (attempts !== undefined && made >= attempts)
            return false;
      
          if (deadline !== undefined && now > deadline)
            return false;
      
          return true;
        },
      
        send: function() {
          this.attempts += 1;
        },
      
        succeed: function() {},
      
        fail: function() {},
      
        abort: function() {}
      });
      
      module.exports = Scheduler;
      ���������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/protocol/server.js�������������������������������������������������������������������0000664�0000000�0000000�00000026170�13711043772�0016747�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var Class      = require('../util/class'),
          array      = require('../util/array'),
          assign     = require('../util/assign'),
          constants  = require('../util/constants'),
          Logging    = require('../mixins/logging'),
          Engine     = require('../engines/proxy'),
          Channel    = require('./channel'),
          Error      = require('./error'),
          Extensible = require('./extensible'),
          Grammar    = require('./grammar'),
          Socket     = require('./socket');
      
      var Server = Class({ className: 'Server',
        initialize: function(options) {
          this._options  = options || {};
          var engineOpts = this._options.engine || {};
          engineOpts.timeout = this._options.timeout;
          this._engine   = Engine.get(engineOpts);
      
          this.info('Created new server: ?', this._options);
        },
      
        close: function() {
          return this._engine.close();
        },
      
        openSocket: function(clientId, socket, request) {
          if (!clientId || !socket) return;
          this._engine.openSocket(clientId, new Socket(this, socket, request));
        },
      
        closeSocket: function(clientId, close) {
          this._engine.flushConnection(clientId, close);
        },
      
        process: function(messages, request, callback, context) {
          var local = (request === null);
      
          messages = [].concat(messages);
          this.info('Processing messages: ? (local: ?)', messages, local);
      
          if (messages.length === 0) return callback.call(context, []);
          var processed = 0, responses = [], self = this;
      
          var gatherReplies = function(replies) {
            responses = responses.concat(replies);
            processed += 1;
            if (processed < messages.length) return;
      
            var n = responses.length;
            while (n--) {
              if (!responses[n]) responses.splice(n,1);
            }
            self.info('Returning replies: ?', responses);
            callback.call(context, responses);
          };
      
          var handleReply = function(replies) {
            var assigned = 0, expected = replies.length;
            if (expected === 0) gatherReplies(replies);
      
            for (var i = 0, n = replies.length; i < n; i++) {
              this.debug('Processing reply: ?', replies[i]);
              (function(index) {
                self.pipeThroughExtensions('outgoing', replies[index], request, function(message) {
                  replies[index] = message;
                  assigned += 1;
                  if (assigned === expected) gatherReplies(replies);
                });
              })(i);
            }
          };
      
          for (var i = 0, n = messages.length; i < n; i++) {
            this.pipeThroughExtensions('incoming', messages[i], request, function(pipedMessage) {
              this._handle(pipedMessage, local, handleReply, this);
            }, this);
          }
        },
      
        _makeResponse: function(message) {
          var response = {};
      
          if (message.id)       response.id       = message.id;
          if (message.clientId) response.clientId = message.clientId;
          if (message.channel)  response.channel  = message.channel;
          if (message.error)    response.error    = message.error;
      
          response.successful = !response.error;
          return response;
        },
      
        _handle: function(message, local, callback, context) {
          if (!message) return callback.call(context, []);
          this.info('Handling message: ? (local: ?)', message, local);
      
          var channelName = message.channel,
              error       = message.error,
              response;
      
          if (Channel.isMeta(channelName))
            return this._handleMeta(message, local, callback, context);
      
          if (!Grammar.CHANNEL_NAME.test(channelName))
            error = Error.channelInvalid(channelName);
      
          if (message.data === undefined)
            error = Error.parameterMissing('data');
      
          if (!error) this._engine.publish(message);
      
          response = this._makeResponse(message);
          if (error) response.error = error;
          response.successful = !response.error;
          callback.call(context, [response]);
        },
      
        _handleMeta: function(message, local, callback, context) {
          var method = this._methodFor(message),
              response;
      
          if (method === null) {
            response = this._makeResponse(message);
            response.error = Error.channelForbidden(message.channel);
            response.successful = false;
            return callback.call(context, [response]);
          }
      
          this[method](message, local, function(responses) {
            responses = [].concat(responses);
            for (var i = 0, n = responses.length; i < n; i++) this._advize(responses[i], message.connectionType);
            callback.call(context, responses);
          }, this);
        },
      
        _methodFor: function(message) {
          var channel = message.channel;
      
          if (channel === Channel.HANDSHAKE)   return 'handshake';
          if (channel === Channel.CONNECT)     return 'connect';
          if (channel === Channel.SUBSCRIBE)   return 'subscribe';
          if (channel === Channel.UNSUBSCRIBE) return 'unsubscribe';
          if (channel === Channel.DISCONNECT)  return 'disconnect';
      
          return null;
        },
      
        _advize: function(response, connectionType) {
          if (array.indexOf([Channel.HANDSHAKE, Channel.CONNECT], response.channel) < 0)
            return;
      
          var interval, timeout;
          if (connectionType === 'eventsource') {
            interval = Math.floor(this._engine.timeout * 1000);
            timeout  = 0;
          } else {
            interval = Math.floor(this._engine.interval * 1000);
            timeout  = Math.floor(this._engine.timeout * 1000);
          }
      
          response.advice = response.advice || {};
          if (response.error) {
            assign(response.advice, { reconnect:  'handshake' }, false);
          } else {
            assign(response.advice, {
              reconnect:  'retry',
              interval:   interval,
              timeout:    timeout
            }, false);
          }
        },
      
        // MUST contain  * version
        //               * supportedConnectionTypes
        // MAY contain   * minimumVersion
        //               * ext
        //               * id
        handshake: function(message, local, callback, context) {
          var response = this._makeResponse(message);
          response.version = constants.BAYEUX_VERSION;
      
          if (!message.version)
            response.error = Error.parameterMissing('version');
      
          var clientConns = message.supportedConnectionTypes,
              commonConns;
      
          response.supportedConnectionTypes = constants.CONNECTION_TYPES;
      
          if (clientConns) {
            commonConns = array.filter(clientConns, function(conn) {
              return array.indexOf(constants.CONNECTION_TYPES, conn) >= 0;
            });
            if (commonConns.length === 0)
              response.error = Error.conntypeMismatch(clientConns);
          } else {
            response.error = Error.parameterMissing('supportedConnectionTypes');
          }
      
          response.successful = !response.error;
          if (!response.successful) return callback.call(context, response);
      
          this._engine.createClient(function(clientId) {
            response.clientId = clientId;
            callback.call(context, response);
          }, this);
        },
      
        // MUST contain  * clientId
        //               * connectionType
        // MAY contain   * ext
        //               * id
        connect: function(message, local, callback, context) {
          var response       = this._makeResponse(message),
              clientId       = message.clientId,
              connectionType = message.connectionType;
      
          this._engine.clientExists(clientId, function(exists) {
            if (!exists)         response.error = Error.clientUnknown(clientId);
            if (!clientId)       response.error = Error.parameterMissing('clientId');
      
            if (array.indexOf(constants.CONNECTION_TYPES, connectionType) < 0)
              response.error = Error.conntypeMismatch(connectionType);
      
            if (!connectionType) response.error = Error.parameterMissing('connectionType');
      
            response.successful = !response.error;
      
            if (!response.successful) {
              delete response.clientId;
              return callback.call(context, response);
            }
      
            if (message.connectionType === 'eventsource') {
              message.advice = message.advice || {};
              message.advice.timeout = 0;
            }
            this._engine.connect(response.clientId, message.advice, function(events) {
              callback.call(context, [response].concat(events));
            });
          }, this);
        },
      
        // MUST contain  * clientId
        // MAY contain   * ext
        //               * id
        disconnect: function(message, local, callback, context) {
          var response = this._makeResponse(message),
              clientId = message.clientId;
      
          this._engine.clientExists(clientId, function(exists) {
            if (!exists)   response.error = Error.clientUnknown(clientId);
            if (!clientId) response.error = Error.parameterMissing('clientId');
      
            response.successful = !response.error;
            if (!response.successful) delete response.clientId;
      
            if (response.successful) this._engine.destroyClient(clientId);
            callback.call(context, response);
          }, this);
        },
      
        // MUST contain  * clientId
        //               * subscription
        // MAY contain   * ext
        //               * id
        subscribe: function(message, local, callback, context) {
          var response     = this._makeResponse(message),
              clientId     = message.clientId,
              subscription = message.subscription,
              channel;
      
          subscription = subscription ? [].concat(subscription) : [];
      
          this._engine.clientExists(clientId, function(exists) {
            if (!exists)               response.error = Error.clientUnknown(clientId);
            if (!clientId)             response.error = Error.parameterMissing('clientId');
            if (!message.subscription) response.error = Error.parameterMissing('subscription');
      
            response.subscription = message.subscription || [];
      
            for (var i = 0, n = subscription.length; i < n; i++) {
              channel = subscription[i];
      
              if (response.error) break;
              if (!local && !Channel.isSubscribable(channel)) response.error = Error.channelForbidden(channel);
              if (!Channel.isValid(channel))                  response.error = Error.channelInvalid(channel);
      
              if (response.error) break;
              this._engine.subscribe(clientId, channel);
            }
      
            response.successful = !response.error;
            callback.call(context, response);
          }, this);
        },
      
        // MUST contain  * clientId
        //               * subscription
        // MAY contain   * ext
        //               * id
        unsubscribe: function(message, local, callback, context) {
          var response     = this._makeResponse(message),
              clientId     = message.clientId,
              subscription = message.subscription,
              channel;
      
          subscription = subscription ? [].concat(subscription) : [];
      
          this._engine.clientExists(clientId, function(exists) {
            if (!exists)               response.error = Error.clientUnknown(clientId);
            if (!clientId)             response.error = Error.parameterMissing('clientId');
            if (!message.subscription) response.error = Error.parameterMissing('subscription');
      
            response.subscription = message.subscription || [];
      
            for (var i = 0, n = subscription.length; i < n; i++) {
              channel = subscription[i];
      
              if (response.error) break;
              if (!local && !Channel.isSubscribable(channel)) response.error = Error.channelForbidden(channel);
              if (!Channel.isValid(channel))                  response.error = Error.channelInvalid(channel);
      
              if (response.error) break;
              this._engine.unsubscribe(clientId, channel);
            }
      
            response.successful = !response.error;
            callback.call(context, response);
          }, this);
        }
      });
      
      Server.create = function(options) {
        return new Server(options);
      };
      
      assign(Server.prototype, Logging);
      assign(Server.prototype, Extensible);
      
      module.exports = Server;
      ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/protocol/socket.js�������������������������������������������������������������������0000664�0000000�0000000�00000001110�13711043772�0016714�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var Class  = require('../util/class'),
          toJSON = require('../util/to_json');
      
      module.exports = Class({
        initialize: function(server, socket, request) {
          this._server  = server;
          this._socket  = socket;
          this._request = request;
        },
      
        send: function(message) {
          this._server.pipeThroughExtensions('outgoing', message, this._request, function(pipedMessage) {
            if (this._socket)
              this._socket.send(toJSON([pipedMessage]));
          }, this);
        },
      
        close: function() {
          if (this._socket) this._socket.close();
          delete this._socket;
        }
      });
      ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/protocol/subscription.js�������������������������������������������������������������0000664�0000000�0000000�00000002034�13711043772�0020156�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var Class      = require('../util/class'),
          assign     = require('../util/assign'),
          Deferrable = require('../mixins/deferrable');
      
      var Subscription = Class({
        initialize: function(client, channels, callback, context) {
          this._client    = client;
          this._channels  = channels;
          this._callback  = callback;
          this._context   = context;
          this._cancelled = false;
        },
      
        withChannel: function(callback, context) {
          this._withChannel = [callback, context];
          return this;
        },
      
        apply: function(context, args) {
          var message = args[0];
      
          if (this._callback)
            this._callback.call(this._context, message.data);
      
          if (this._withChannel)
            this._withChannel[0].call(this._withChannel[1], message.channel, message.data);
        },
      
        cancel: function() {
          if (this._cancelled) return;
          this._client.unsubscribe(this._channels, this);
          this._cancelled = true;
        },
      
        unsubscribe: function() {
          this.cancel();
        }
      });
      
      assign(Subscription.prototype, Deferrable);
      
      module.exports = Subscription;
      ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/transport/���������������������������������������������������������������������������0000775�0000000�0000000�00000000000�13711043772�0015270�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/transport/browser_transports.js������������������������������������������������������0000664�0000000�0000000�00000000603�13711043772�0021607�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var Transport = require('./transport');
      
      Transport.register('websocket', require('./web_socket'));
      Transport.register('eventsource', require('./event_source'));
      Transport.register('long-polling', require('./xhr'));
      Transport.register('cross-origin-long-polling', require('./cors'));
      Transport.register('callback-polling', require('./jsonp'));
      
      module.exports = Transport;
      �����������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/transport/cors.js��������������������������������������������������������������������0000664�0000000�0000000�00000004253�13711043772�0016600�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var Class     = require('../util/class'),
          Set       = require('../util/set'),
          URI       = require('../util/uri'),
          assign    = require('../util/assign'),
          toJSON    = require('../util/to_json'),
          Transport = require('./transport');
      
      var CORS = assign(Class(Transport, {
        encode: function(messages) {
          return 'message=' + encodeURIComponent(toJSON(messages));
        },
      
        request: function(messages) {
          var xhrClass = global.XDomainRequest ? XDomainRequest : XMLHttpRequest,
              xhr      = new xhrClass(),
              id       = ++CORS._id,
              headers  = this._dispatcher.headers,
              self     = this,
              key;
      
          xhr.open('POST', this.endpoint.href, true);
          xhr.withCredentials = true;
      
          if (xhr.setRequestHeader) {
            xhr.setRequestHeader('Pragma', 'no-cache');
            for (key in headers) {
              if (!headers.hasOwnProperty(key)) continue;
              xhr.setRequestHeader(key, headers[key]);
            }
          }
      
          var cleanUp = function() {
            if (!xhr) return false;
            CORS._pending.remove(id);
            xhr.onload = xhr.onerror = xhr.ontimeout = xhr.onprogress = null;
            xhr = null;
          };
      
          xhr.onload = function() {
            var replies;
            try { replies = JSON.parse(xhr.responseText) } catch (error) {}
      
            cleanUp();
      
            if (replies)
              self._receive(replies);
            else
              self._handleError(messages);
          };
      
          xhr.onerror = xhr.ontimeout = function() {
            cleanUp();
            self._handleError(messages);
          };
      
          xhr.onprogress = function() {};
      
          if (xhrClass === global.XDomainRequest)
            CORS._pending.add({ id: id, xhr: xhr });
      
          xhr.send(this.encode(messages));
          return xhr;
        }
      }), {
        _id:      0,
        _pending: new Set(),
      
        isUsable: function(dispatcher, endpoint, callback, context) {
          if (URI.isSameOrigin(endpoint))
            return callback.call(context, false);
      
          if (global.XDomainRequest)
            return callback.call(context, endpoint.protocol === location.protocol);
      
          if (global.XMLHttpRequest) {
            var xhr = new XMLHttpRequest();
            return callback.call(context, xhr.withCredentials !== undefined);
          }
          return callback.call(context, false);
        }
      });
      
      module.exports = CORS;
      �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/transport/event_source.js������������������������������������������������������������0000664�0000000�0000000�00000005177�13711043772�0020341�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var Class      = require('../util/class'),
          URI        = require('../util/uri'),
          copyObject = require('../util/copy_object'),
          assign     = require('../util/assign'),
          Deferrable = require('../mixins/deferrable'),
          Transport  = require('./transport'),
          XHR        = require('./xhr');
      
      var EventSource = assign(Class(Transport, {
        initialize: function(dispatcher, endpoint) {
          Transport.prototype.initialize.call(this, dispatcher, endpoint);
          if (!global.EventSource) return this.setDeferredStatus('failed');
      
          this._xhr = new XHR(dispatcher, endpoint);
      
          endpoint = copyObject(endpoint);
          endpoint.pathname += '/' + dispatcher.clientId;
      
          var socket = new global.EventSource(URI.stringify(endpoint)),
              self   = this;
      
          socket.onopen = function() {
            self._everConnected = true;
            self.setDeferredStatus('succeeded');
          };
      
          socket.onerror = function() {
            if (self._everConnected) {
              self._handleError([]);
            } else {
              self.setDeferredStatus('failed');
              socket.close();
            }
          };
      
          socket.onmessage = function(event) {
            var replies;
            try { replies = JSON.parse(event.data) } catch (error) {}
      
            if (replies)
              self._receive(replies);
            else
              self._handleError([]);
          };
      
          this._socket = socket;
        },
      
        close: function() {
          if (!this._socket) return;
          this._socket.onopen = this._socket.onerror = this._socket.onmessage = null;
          this._socket.close();
          delete this._socket;
        },
      
        isUsable: function(callback, context) {
          this.callback(function() { callback.call(context, true) });
          this.errback(function() { callback.call(context, false) });
        },
      
        encode: function(messages) {
          return this._xhr.encode(messages);
        },
      
        request: function(messages) {
          return this._xhr.request(messages);
        }
      
      }), {
        isUsable: function(dispatcher, endpoint, callback, context) {
          var id = dispatcher.clientId;
          if (!id) return callback.call(context, false);
      
          XHR.isUsable(dispatcher, endpoint, function(usable) {
            if (!usable) return callback.call(context, false);
            this.create(dispatcher, endpoint).isUsable(callback, context);
          }, this);
        },
      
        create: function(dispatcher, endpoint) {
          var sockets = dispatcher.transports.eventsource = dispatcher.transports.eventsource || {},
              id      = dispatcher.clientId;
      
          var url = copyObject(endpoint);
          url.pathname += '/' + (id || '');
          url = URI.stringify(url);
      
          sockets[url] = sockets[url] || new this(dispatcher, endpoint);
          return sockets[url];
        }
      });
      
      assign(EventSource.prototype, Deferrable);
      
      module.exports = EventSource;
      �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/transport/jsonp.js�������������������������������������������������������������������0000664�0000000�0000000�00000003303�13711043772�0016756�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var Class      = require('../util/class'),
          URI        = require('../util/uri'),
          copyObject = require('../util/copy_object'),
          assign     = require('../util/assign'),
          toJSON     = require('../util/to_json'),
          Transport  = require('./transport');
      
      var JSONP = assign(Class(Transport, {
       encode: function(messages) {
          var url = copyObject(this.endpoint);
          url.query.message = toJSON(messages);
          url.query.jsonp   = '__jsonp' + JSONP._cbCount + '__';
          return URI.stringify(url);
        },
      
        request: function(messages) {
          var head         = document.getElementsByTagName('head')[0],
              script       = document.createElement('script'),
              callbackName = JSONP.getCallbackName(),
              endpoint     = copyObject(this.endpoint),
              self         = this;
      
          endpoint.query.message = toJSON(messages);
          endpoint.query.jsonp   = callbackName;
      
          var cleanup = function() {
            if (!global[callbackName]) return false;
            global[callbackName] = undefined;
            try { delete global[callbackName] } catch (error) {}
            script.parentNode.removeChild(script);
          };
      
          global[callbackName] = function(replies) {
            cleanup();
            self._receive(replies);
          };
      
          script.type = 'text/javascript';
          script.src  = URI.stringify(endpoint);
          head.appendChild(script);
      
          script.onerror = function() {
            cleanup();
            self._handleError(messages);
          };
      
          return { abort: cleanup };
        }
      }), {
        _cbCount: 0,
      
        getCallbackName: function() {
          this._cbCount += 1;
          return '__jsonp' + this._cbCount + '__';
        },
      
        isUsable: function(dispatcher, endpoint, callback, context) {
          callback.call(context, true);
        }
      });
      
      module.exports = JSONP;
      �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/transport/node_http.js���������������������������������������������������������������0000664�0000000�0000000�00000007472�13711043772�0017624�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var Buffer = require('safe-buffer').Buffer,
          http   = require('http'),
          https  = require('https'),
          tunnel = require('tunnel-agent');
      
      var Class     = require('../util/class'),
          URI       = require('../util/uri'),
          assign    = require('../util/assign'),
          toJSON    = require('../util/to_json'),
          Transport = require('./transport');
      
      var NodeHttp = assign(Class(Transport, { className: 'NodeHttp',
        SECURE_PROTOCOLS: ['https:', 'wss:'],
      
        initialize: function() {
          Transport.prototype.initialize.apply(this, arguments);
      
          this._endpointSecure = (this.SECURE_PROTOCOLS.indexOf(this.endpoint.protocol) >= 0);
          this._httpClient     = this._endpointSecure ? https : http;
      
          var proxy = this._proxy;
          if (!proxy.origin) return;
      
          this._proxyUri    = URI.parse(proxy.origin);
          this._proxySecure = (this.SECURE_PROTOCOLS.indexOf(this._proxyUri.protocol) >= 0);
      
          if (!this._endpointSecure) {
            this._httpClient = this._proxySecure ? https : http;
            return;
          }
      
          var options = assign({
            proxy: {
              host:       this._proxyUri.hostname,
              port:       this._proxyUri.port || this.DEFAULT_PORTS[this._proxyUri.protocol],
              proxyAuth:  this._proxyUri.auth,
              headers:    assign({ host: this.endpoint.host }, proxy.headers)
            }
          }, this._dispatcher.tls);
      
          if (this._proxySecure) {
            assign(options.proxy, proxy.tls);
            this._tunnel = tunnel.httpsOverHttps(options);
          } else {
            this._tunnel = tunnel.httpsOverHttp(options);
          }
        },
      
        encode: function(messages) {
          return toJSON(messages);
        },
      
        request: function(messages) {
          var content = Buffer.from(this.encode(messages), 'utf8'),
              params  = this._buildParams(content),
              request = this._httpClient.request(params),
              self    = this;
      
          request.on('response', function(response) {
            self._handleResponse(messages, response);
            self._storeCookies(response.headers['set-cookie']);
          });
      
          request.on('error', function(error) {
            self.error('HTTP error: ' + error.message);
            self._handleError(messages);
          });
      
          request.end(content);
          return request;
        },
      
        _buildParams: function(content) {
          var uri    = this.endpoint,
              proxy  = this._proxyUri,
              target = this._tunnel ? uri : (proxy || uri);
      
          var headers = {
            'Content-Length': content.length,
            'Content-Type':   'application/json',
            'Host':           uri.host
          };
      
          if (uri.auth)
            headers['Authorization'] = 'Basic ' + Buffer.from(uri.auth, 'utf8').toString('base64');
      
          var params = {
            method:   'POST',
            host:     target.hostname,
            port:     target.port || this.DEFAULT_PORTS[target.protocol],
            path:     uri.path,
            headers:  assign(headers, this._dispatcher.headers)
          };
      
          var cookie = this._getCookies();
          if (cookie !== '') params.headers['Cookie'] = cookie;
      
          if (this._tunnel) {
            params.agent = this._tunnel;
          } else if (this._endpointSecure) {
            assign(params, this._dispatcher.tls);
          } else if (proxy) {
            params.path = this.endpoint.href;
            assign(params, this._proxy.tls);
            if (proxy.auth)
              params.headers['Proxy-Authorization'] = Buffer.from(proxy.auth, 'utf8').toString('base64');
          }
      
          return params;
        },
      
        _handleResponse: function(messages, response) {
          var body = '',
              self = this;
      
          response.setEncoding('utf8');
          response.on('data', function(chunk) { body += chunk });
      
          response.on('end', function() {
            var replies;
            try { replies = JSON.parse(body) } catch (error) {}
      
            if (replies)
              self._receive(replies);
            else
              self._handleError(messages);
          });
        }
      
      }), {
        isUsable: function(dispatcher, endpoint, callback, context) {
          callback.call(context, URI.isURI(endpoint));
        }
      });
      
      module.exports = NodeHttp;
      ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/transport/node_local.js��������������������������������������������������������������0000664�0000000�0000000�00000001414�13711043772�0017725�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var asap       = require('asap'),
          Class      = require('../util/class'),
          URI        = require('../util/uri'),
          copyObject = require('../util/copy_object'),
          assign     = require('../util/assign'),
          Server     = require('../protocol/server'),
          Transport  = require('./transport');
      
      var NodeLocal = assign(Class(Transport, {
        batching: false,
      
        request: function(messages) {
          messages = copyObject(messages);
          var self = this;
      
          asap(function() {
            self.endpoint.process(messages, null, function(replies) {
              self._receive(copyObject(replies));
            });
          });
        }
      }), {
        isUsable: function(client, endpoint, callback, context) {
          callback.call(context, endpoint instanceof Server);
        }
      });
      
      module.exports = NodeLocal;
      ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/transport/node_transports.js���������������������������������������������������������0000664�0000000�0000000�00000000406�13711043772�0021052�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var Transport = require('./transport');
      
      Transport.register('in-process', require('./node_local'));
      Transport.register('websocket', require('./web_socket'));
      Transport.register('long-polling', require('./node_http'));
      
      module.exports = Transport;
      ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/transport/package.json���������������������������������������������������������������0000664�0000000�0000000�00000000110�13711043772�0017546�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{
        "main":     "node_transports",
        "browser":  "browser_transports"
      }
      ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/transport/transport.js���������������������������������������������������������������0000664�0000000�0000000�00000013745�13711043772�0017674�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var Class    = require('../util/class'),
          Cookie   = require('../util/cookies').Cookie,
          Promise  = require('../util/promise'),
          array    = require('../util/array'),
          assign   = require('../util/assign'),
          Logging  = require('../mixins/logging'),
          Timeouts = require('../mixins/timeouts'),
          Channel  = require('../protocol/channel');
      
      var Transport = assign(Class({ className: 'Transport',
        DEFAULT_PORTS: { 'http:': 80, 'https:': 443, 'ws:': 80, 'wss:': 443 },
        MAX_DELAY:     0,
      
        batching:  true,
      
        initialize: function(dispatcher, endpoint) {
          this._dispatcher = dispatcher;
          this.endpoint    = endpoint;
          this._outbox     = [];
          this._proxy      = assign({}, this._dispatcher.proxy);
      
          if (!this._proxy.origin)
            this._proxy.origin = this._findProxy();
        },
      
        close: function() {},
      
        encode: function(messages) {
          return '';
        },
      
        sendMessage: function(message) {
          this.debug('Client ? sending message to ?: ?',
                     this._dispatcher.clientId, this.endpoint.href, message);
      
          if (!this.batching) return Promise.resolve(this.request([message]));
      
          this._outbox.push(message);
          this._flushLargeBatch();
      
          if (message.channel === Channel.HANDSHAKE)
            return this._publish(0.01);
      
          if (message.channel === Channel.CONNECT)
            this._connectMessage = message;
      
          return this._publish(this.MAX_DELAY);
        },
      
        _makePromise: function() {
          var self = this;
      
          this._requestPromise = this._requestPromise || new Promise(function(resolve) {
            self._resolvePromise = resolve;
          });
        },
      
        _publish: function(delay) {
          this._makePromise();
      
          this.addTimeout('publish', delay, function() {
            this._flush();
            delete this._requestPromise;
          }, this);
      
          return this._requestPromise;
        },
      
        _flush: function() {
          this.removeTimeout('publish');
      
          if (this._outbox.length > 1 && this._connectMessage)
            this._connectMessage.advice = { timeout: 0 };
      
          this._resolvePromise(this.request(this._outbox));
      
          this._connectMessage = null;
          this._outbox = [];
        },
      
        _flushLargeBatch: function() {
          var string = this.encode(this._outbox);
          if (string.length < this._dispatcher.maxRequestSize) return;
          var last = this._outbox.pop();
      
          this._makePromise();
          this._flush();
      
          if (last) this._outbox.push(last);
        },
      
        _receive: function(replies) {
          if (!replies) return;
          replies = [].concat(replies);
      
          this.debug('Client ? received from ? via ?: ?',
                     this._dispatcher.clientId, this.endpoint.href, this.connectionType, replies);
      
          for (var i = 0, n = replies.length; i < n; i++)
            this._dispatcher.handleResponse(replies[i]);
        },
      
        _handleError: function(messages, immediate) {
          messages = [].concat(messages);
      
          this.debug('Client ? failed to send to ? via ?: ?',
                     this._dispatcher.clientId, this.endpoint.href, this.connectionType, messages);
      
          for (var i = 0, n = messages.length; i < n; i++)
            this._dispatcher.handleError(messages[i]);
        },
      
        _getCookies: function() {
          var cookies = this._dispatcher.cookies,
              url     = this.endpoint.href;
      
          if (!cookies) return '';
      
          return array.map(cookies.getCookiesSync(url), function(cookie) {
            return cookie.cookieString();
          }).join('; ');
        },
      
        _storeCookies: function(setCookie) {
          var cookies = this._dispatcher.cookies,
              url     = this.endpoint.href,
              cookie;
      
          if (!setCookie || !cookies) return;
          setCookie = [].concat(setCookie);
      
          for (var i = 0, n = setCookie.length; i < n; i++) {
            cookie = Cookie.parse(setCookie[i]);
            cookies.setCookieSync(cookie, url);
          }
        },
      
        _findProxy: function() {
          if (typeof process === 'undefined') return undefined;
      
          var protocol = this.endpoint.protocol;
          if (!protocol) return undefined;
      
          var name   = protocol.replace(/:$/, '').toLowerCase() + '_proxy',
              upcase = name.toUpperCase(),
              env    = process.env,
              keys, proxy;
      
          if (name === 'http_proxy' && env.REQUEST_METHOD) {
            keys = Object.keys(env).filter(function(k) { return /^http_proxy$/i.test(k) });
            if (keys.length === 1) {
              if (keys[0] === name && env[upcase] === undefined)
                proxy = env[name];
            } else if (keys.length > 1) {
              proxy = env[name];
            }
            proxy = proxy || env['CGI_' + upcase];
          } else {
            proxy = env[name] || env[upcase];
            if (proxy && !env[name])
              console.warn('The environment variable ' + upcase +
                           ' is discouraged. Use ' + name + '.');
          }
          return proxy;
        }
      
      }), {
        get: function(dispatcher, allowed, disabled, callback, context) {
          var endpoint = dispatcher.endpoint;
      
          array.asyncEach(this._transports, function(pair, resume) {
            var connType     = pair[0], klass = pair[1],
                connEndpoint = dispatcher.endpointFor(connType);
      
            if (array.indexOf(disabled, connType) >= 0)
              return resume();
      
            if (array.indexOf(allowed, connType) < 0) {
              klass.isUsable(dispatcher, connEndpoint, function() {});
              return resume();
            }
      
            klass.isUsable(dispatcher, connEndpoint, function(isUsable) {
              if (!isUsable) return resume();
              var transport = klass.hasOwnProperty('create') ? klass.create(dispatcher, connEndpoint) : new klass(dispatcher, connEndpoint);
              callback.call(context, transport);
            });
          }, function() {
            throw new Error('Could not find a usable connection type for ' + endpoint.href);
          });
        },
      
        register: function(type, klass) {
          this._transports.push([type, klass]);
          klass.prototype.connectionType = type;
        },
      
        getConnectionTypes: function() {
          return array.map(this._transports, function(t) { return t[0] });
        },
      
        disable: function(feature) {
          if (feature !== 'autodisconnect') return;
      
          for (var i = 0; i < this._transports.length; i++)
            this._transports[i][1]._unloaded = false;
        },
      
        _transports: []
      });
      
      assign(Transport.prototype, Logging);
      assign(Transport.prototype, Timeouts);
      
      module.exports = Transport;
      ���������������������������faye-1.4.0/src/transport/web_socket.js��������������������������������������������������������������0000664�0000000�0000000�00000010747�13711043772�0017764�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var Class      = require('../util/class'),
          Promise    = require('../util/promise'),
          Set        = require('../util/set'),
          URI        = require('../util/uri'),
          browser    = require('../util/browser'),
          copyObject = require('../util/copy_object'),
          assign     = require('../util/assign'),
          toJSON     = require('../util/to_json'),
          ws         = require('../util/websocket'),
          Deferrable = require('../mixins/deferrable'),
          Transport  = require('./transport');
      
      var WebSocket = assign(Class(Transport, {
        UNCONNECTED:  1,
        CONNECTING:   2,
        CONNECTED:    3,
      
        batching:     false,
      
        isUsable: function(callback, context) {
          this.callback(function() { callback.call(context, true) });
          this.errback(function() { callback.call(context, false) });
          this.connect();
        },
      
        request: function(messages) {
          this._pending = this._pending || new Set();
          for (var i = 0, n = messages.length; i < n; i++) this._pending.add(messages[i]);
      
          var self = this;
      
          var promise = new Promise(function(resolve, reject) {
            self.callback(function(socket) {
              if (!socket || socket.readyState !== 1) return;
              socket.send(toJSON(messages));
              resolve(socket);
            });
      
            self.connect();
          });
      
          return {
            abort: function() { promise.then(function(ws) { ws.close() }) }
          };
        },
      
        connect: function() {
          if (WebSocket._unloaded) return;
      
          this._state = this._state || this.UNCONNECTED;
          if (this._state !== this.UNCONNECTED) return;
          this._state = this.CONNECTING;
      
          var socket = this._createSocket();
          if (!socket) return this.setDeferredStatus('failed');
      
          var self = this;
      
          socket.onopen = function() {
            if (socket.headers) self._storeCookies(socket.headers['set-cookie']);
            self._socket = socket;
            self._state = self.CONNECTED;
            self._everConnected = true;
            self.setDeferredStatus('succeeded', socket);
          };
      
          var closed = false;
          socket.onclose = socket.onerror = function() {
            if (closed) return;
            closed = true;
      
            var wasConnected = (self._state === self.CONNECTED);
            socket.onopen = socket.onclose = socket.onerror = socket.onmessage = null;
      
            delete self._socket;
            self._state = self.UNCONNECTED;
      
            var pending = self._pending ? self._pending.toArray() : [];
            delete self._pending;
      
            if (wasConnected || self._everConnected) {
              self.setDeferredStatus('unknown');
              self._handleError(pending, wasConnected);
            } else {
              self.setDeferredStatus('failed');
            }
          };
      
          socket.onmessage = function(event) {
            var replies;
            try { replies = JSON.parse(event.data) } catch (error) {}
      
            if (!replies) return;
      
            replies = [].concat(replies);
      
            for (var i = 0, n = replies.length; i < n; i++) {
              if (replies[i].successful === undefined) continue;
              self._pending.remove(replies[i]);
            }
            self._receive(replies);
          };
        },
      
        close: function() {
          if (!this._socket) return;
          this._socket.close();
        },
      
        _createSocket: function() {
          var url        = WebSocket.getSocketUrl(this.endpoint),
              headers    = this._dispatcher.headers,
              extensions = this._dispatcher.wsExtensions,
              cookie     = this._getCookies(),
              tls        = this._dispatcher.tls,
              options    = { extensions: extensions, headers: headers, proxy: this._proxy, tls: tls };
      
          if (cookie !== '') options.headers['Cookie'] = cookie;
      
          try {
            return ws.create(url, [], options);
          } catch (e) {
            // catch CSP error to allow transport to fallback to next connType
          }
        }
      
      }), {
        PROTOCOLS: {
          'http:':  'ws:',
          'https:': 'wss:'
        },
      
        create: function(dispatcher, endpoint) {
          var sockets = dispatcher.transports.websocket = dispatcher.transports.websocket || {};
          sockets[endpoint.href] = sockets[endpoint.href] || new this(dispatcher, endpoint);
          return sockets[endpoint.href];
        },
      
        getSocketUrl: function(endpoint) {
          endpoint = copyObject(endpoint);
          endpoint.protocol = this.PROTOCOLS[endpoint.protocol];
          return URI.stringify(endpoint);
        },
      
        isUsable: function(dispatcher, endpoint, callback, context) {
          this.create(dispatcher, endpoint).isUsable(callback, context);
        }
      });
      
      assign(WebSocket.prototype, Deferrable);
      
      if (browser.Event && global.onbeforeunload !== undefined) {
        browser.Event.on(global, 'beforeunload', function() {
          if (WebSocket._unloaded === undefined)
            WebSocket._unloaded = true;
        });
      }
      
      module.exports = WebSocket;
      �������������������������faye-1.4.0/src/transport/xhr.js���������������������������������������������������������������������0000664�0000000�0000000�00000004365�13711043772�0016437�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var Class     = require('../util/class'),
          URI       = require('../util/uri'),
          browser   = require('../util/browser'),
          assign    = require('../util/assign'),
          toJSON    = require('../util/to_json'),
          Transport = require('./transport');
      
      var XHR = assign(Class(Transport, {
        encode: function(messages) {
          return toJSON(messages);
        },
      
        request: function(messages) {
          var href = this.endpoint.href,
              self = this,
              xhr;
      
          // Prefer XMLHttpRequest over ActiveXObject if they both exist
          if (global.XMLHttpRequest) {
            xhr = new XMLHttpRequest();
          } else if (global.ActiveXObject) {
            xhr = new ActiveXObject('Microsoft.XMLHTTP');
          } else {
            return this._handleError(messages);
          }
      
          xhr.open('POST', href, true);
          xhr.setRequestHeader('Content-Type', 'application/json');
          xhr.setRequestHeader('Pragma', 'no-cache');
          xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
      
          var headers = this._dispatcher.headers;
          for (var key in headers) {
            if (!headers.hasOwnProperty(key)) continue;
            xhr.setRequestHeader(key, headers[key]);
          }
      
          var abort = function() { xhr.abort() };
          if (global.onbeforeunload !== undefined)
            browser.Event.on(global, 'beforeunload', abort);
      
          xhr.onreadystatechange = function() {
            if (!xhr || xhr.readyState !== 4) return;
      
            var replies    = null,
                status     = xhr.status,
                text       = xhr.responseText,
                successful = (status >= 200 && status < 300) || status === 304 || status === 1223;
      
            if (global.onbeforeunload !== undefined)
              browser.Event.detach(global, 'beforeunload', abort);
      
            xhr.onreadystatechange = function() {};
            xhr = null;
      
            if (!successful) return self._handleError(messages);
      
            try {
              replies = JSON.parse(text);
            } catch (error) {}
      
            if (replies)
              self._receive(replies);
            else
              self._handleError(messages);
          };
      
          xhr.send(this.encode(messages));
          return xhr;
        }
      }), {
        isUsable: function(dispatcher, endpoint, callback, context) {
          var usable = (navigator.product === 'ReactNative')
                    || URI.isSameOrigin(endpoint);
      
          callback.call(context, usable);
        }
      });
      
      module.exports = XHR;
      ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/util/��������������������������������������������������������������������������������0000775�0000000�0000000�00000000000�13711043772�0014211�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/util/array.js������������������������������������������������������������������������0000664�0000000�0000000�00000003375�13711043772�0015675�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      module.exports = {
        commonElement: function(lista, listb) {
          for (var i = 0, n = lista.length; i < n; i++) {
            if (this.indexOf(listb, lista[i]) !== -1)
              return lista[i];
          }
          return null;
        },
      
        indexOf: function(list, needle) {
          if (list.indexOf) return list.indexOf(needle);
      
          for (var i = 0, n = list.length; i < n; i++) {
            if (list[i] === needle) return i;
          }
          return -1;
        },
      
        map: function(object, callback, context) {
          if (object.map) return object.map(callback, context);
          var result = [];
      
          if (object instanceof Array) {
            for (var i = 0, n = object.length; i < n; i++) {
              result.push(callback.call(context || null, object[i], i));
            }
          } else {
            for (var key in object) {
              if (!object.hasOwnProperty(key)) continue;
              result.push(callback.call(context || null, key, object[key]));
            }
          }
          return result;
        },
      
        filter: function(array, callback, context) {
          if (array.filter) return array.filter(callback, context);
          var result = [];
          for (var i = 0, n = array.length; i < n; i++) {
            if (callback.call(context || null, array[i], i))
              result.push(array[i]);
          }
          return result;
        },
      
        asyncEach: function(list, iterator, callback, context) {
          var n       = list.length,
              i       = -1,
              calls   = 0,
              looping = false;
      
          var iterate = function() {
            calls -= 1;
            i += 1;
            if (i === n) return callback && callback.call(context);
            iterator(list[i], resume);
          };
      
          var loop = function() {
            if (looping) return;
            looping = true;
            while (calls > 0) iterate();
            looping = false;
          };
      
          var resume = function() {
            calls += 1;
            loop();
          };
          resume();
        }
      };
      �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/util/assign.js�����������������������������������������������������������������������0000664�0000000�0000000�00000000522�13711043772�0016032�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var forEach = Array.prototype.forEach,
          hasOwn  = Object.prototype.hasOwnProperty;
      
      module.exports = function(target) {
        forEach.call(arguments, function(source, i) {
          if (i === 0) return;
      
          for (var key in source) {
            if (hasOwn.call(source, key)) target[key] = source[key];
          }
        });
      
        return target;
      };
      ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/util/browser/������������������������������������������������������������������������0000775�0000000�0000000�00000000000�13711043772�0015674�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/util/browser/event.js����������������������������������������������������������������0000664�0000000�0000000�00000002507�13711043772�0017357�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var Event = {
        _registry: [],
      
        on: function(element, eventName, callback, context) {
          var wrapped = function() { callback.call(context) };
      
          if (element.addEventListener)
            element.addEventListener(eventName, wrapped, false);
          else
            element.attachEvent('on' + eventName, wrapped);
      
          this._registry.push({
            _element:   element,
            _type:      eventName,
            _callback:  callback,
            _context:     context,
            _handler:   wrapped
          });
        },
      
        detach: function(element, eventName, callback, context) {
          var i = this._registry.length, register;
          while (i--) {
            register = this._registry[i];
      
            if ((element    && element    !== register._element)  ||
                (eventName  && eventName  !== register._type)     ||
                (callback   && callback   !== register._callback) ||
                (context    && context    !== register._context))
              continue;
      
            if (register._element.removeEventListener)
              register._element.removeEventListener(register._type, register._handler, false);
            else
              register._element.detachEvent('on' + register._type, register._handler);
      
            this._registry.splice(i,1);
            register = null;
          }
        }
      };
      
      if (global.onunload !== undefined)
        Event.on(global, 'unload', Event.detach, Event);
      
      module.exports = {
        Event: Event
      };
      �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/util/browser/node_shim.js������������������������������������������������������������0000664�0000000�0000000�00000000044�13711043772�0020175�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      module.exports = {};
      ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/util/browser/package.json������������������������������������������������������������0000664�0000000�0000000�00000000065�13711043772�0020163�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{
        "main":     "node_shim",
        "browser":  "event"
      }
      ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/util/class.js������������������������������������������������������������������������0000664�0000000�0000000�00000000731�13711043772�0015655�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var assign = require('./assign');
      
      module.exports = function(parent, methods) {
        if (typeof parent !== 'function') {
          methods = parent;
          parent  = Object;
        }
      
        var klass = function() {
          if (!this.initialize) return this;
          return this.initialize.apply(this, arguments) || this;
        };
      
        var bridge = function() {};
        bridge.prototype = parent.prototype;
      
        klass.prototype = new bridge();
        assign(klass.prototype, methods);
      
        return klass;
      };
      ���������������������������������������faye-1.4.0/src/util/constants.js��������������������������������������������������������������������0000664�0000000�0000000�00000000540�13711043772�0016562�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������module.exports = {
        VERSION:          '1.4.0',
      
        BAYEUX_VERSION:   '1.0',
        ID_LENGTH:        160,
        JSONP_CALLBACK:   'jsonpcallback',
        CONNECTION_TYPES: ['long-polling', 'cross-origin-long-polling', 'callback-polling', 'websocket', 'eventsource', 'in-process'],
      
        MANDATORY_CONNECTION_TYPES: ['long-polling', 'callback-polling', 'in-process']
      };
      ����������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/util/cookies/������������������������������������������������������������������������0000775�0000000�0000000�00000000000�13711043772�0015645�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/util/cookies/browser_cookies.js������������������������������������������������������0000664�0000000�0000000�00000000044�13711043772�0021400�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      module.exports = {};
      ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/util/cookies/node_cookies.js���������������������������������������������������������0000664�0000000�0000000�00000000071�13711043772�0020642�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      module.exports = require('tough-cookie');
      �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/util/cookies/package.json������������������������������������������������������������0000664�0000000�0000000�00000000102�13711043772�0020124�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{
        "main":     "node_cookies",
        "browser":  "browser_cookies"
      }
      ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/util/copy_object.js������������������������������������������������������������������0000664�0000000�0000000�00000000675�13711043772�0017057�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var copyObject = function(object) {
        var clone, i, key;
        if (object instanceof Array) {
          clone = [];
          i = object.length;
          while (i--) clone[i] = copyObject(object[i]);
          return clone;
        } else if (typeof object === 'object') {
          clone = (object === null) ? null : {};
          for (key in object) clone[key] = copyObject(object[key]);
          return clone;
        } else {
          return object;
        }
      };
      
      module.exports = copyObject;
      �������������������������������������������������������������������faye-1.4.0/src/util/event_emitter.js����������������������������������������������������������������0000664�0000000�0000000�00000012124�13711043772�0017421�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/*
      Copyright Joyent, Inc. and other Node contributors. All rights reserved.
      Permission is hereby granted, free of charge, to any person obtaining a copy of
      this software and associated documentation files (the "Software"), to deal in
      the Software without restriction, including without limitation the rights to
      use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
      of the Software, and to permit persons to whom the Software is furnished to do
      so, subject to the following conditions:
      
      The above copyright notice and this permission notice shall be included in all
      copies or substantial portions of the Software.
      
      THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
      IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
      AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
      OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
      SOFTWARE.
      */
      
      var isArray = typeof Array.isArray === 'function'
          ? Array.isArray
          : function (xs) {
              return Object.prototype.toString.call(xs) === '[object Array]'
          }
      ;
      function indexOf (xs, x) {
          if (xs.indexOf) return xs.indexOf(x);
          for (var i = 0; i < xs.length; i++) {
              if (x === xs[i]) return i;
          }
          return -1;
      }
      
      function EventEmitter() {}
      module.exports = EventEmitter;
      
      EventEmitter.prototype.emit = function(type) {
        // If there is no 'error' event listener then throw.
        if (type === 'error') {
          if (!this._events || !this._events.error ||
              (isArray(this._events.error) && !this._events.error.length))
          {
            if (arguments[1] instanceof Error) {
              throw arguments[1]; // Unhandled 'error' event
            } else {
              throw new Error("Uncaught, unspecified 'error' event.");
            }
            return false;
          }
        }
      
        if (!this._events) return false;
        var handler = this._events[type];
        if (!handler) return false;
      
        if (typeof handler == 'function') {
          switch (arguments.length) {
            // fast cases
            case 1:
              handler.call(this);
              break;
            case 2:
              handler.call(this, arguments[1]);
              break;
            case 3:
              handler.call(this, arguments[1], arguments[2]);
              break;
            // slower
            default:
              var args = Array.prototype.slice.call(arguments, 1);
              handler.apply(this, args);
          }
          return true;
      
        } else if (isArray(handler)) {
          var args = Array.prototype.slice.call(arguments, 1);
      
          var listeners = handler.slice();
          for (var i = 0, l = listeners.length; i < l; i++) {
            listeners[i].apply(this, args);
          }
          return true;
      
        } else {
          return false;
        }
      };
      
      // EventEmitter is defined in src/node_events.cc
      // EventEmitter.prototype.emit() is also defined there.
      EventEmitter.prototype.addListener = function(type, listener) {
        if ('function' !== typeof listener) {
          throw new Error('addListener only takes instances of Function');
        }
      
        if (!this._events) this._events = {};
      
        // To avoid recursion in the case that type == "newListeners"! Before
        // adding it to the listeners, first emit "newListeners".
        this.emit('newListener', type, listener);
      
        if (!this._events[type]) {
          // Optimize the case of one listener. Don't need the extra array object.
          this._events[type] = listener;
        } else if (isArray(this._events[type])) {
          // If we've already got an array, just append.
          this._events[type].push(listener);
        } else {
          // Adding the second element, need to change to array.
          this._events[type] = [this._events[type], listener];
        }
      
        return this;
      };
      
      EventEmitter.prototype.on = EventEmitter.prototype.addListener;
      
      EventEmitter.prototype.once = function(type, listener) {
        var self = this;
        self.on(type, function g() {
          self.removeListener(type, g);
          listener.apply(this, arguments);
        });
      
        return this;
      };
      
      EventEmitter.prototype.removeListener = function(type, listener) {
        if ('function' !== typeof listener) {
          throw new Error('removeListener only takes instances of Function');
        }
      
        // does not use listeners(), so no side effect of creating _events[type]
        if (!this._events || !this._events[type]) return this;
      
        var list = this._events[type];
      
        if (isArray(list)) {
          var i = indexOf(list, listener);
          if (i < 0) return this;
          list.splice(i, 1);
          if (list.length == 0)
            delete this._events[type];
        } else if (this._events[type] === listener) {
          delete this._events[type];
        }
      
        return this;
      };
      
      EventEmitter.prototype.removeAllListeners = function(type) {
        if (arguments.length === 0) {
          this._events = {};
          return this;
        }
      
        // does not use listeners(), so no side effect of creating _events[type]
        if (type && this._events && this._events[type]) this._events[type] = null;
        return this;
      };
      
      EventEmitter.prototype.listeners = function(type) {
        if (!this._events) this._events = {};
        if (!this._events[type]) this._events[type] = [];
        if (!isArray(this._events[type])) {
          this._events[type] = [this._events[type]];
        }
        return this._events[type];
      };
      ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/util/id_from_messages.js�������������������������������������������������������������0000664�0000000�0000000�00000000401�13711043772�0020050�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var array = require('./array');
      
      module.exports = function(messages) {
        var connect = array.filter([].concat(messages), function(message) {
          return message.channel === '/meta/connect';
        });
        return connect[0] && connect[0].clientId;
      };
      ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/util/namespace.js��������������������������������������������������������������������0000664�0000000�0000000�00000000707�13711043772�0016507�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var Class  = require('./class'),
          random = require('./random');
      
      module.exports = Class({
        initialize: function() {
          this._used = {};
        },
      
        exists: function(id) {
          return this._used.hasOwnProperty(id);
        },
      
        generate: function() {
          var name = random();
          while (this._used.hasOwnProperty(name))
            name = random();
          return this._used[name] = name;
        },
      
        release: function(id) {
          delete this._used[id];
        }
      });
      ���������������������������������������������������������faye-1.4.0/src/util/promise.js����������������������������������������������������������������������0000664�0000000�0000000�00000007205�13711043772�0016231�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var asap = require('asap');
      
      var PENDING   = -1,
          FULFILLED =  0,
          REJECTED  =  1;
      
      var Promise = function(task) {
        this._state = PENDING;
        this._value = null;
        this._defer = [];
      
        execute(this, task);
      };
      
      Promise.prototype.then = function(onFulfilled, onRejected) {
        var promise = new Promise();
      
        var deferred = {
          promise:     promise,
          onFulfilled: onFulfilled,
          onRejected:  onRejected
        };
      
        if (this._state === PENDING)
          this._defer.push(deferred);
        else
          propagate(this, deferred);
      
        return promise;
      };
      
      Promise.prototype['catch'] = function(onRejected) {
        return this.then(null, onRejected);
      };
      
      var execute = function(promise, task) {
        if (typeof task !== 'function') return;
      
        var calls = 0;
      
        var resolvePromise = function(value) {
          if (calls++ === 0) resolve(promise, value);
        };
      
        var rejectPromise = function(reason) {
          if (calls++ === 0) reject(promise, reason);
        };
      
        try {
          task(resolvePromise, rejectPromise);
        } catch (error) {
          rejectPromise(error);
        }
      };
      
      var propagate = function(promise, deferred) {
        var state   = promise._state,
            value   = promise._value,
            next    = deferred.promise,
            handler = [deferred.onFulfilled, deferred.onRejected][state],
            pass    = [resolve, reject][state];
      
        if (typeof handler !== 'function')
          return pass(next, value);
      
        asap(function() {
          try {
            resolve(next, handler(value));
          } catch (error) {
            reject(next, error);
          }
        });
      };
      
      var resolve = function(promise, value) {
        if (promise === value)
          return reject(promise, new TypeError('Recursive promise chain detected'));
      
        var then;
      
        try {
          then = getThen(value);
        } catch (error) {
          return reject(promise, error);
        }
      
        if (!then) return fulfill(promise, value);
      
        execute(promise, function(resolvePromise, rejectPromise) {
          then.call(value, resolvePromise, rejectPromise);
        });
      };
      
      var getThen = function(value) {
        var type = typeof value,
            then = (type === 'object' || type === 'function') && value && value.then;
      
        return (typeof then === 'function')
               ? then
               : null;
      };
      
      var fulfill = function(promise, value) {
        settle(promise, FULFILLED, value);
      };
      
      var reject = function(promise, reason) {
        settle(promise, REJECTED, reason);
      };
      
      var settle = function(promise, state, value) {
        var defer = promise._defer, i = 0;
      
        promise._state = state;
        promise._value = value;
        promise._defer = null;
      
        if (defer.length === 0) return;
        while (i < defer.length) propagate(promise, defer[i++]);
      };
      
      Promise.resolve = function(value) {
        try {
          if (getThen(value)) return value;
        } catch (error) {
          return Promise.reject(error);
        }
      
        return new Promise(function(resolve, reject) { resolve(value) });
      };
      
      Promise.reject = function(reason) {
        return new Promise(function(resolve, reject) { reject(reason) });
      };
      
      Promise.all = function(promises) {
        return new Promise(function(resolve, reject) {
          var list = [], n = promises.length, i;
      
          if (n === 0) return resolve(list);
      
          var push = function(promise, i) {
            Promise.resolve(promise).then(function(value) {
              list[i] = value;
              if (--n === 0) resolve(list);
            }, reject);
          };
      
          for (i = 0; i < n; i++) push(promises[i], i);
        });
      };
      
      Promise.race = function(promises) {
        return new Promise(function(resolve, reject) {
          for (var i = 0, n = promises.length; i < n; i++)
            Promise.resolve(promises[i]).then(resolve, reject);
        });
      };
      
      Promise.deferred = function() {
        var tuple = {};
      
        tuple.promise = new Promise(function(resolve, reject) {
          tuple.resolve = resolve;
          tuple.reject  = reject;
        });
        return tuple;
      };
      
      module.exports = Promise;
      �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/util/random.js�����������������������������������������������������������������������0000664�0000000�0000000�00000000554�13711043772�0016033�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var csprng    = require('csprng'),
          constants = require('./constants');
      
      module.exports = function(bitlength) {
        bitlength = bitlength || constants.ID_LENGTH;
        var maxLength = Math.ceil(bitlength * Math.log(2) / Math.log(36));
        var string = csprng(bitlength, 36);
        while (string.length < maxLength) string = '0' + string;
        return string;
      };
      ����������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/util/set.js��������������������������������������������������������������������������0000664�0000000�0000000�00000002064�13711043772�0015344�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var Class = require('./class');
      
      module.exports = Class({
        initialize: function() {
          this._index = {};
        },
      
        add: function(item) {
          var key = (item.id !== undefined) ? item.id : item;
          if (this._index.hasOwnProperty(key)) return false;
          this._index[key] = item;
          return true;
        },
      
        forEach: function(block, context) {
          for (var key in this._index) {
            if (this._index.hasOwnProperty(key))
              block.call(context, this._index[key]);
          }
        },
      
        isEmpty: function() {
          for (var key in this._index) {
            if (this._index.hasOwnProperty(key)) return false;
          }
          return true;
        },
      
        member: function(item) {
          for (var key in this._index) {
            if (this._index[key] === item) return true;
          }
          return false;
        },
      
        remove: function(item) {
          var key = (item.id !== undefined) ? item.id : item;
          var removed = this._index[key];
          delete this._index[key];
          return removed;
        },
      
        toArray: function() {
          var array = [];
          this.forEach(function(item) { array.push(item) });
          return array;
        }
      });
      ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/util/to_json.js����������������������������������������������������������������������0000664�0000000�0000000�00000000366�13711043772�0016227�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      // http://assanka.net/content/tech/2009/09/02/json2-js-vs-prototype/
      
      module.exports = function(object) {
        return JSON.stringify(object, function(key, value) {
          return (this[key] instanceof Array) ? this[key] : value;
        });
      };
      ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/util/uri.js��������������������������������������������������������������������������0000664�0000000�0000000�00000004571�13711043772�0015355�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      module.exports = {
        isURI: function(uri) {
          return uri && uri.protocol && uri.host && uri.path;
        },
      
        isSameOrigin: function(uri) {
          return uri.protocol === location.protocol &&
                 uri.hostname === location.hostname &&
                 uri.port     === location.port;
        },
      
        parse: function(url) {
          if (typeof url !== 'string') return url;
          var uri = {}, parts, query, pairs, i, n, data;
      
          var consume = function(name, pattern) {
            url = url.replace(pattern, function(match) {
              uri[name] = match;
              return '';
            });
            uri[name] = uri[name] || '';
          };
      
          consume('protocol', /^[a-z]+\:/i);
          consume('host',     /^\/\/[^\/\?#]+/);
      
          if (!/^\//.test(url) && !uri.host)
            url = location.pathname.replace(/[^\/]*$/, '') + url;
      
          consume('pathname', /^[^\?#]*/);
          consume('search',   /^\?[^#]*/);
          consume('hash',     /^#.*/);
      
          uri.protocol = uri.protocol || location.protocol;
      
          if (uri.host) {
            uri.host = uri.host.substr(2);
      
            if (/@/.test(uri.host)) {
              uri.auth = uri.host.split('@')[0];
              uri.host = uri.host.split('@')[1];
            }
            parts        = uri.host.match(/^\[([^\]]+)\]|^[^:]+/);
            uri.hostname = parts[1] || parts[0];
            uri.port     = (uri.host.match(/:(\d+)$/) || [])[1] || '';
          } else {
            uri.host     = location.host;
            uri.hostname = location.hostname;
            uri.port     = location.port;
          }
      
          uri.pathname = uri.pathname || '/';
          uri.path = uri.pathname + uri.search;
      
          query = uri.search.replace(/^\?/, '');
          pairs = query ? query.split('&') : [];
          data  = {};
      
          for (i = 0, n = pairs.length; i < n; i++) {
            parts = pairs[i].split('=');
            data[decodeURIComponent(parts[0] || '')] = decodeURIComponent(parts[1] || '');
          }
      
          uri.query = data;
      
          uri.href = this.stringify(uri);
          return uri;
        },
      
        stringify: function(uri) {
          var auth   = uri.auth ? uri.auth + '@' : '',
              string = uri.protocol + '//' + auth + uri.host;
      
          string += uri.pathname + this.queryString(uri.query) + (uri.hash || '');
      
          return string;
        },
      
        queryString: function(query) {
          var pairs = [];
          for (var key in query) {
            if (!query.hasOwnProperty(key)) continue;
            pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(query[key]));
          }
          if (pairs.length === 0) return '';
          return '?' + pairs.join('&');
        }
      };
      ���������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/util/validate_options.js�������������������������������������������������������������0000664�0000000�0000000�00000000345�13711043772�0020115�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var array = require('./array');
      
      module.exports = function(options, validKeys) {
        for (var key in options) {
          if (array.indexOf(validKeys, key) < 0)
            throw new Error('Unrecognized option: ' + key);
        }
      };
      �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/util/websocket/����������������������������������������������������������������������0000775�0000000�0000000�00000000000�13711043772�0016177�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/util/websocket/browser_websocket.js��������������������������������������������������0000664�0000000�0000000�00000000321�13711043772�0022262�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var WS = global.MozWebSocket || global.WebSocket;
      
      module.exports = {
        create: function(url, protocols, options) {
          if (typeof WS !== 'function') return null;
          return new WS(url);
        }
      };
      ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/util/websocket/node_websocket.js�����������������������������������������������������0000664�0000000�0000000�00000000257�13711043772�0021534�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'use strict';
      
      var WS = require('faye-websocket').Client;
      
      module.exports = {
        create: function(url, protocols, options) {
          return new WS(url, protocols, options);
        }
      };
      �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/src/util/websocket/package.json����������������������������������������������������������0000664�0000000�0000000�00000000106�13711043772�0020462�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{
        "main":     "node_websocket",
        "browser":  "browser_websocket"
      }
      ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������faye-1.4.0/webpack.config.js������������������������������������������������������������������������0000664�0000000�0000000�00000001110�13711043772�0015654�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������let mode = process.env.NODE_ENV || 'development',
          name;
      
      if (mode === 'production') {
        name = 'faye-browser-min';
      } else {
        name = 'faye-browser';
      }
      
      module.exports = {
        mode,
        devtool: 'source-map',
      
        entry: {
          ['build/client/' + name]: '.',
          'spec/browser_bundle': './spec/browser'
        },
      
        output: {
          path: __dirname,
          filename: '[name].js',
          library: 'Faye'
        },
      
        module: {
          rules: [
            {
              test: /\/spec\/.*\.js$/,
              loader: 'imports-loader?define=>false'
            }
          ],
      
          noParse: /jstest/
        },
      
        node: {
          process: false
        }
      };
      ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������