[Pkg-javascript-commits] [node-engine.io] 01/02: Imported Upstream version 1.5.1

Sebastiaan Couwenberg sebastic at moszumanska.debian.org
Sun Mar 29 00:18:30 UTC 2015


This is an automated email from the git hooks/post-receive script.

sebastic pushed a commit to branch master
in repository node-engine.io.

commit 2af68e403ea979817451f6056ae54515e2d8f240
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Sun Mar 29 00:23:15 2015 +0100

    Imported Upstream version 1.5.1
---
 .gitignore                        |    4 +
 .npmignore                        |    6 +
 .travis.yml                       |    7 +
 History.md                        |  408 ++++++++
 Makefile                          |   23 +
 README.md                         |  517 ++++++++++
 examples/latency/README.md        |   18 +
 examples/latency/index.html       |   14 +
 examples/latency/index.js         |   30 +
 examples/latency/package.json     |   11 +
 examples/latency/public/index.js  |   57 ++
 examples/latency/public/style.css |    5 +
 index.js                          |    4 +
 lib/engine.io.js                  |  126 +++
 lib/server.js                     |  391 ++++++++
 lib/socket.js                     |  393 ++++++++
 lib/transport.js                  |  114 +++
 lib/transports/index.js           |   36 +
 lib/transports/polling-jsonp.js   |  108 ++
 lib/transports/polling-xhr.js     |  101 ++
 lib/transports/polling.js         |  279 +++++
 lib/transports/websocket.js       |  110 ++
 package.json                      |   32 +
 test/common.js                    |   29 +
 test/engine.io.js                 |  230 +++++
 test/fixtures/ca.crt              |   35 +
 test/fixtures/ca.key              |   51 +
 test/fixtures/ca.key.org          |   54 +
 test/fixtures/client.crt          |   23 +
 test/fixtures/client.csr          |   12 +
 test/fixtures/client.key          |   15 +
 test/fixtures/client.key.orig     |   18 +
 test/fixtures/client.pfx          |  Bin 0 -> 1989 bytes
 test/fixtures/server.crt          |   23 +
 test/fixtures/server.csr          |   11 +
 test/fixtures/server.key          |   15 +
 test/fixtures/server.key.orig     |   18 +
 test/jsonp.js                     |  232 +++++
 test/server.js                    | 2010 +++++++++++++++++++++++++++++++++++++
 39 files changed, 5570 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100755
index 0000000..0a5dfa3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+node_modules
+npm-debug.log
+coverage.html
+lib-cov/
\ No newline at end of file
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..f09f630
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1,6 @@
+examples
+node_modules
+test
+npm-debug.log
+coverage.html
+.gitignore
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..04a6969
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,7 @@
+language: node_js
+node_js:
+  - "0.10"
+  - "0.8"
+
+notifications:
+  irc: "irc.freenode.org#socket.io"
diff --git a/History.md b/History.md
new file mode 100644
index 0000000..2a37b0b
--- /dev/null
+++ b/History.md
@@ -0,0 +1,408 @@
+
+1.5.1 / 2015-01-19
+==================
+
+ * no change on this release
+ * package: bump `engine.io-client`
+
+1.5.0 / 2015-01-18
+==================
+
+ * package: bump `engine.io-parser`
+ * polling: correctly abort the ongoing data request when closing [lpinca]
+ * add cert-related client tests [rase-]
+
+1.4.3 / 2014-11-21
+==================
+
+ * package: bump `ws` to fix fd leaks
+ * socket: flush the write buffer before closing the socket [nkzawa]
+ * polling: close the pending poll request when closing transport [nkzawa]
+
+1.4.2 / 2014-10-08
+==================
+
+ * add iframe onload handling to jsonp tests [rase-]
+
+1.4.1 / 2014-10-03
+==================
+
+ * socket: allow upgrades if the socket is still in closing state
+ * README: fix typo
+
+1.4.0 / 2014-09-03
+==================
+
+ * readme: fix formatting for goals numbering
+ * server: ref fix by @nicokaiser
+ * server: fix ws memory leak (fixes #268)
+ * cache remote address in handshake since it might be lost later.
+ * correct git ref
+ * update client to commit with bumped parser
+ * package: bump parser
+ * npmignore: ignore `.gitignore`
+ * package: bump `debug`
+ * package: bump `engine.io-parser` for memleak fix
+
+1.3.1 / 2014-06-19
+==================
+
+ * package: bump `engine.io-client`
+
+1.3.0 / 2014-06-13
+==================
+
+ * update example to use v1.2.2
+ * fixed newline parsing in jsonp
+ * make require('engine.io')() return a new Server instance [defunctzombie]
+ * add Server.attach method [defunctzombie]
+ * fix GH-211, set CORS headers when sending error message [mokesmokes]
+
+1.2.2 / 2014-05-30
+==================
+
+ * package: bump `engine.io-parser` for binary utf8 fix
+
+1.2.1 / 2014-05-22
+==================
+
+ * package: bump engine.io-client
+
+1.2.0 / 2014-05-18
+==================
+
+ * removed flashsocket, moving to userland
+
+1.1.1 / 2014-05-14
+==================
+
+ * test: reduce packet size
+ * package: bump parser
+
+1.1.0 / 2014-04-27
+==================
+
+ * socket: removed unneeded `clearTimeout` (fixes #250)
+ * made the request verification process async
+ * package: bump `engine.io-parser`
+ * use _query instead of query, fixes compat with restify
+ * added a maximum buffer size to received data from polling
+ * fixing looping array via for in to normal loop
+
+1.0.5 / 2014-03-18
+==================
+
+ * package: bump `engine.io-parser` and `engine.io-client`
+
+1.0.4 / 2014-03-14
+==================
+
+ * package: bump `engine.io-client`
+
+1.0.3 / 2014-03-12
+==================
+
+ * package: bump `engine.io-client`
+
+1.0.2 / 2014-03-12
+==================
+
+ * bump engine.io-client
+
+1.0.1 / 2014-03-06
+==================
+
+ * package: bump `engine.io-parser`
+ * transports: fix jshint warnings and style
+
+1.0.0 / 2014-03-06
+==================
+
+ * polling-xhr: added `OPTIONS` support, fixes CORS
+ * close() properly when triggered in connection handler
+ * fix DDOS vector by setting up too many intervals
+ * binary support
+
+0.9.0 / 2014-02-09
+==================
+
+ * Prevent errors with connections behind proxies without WS support
+   like Squid [nicklagrow, samaanghani, davidhcummings]
+ * Socket#request a simple property [mokesmokes]
+ * Changed `Socket`'s `upgrade` event to happen after upgrade [mokesmokes]
+ * Document `Socket#id` [mokesmokes]
+
+0.8.2 / 2014-01-18
+==================
+
+ * package: bump `engine.io-client`
+
+0.8.1 / 2014-01-17
+==================
+
+ * package: bump `engine.io-client`
+ * package: pin dev deps
+ * examples: fix port output
+ * fix latency example
+
+0.8.0 / 2014-01-05
+==================
+
+ * package: bump `engine.io-client` to `0.8.0`
+ * test: fix syntax, remove globals
+
+0.7.14 / 2014-01-01
+===================
+
+ * package: bump `engine.io-client` to `0.7.14`
+
+0.7.13 / 2013-12-20
+===================
+
+ * package: bump `engine.io-client`
+ * transports: added support for XSS filters on IE [guille, 3rd-eden]
+
+0.7.12 / 2013-11-11
+===================
+
+ * package: bump `engine.io-client`
+
+0.7.11 / 2013-11-06
+===================
+
+ * package: bump engine.io-client
+ * fix GH-198
+
+0.7.10 / 2013-10-28
+===================
+
+ * package: bump `engine.io-client`
+ * package: update "ws" to v0.4.31
+
+0.7.9 / 2013-08-30
+==================
+
+ * package: bump `engine.io-client`
+
+0.7.8 / 2013-08-30
+==================
+
+ * package: bump `engine.io-client`
+ * package: bump ws
+
+0.7.7 / 2013-08-30
+==================
+
+ * package: bump `engine.io-client`
+
+0.7.6 / 2013-08-30
+==================
+
+ * package: bump engine.io-client
+
+0.7.5 / 2013-08-30
+==================
+
+ * package: bump engine.io-client
+
+0.7.4 / 2013-08-25
+==================
+
+ * package: bump `engine.io-client`
+
+0.7.3 / 2013-08-23
+==================
+
+ * package: bump engine.io-client (noop)
+ * package: fix regresison in upgrade cause by ws update
+
+0.7.2 / 2013-08-23
+==================
+
+ * package: bump `engine.io-client` for `WebSocket` browser fix
+
+0.7.1 / 2013-08-23
+==================
+
+ * package: bump engine.io-client for ws fix
+
+0.7.0 / 2013-08-23
+==================
+
+ * package: bump engine.io-client
+ * updated example
+ * inline merge
+ * added support node version 0.10 to .travis.yml
+ * fixed respond to flash policy request test. Closes #184
+ * fixed upgrade with timeout test. Closes #185
+ * engine.io: don't use __proto__, closes #170
+
+0.6.3 / 2013-06-21
+==================
+
+  * package: bumped `engine.io-client` to `0.6.3`
+
+0.6.2 / 2013-06-15
+==================
+
+  * fix upgrade stalling edge case introduced with #174 fix
+  * remove unneeded client code related to iOS
+  * added test for `engine.io-client` `0.6.1`
+
+0.6.1 / 2013-06-06
+==================
+
+  * package: bumped `engine.io-client` to `0.6.1`
+
+0.6.0 / 2013-05-31
+==================
+
+  * socket: clear timer after sending one noop packet (fixes #174)
+  * clear all timers on socket close
+  * sending error on transport creation upon a bad request
+  * added test for client-side buffer cleanup
+  * changed flushComplete to flush
+  * ended support for node 0.6
+
+0.5.0 / 2013-03-16
+==================
+
+  * polling: implemented new parser
+  * test writeBuffer isn't cleared onError, removed 'closing' check in .flush()
+  * fixed bug89 and added tests: writeBuffer not flushed until nextTick
+
+0.4.3 / 2013-02-08
+==================
+
+  * package: bumped `engine.io-client` to `0.4.3`
+
+0.4.2 / 2013-02-08
+==================
+
+  * Only end upgrade socket connections if unhandled
+  * Fix websocket dependency
+  * Close socket if upgrade is received and socket.readyState != open
+
+0.4.1 / 2013-01-18
+==================
+
+  * package: bumped versions
+  * Fixed bugs in previous send callback fix and updated test cases
+  * Added a test case which makes the code before the send callback fix fail
+  * socket: emit `data` event (synonym with `message`)
+  * socket: added `Socket#write`
+  * engine.io: cleanup
+  * engine.io: deprecated `resource`
+  * `npm docs engine.io` works now
+
+0.3.10 / 2012-12-03
+===================
+
+  * package: bumped `engine.io-client` with `close` fixes
+  * add packetCreate event [jxck]
+  * add packet event to socket [jxck]
+  * transport: remove `Connection` headers and let node handle it
+  * server: send validation failure reason to clients
+  * engine: invoking as a function causes attach
+  * socket: reset `writeBuffer` before send
+
+0.3.9 / 2012-10-23
+==================
+
+  * package: bumped `engine.io-client`
+
+0.3.8 / 2012-10-23
+==================
+
+  * package: bumped engine.io-client
+  * examples: added first example
+
+0.3.7 / 2012-10-21
+==================
+
+  * package: bumped `engine.io-client`
+
+0.3.6 / 2012-10-21
+==================
+
+  [skipped]
+
+0.3.5 / 2012-10-14
+==================
+
+  * package: reverted last commit - we use the parser from the client
+
+0.3.4 / 2012-10-14
+==================
+
+  * package: `engine.io-client` moved to `devDependencies`
+  * socket: added missing jsdoc
+
+0.3.3 / 2012-10-10
+==================
+
+  * socket: fixed check interval clearing [joewalnes]
+  * transports: improved instrumentation
+
+0.3.2 / 2012-10-08
+==================
+
+  * socket: improve check interval for upgrade
+
+0.3.1 / 2012-10-08
+==================
+
+  * socket: faster upgrades (we perform a check immediately)
+  * server: don't assume sid is numeric
+
+0.3.0 / 2012-10-04
+==================
+
+  * socket: `writeBuffer` now gets sliced, and is recoverable after `close` [afshinm]
+  * server: expect ping from client and send interval with handshake [cadorn]
+  * polling-jsonp: prevent client breakage with utf8 whitespace
+  * socket: fix `flush` and `drain` events
+  * socket: add `send` callback [afshinm]
+  * transport: avoid unhandled error events for stale transports
+  * README: documentation improvements [EugenDueck]
+
+0.2.2 / 2012-08-26
+==================
+
+  * server: remove buffering for flash policy requests
+  * transport: avoid unhandled error events for stale transports (fixes #69)
+  * readme: documented `toString` behavior on `send` [EugenDueck]
+
+0.2.1 / 2012-08-13
+==================
+
+  * polling-xhr: skip Keep-Alive when it's implied [EugenDueck]
+  * polling-jsonp: skip Keep-Alive when it's implied [EugenDueck]
+  * README: added plugins list with engine.io-conflation
+  * socket: added flush/drain events (fixes #56)
+  * server: avoid passing websocket to non-websocket transports (fixes #24)
+
+0.2.0 / 2012-08-06
+==================
+
+  * Bumped client
+  * test: added closing connection test
+  * server: implemented stronger id generator with collision detection
+
+0.1.2 / 2012-08-02
+==================
+
+  * Fixed a jsonp bug in Nokia mobile phones and potentially other UAs.
+
+0.1.1 / 2012-08-01
+==================
+
+  * Fixed errors when a socket is closed while upgrade probe is happening.
+  * Improved WS error handling
+  * Replaced websocket.io with ws, now that it supports older drafts
+  * README fixes
+
+0.1.0 / 2012-07-03
+==================
+
+  * Initial release.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..5046ced
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,23 @@
+
+TESTS = test/*.js
+BENCHMARKS = $(shell find bench -type f ! -name 'runner.js')
+REPORTER = dot
+
+test:
+	@./node_modules/.bin/mocha \
+		--reporter $(REPORTER) \
+		--slow 500ms \
+		--bail \
+		--globals ___eio,document \
+		$(TESTS)
+
+test-cov: lib-cov
+	EIO_COV=1 $(MAKE) test REPORTER=html-cov > coverage.html
+
+lib-cov:
+	jscoverage --no-highlight lib lib-cov
+
+bench:
+	@node $(PROFILEFLAGS) bench/runner.js $(BENCHMARKS)
+
+.PHONY: test test-cov bench
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..3ad198a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,517 @@
+
+# Engine.IO: the realtime engine
+
+[![Build Status](https://secure.travis-ci.org/Automattic/engine.io.png)](http://travis-ci.org/Automattic/engine.io)
+[![NPM version](https://badge.fury.io/js/engine.io.png)](http://badge.fury.io/js/engine.io)
+
+`Engine.IO` is the implementation of transport-based
+cross-browser/cross-device bi-directional communication layer for
+[Socket.IO](http://github.com/learnboost/socket.io).
+
+## How to use
+
+### Server
+
+#### (A) Listening on a port
+
+```js
+var engine = require('engine.io');
+var server = engine.listen(80);
+
+server.on('connection', function(socket){
+  socket.send('utf 8 string');
+  socket.send(new Buffer([0, 1, 2, 3, 4, 5])); // binary data
+});
+```
+
+#### (B) Intercepting requests for a http.Server
+
+```js
+var engine = require('engine.io');
+var http = require('http').createServer().listen(3000);
+var server = engine.attach(http);
+
+server.on('connection', function (socket) {
+  socket.on('message', function(data){ });
+  socket.on('close', function(){ });
+});
+```
+
+#### (C) Passing in requests
+
+```js
+var engine = require('engine.io');
+var server = new engine.Server();
+
+server.on('connection', function(socket){
+  socket.send('hi');
+});
+
+// …
+httpServer.on('upgrade', function(req, socket, head){
+  server.handleUpgrade(req, socket, head);
+});
+httpServer.on('request', function(req, res){
+  server.handleRequest(req, res);
+});
+```
+
+### Client
+
+```html
+<script src="/path/to/engine.io.js"></script>
+<script>
+  var socket = new eio.Socket('ws://localhost/');
+  socket.on('open', function(){
+    socket.on('message', function(data){});
+    socket.on('close', function(){});
+  });
+</script>
+```
+
+For more information on the client refer to the
+[engine-client](http://github.com/learnboost/engine.io-client) repository.
+
+## What features does it have?
+
+- **Maximum reliability**. Connections are established even in the presence of:
+  - proxies and load balancers.
+  - personal firewall and antivirus software.
+  - for more information refer to **Goals** and **Architecture** sections
+- **Minimal client size** aided by:
+  - lazy loading of flash transports.
+  - lack of redundant transports.
+- **Scalable**
+  - load balancer friendly
+- **Future proof**
+- **100% Node.JS core style**
+  - No API sugar (left for higher level projects)
+  - Written in readable vanilla JavaScript
+
+## API
+
+### Server
+
+<hr><br>
+
+#### Top-level
+
+These are exposed by `require('engine.io')`:
+
+##### Events
+
+- `flush`
+    - Called when a socket buffer is being flushed.
+    - **Arguments**
+      - `Socket`: socket being flushed
+      - `Array`: write buffer
+- `drain`
+    - Called when a socket buffer is drained
+    - **Arguments**
+      - `Socket`: socket being flushed
+
+##### Properties
+
+- `protocol` _(Number)_: protocol revision number
+- `Server`: Server class constructor
+- `Socket`: Socket class constructor
+- `Transport` _(Function)_: transport constructor
+- `transports` _(Object)_: map of available transports
+
+##### Methods
+
+- `()`
+    - Returns a new `Server` instance. If the first argument is an `http.Server` then the
+      new `Server` instance will be attached to it. Otherwise, the arguments are passed
+      directly to the `Server` constructor.
+    - **Parameters**
+      - `http.Server`: optional, server to attach to.
+      - `Object`: optional, options object (see `Server#constructor` api docs below)
+
+  The following are identical ways to instantiate a server and then attach it.
+  ```js
+  var httpServer; // previously created with `http.createServer();` from node.js api.
+
+  // create a server first, and then attach
+  var eioServer = require('engine.io').Server();
+  eioServer.attach(httpServer);
+
+  // or call the module as a function to get `Server`
+  var eioServer = require('engine.io')();
+  eioServer.attach(httpServer);
+
+  // immediately attach
+  var eioServer = require('engine.io')(httpServer);
+  ```
+
+- `listen`
+    - Creates an `http.Server` which listens on the given port and attaches WS
+      to it. It returns `501 Not Implemented` for regular http requests.
+    - **Parameters**
+      - `Number`: port to listen on.
+      - `Object`: optional, options object
+      - `Function`: callback for `listen`.
+    - **Options**
+      - All options from `Server.attach` method, documented below.
+      - **Additionally** See Server `constructor` below for options you can pass for creating the new Server
+    - **Returns** `Server`
+- `attach`
+    - Captures `upgrade` requests for a `http.Server`. In other words, makes
+      a regular http.Server WebSocket-compatible.
+    - **Parameters**
+      - `http.Server`: server to attach to.
+      - `Object`: optional, options object
+    - **Options**
+      - All options from `Server.attach` method, documented below.
+      - **Additionally** See Server `constructor` below for options you can pass for creating the new Server
+    - **Returns** `Server` a new Server instance.
+
+<hr><br>
+
+#### Server
+
+The main server/manager. _Inherits from EventEmitter_.
+
+##### Events
+
+- `connection`
+    - Fired when a new connection is established.
+    - **Arguments**
+      - `Socket`: a Socket object
+
+##### Properties
+
+**Important**: if you plan to use Engine.IO in a scalable way, please
+keep in mind the properties below will only reflect the clients connected
+to a single process.
+
+- `clients` _(Object)_: hash of connected clients by id.
+- `clientsCount` _(Number)_: number of connected clients.
+
+##### Methods
+
+- **constructor**
+    - Initializes the server
+    - **Parameters**
+      - `Object`: optional, options object
+    - **Options**
+      - `pingTimeout` (`Number`): how many ms without a pong packet to
+        consider the connection closed (`60000`)
+      - `pingInterval` (`Number`): how many ms before sending a new ping
+        packet (`25000`)
+      - `maxHttpBufferSize` (`Number`): how many bytes or characters a message
+        can be when polling, before closing the session (to avoid DoS). Default
+        value is `10E7`.
+      - `allowRequest` (`Function`): A function that receives a given handshake
+        or upgrade request as its first parameter, and can decide whether to
+        continue or not. The second argument is a function that needs to be
+        called with the decided information: `fn(err, success)`, where
+        `success` is a boolean value where false means that the request is
+        rejected, and err is an error code.
+      - `transports` (`<Array> String`): transports to allow connections
+        to (`['polling', 'websocket']`)
+      - `allowUpgrades` (`Boolean`): whether to allow transport upgrades
+        (`true`)
+      - `cookie` (`String|Boolean`): name of the HTTP cookie that
+        contains the client sid to send as part of handshake response
+        headers. Set to `false` to not send one. (`io`)
+- `close`
+    - Closes all clients
+    - **Returns** `Server` for chaining
+- `handleRequest`
+    - Called internally when a `Engine` request is intercepted.
+    - **Parameters**
+      - `http.ServerRequest`: a node request object
+      - `http.ServerResponse`: a node response object
+    - **Returns** `Server` for chaining
+- `handleUpgrade`
+    - Called internally when a `Engine` ws upgrade is intercepted.
+    - **Parameters** (same as `upgrade` event)
+      - `http.ServerRequest`: a node request object
+      - `net.Stream`: TCP socket for the request
+      - `Buffer`: legacy tail bytes
+    - **Returns** `Server` for chaining
+- `attach`
+    - Attach this Server instance to an `http.Server`
+    - Captures `upgrade` requests for a `http.Server`. In other words, makes
+      a regular http.Server WebSocket-compatible.
+    - **Parameters**
+      - `http.Server`: server to attach to.
+      - `Object`: optional, options object
+    - **Options**
+      - `path` (`String`): name of the path to capture (`/engine.io`).
+      - `destroyUpgrade` (`Boolean`): destroy unhandled upgrade requests (`true`)
+      - `destroyUpgradeTimeout` (`Number`): milliseconds after which unhandled requests are ended (`1000`)
+
+<hr><br>
+
+#### Socket
+
+A representation of a client. _Inherits from EventEmitter_.
+
+##### Events
+
+- `close`
+    - Fired when the client is disconnected.
+    - **Arguments**
+      - `String`: reason for closing
+      - `Object`: description object (optional)
+- `message`
+    - Fired when the client sends a message.
+    - **Arguments**
+      - `String` or `Buffer`: Unicode string or Buffer with binary contents
+- `error`
+    - Fired when an error occurs.
+    - **Arguments**
+      - `Error`: error object
+- `flush`
+    - Called when the write buffer is being flushed.
+    - **Arguments**
+      - `Array`: write buffer
+- `drain`
+    - Called when the write buffer is drained
+- `packet`
+    - Called when a socket received a packet (`message`, `ping`)
+    - **Arguments**
+      - `type`: packet type
+      - `data`: packet data (if type is message)
+- `packetCreate`
+    - Called before a socket sends a packet (`message`, `pong`)
+    - **Arguments**
+      - `type`: packet type
+      - `data`: packet data (if type is message)
+
+##### Properties
+
+- `id` _(String)_: unique identifier
+- `server` _(Server)_: engine parent reference
+- `request` _(http.ServerRequest)_: request that originated the Socket
+- `upgraded` _(Boolean)_: whether the transport has been upgraded
+- `readyState` _(String)_: opening|open|closing|closed
+- `transport` _(Transport)_: transport reference
+
+##### Methods
+
+- `send`:
+    - Sends a message, performing `message = toString(arguments[0])` unless
+      sending binary data, which is sent as is.
+    - **Parameters**
+      - `String` | `Buffer` | `ArrayBuffer` | `ArrayBufferView`: a string or any object implementing `toString()`, with outgoing data, or a Buffer or ArrayBuffer with binary data. Also any ArrayBufferView can be sent as is.
+      - `Function`: optional, a callback executed when the message gets flushed out by the transport
+    - **Returns** `Socket` for chaining
+- `close`
+    - Disconnects the client
+    - **Returns** `Socket` for chaining
+
+### Client
+
+<hr><br>
+
+Exposed in the `eio` global namespace (in the browser), or by
+`require('engine.io-client')` (in Node.JS).
+
+For the client API refer to the 
+[engine-client](http://github.com/learnboost/engine.io-client) repository.
+
+## Debug / logging
+
+Engine.IO is powered by [debug](http://github.com/visionmedia/debug).
+In order to see all the debug output, run your app with the environment variable
+`DEBUG` including the desired scope.
+
+To see the output from all of Engine.IO's debugging scopes you can use:
+
+```
+DEBUG=engine* node myapp
+```
+
+## Transports
+
+- `polling`: XHR / JSONP polling transport.
+- `websocket`: WebSocket transport.
+
+## Plugins
+
+- [engine.io-conflation](https://github.com/EugenDueck/engine.io-conflation): Makes **conflation and aggregation** of messages straightforward.
+
+## Support
+
+The support channels for `engine.io` are the same as `socket.io`:
+  - irc.freenode.net **#socket.io**
+  - [Google Groups](http://groups.google.com/group/socket_io)
+  - [Website](http://socket.io)
+
+## Development
+
+To contribute patches, run tests or benchmarks, make sure to clone the
+repository:
+
+```
+git clone git://github.com/LearnBoost/engine.io.git
+```
+
+Then:
+
+```
+cd engine.io
+npm install
+```
+
+## Tests
+
+Tests run with `make test`. It runs the server tests that are aided by
+the usage of `engine.io-client`.
+
+Make sure `npm install` is run first.
+
+## Goals
+
+The main goal of `Engine` is ensuring the most reliable realtime communication.
+Unlike the previous Socket.IO core, it always establishes a long-polling
+connection first, then tries to upgrade to better transports that are "tested" on
+the side.
+
+During the lifetime of the Socket.IO projects, we've found countless drawbacks
+to relying on `HTML5 WebSocket` or `Flash Socket` as the first connection
+mechanisms.
+
+Both are clearly the _right way_ of establishing a bidirectional communication,
+with HTML5 WebSocket being the way of the future. However, to answer most business
+needs, alternative traditional HTTP 1.1 mechanisms are just as good as delivering
+the same solution.
+
+WebSocket based connections have two fundamental benefits:
+
+1. **Better server performance**
+  - _A: Load balancers_<br>
+      Load balancing a long polling connection poses a serious architectural nightmare
+      since requests can come from any number of open sockets by the user agent, but
+      they all need to be routed to the process and computer that owns the `Engine`
+      connection. This negatively impacts RAM and CPU usage.
+  - _B: Network traffic_<br>
+      WebSocket is designed around the premise that each message frame has to be 
+      surrounded by the least amount of data. In HTTP 1.1 transports, each message
+      frame is surrounded by HTTP headers and chunked encoding frames. If you try to
+      send the message _"Hello world"_ with xhr-polling, the message ultimately
+      becomes larger than if you were to send it with WebSocket.
+  - _C: Lightweight parser_<br>
+      As an effect of **B**, the server has to do a lot more work to parse the network
+      data and figure out the message when traditional HTTP requests are used
+      (as in long polling). This means that another advantage of WebSocket is
+      less server CPU usage.
+
+2. **Better user experience**
+
+    Due to the reasons stated in point **1**, the most important effect of being able
+    to establish a WebSocket connection is raw data transfer speed, which translates
+    in _some_ cases in better user experience.
+
+    Applications with heavy realtime interaction (such as games) will benefit greatly,
+    whereas applications like realtime chat (Gmail/Facebook), newsfeeds (Facebook) or
+    timelines (Twitter) will have negligible user experience improvements.
+
+Having said this, attempting to establish a WebSocket connection directly so far has
+proven problematic:
+
+1. **Proxies**<br>
+    Many corporate proxies block WebSocket traffic.
+
+2. **Personal firewall and antivirus software**<br>
+    As a result of our research, we've found that at least 3 personal security
+    applications block WebSocket traffic.
+
+3. **Cloud application platforms**<br>
+    Platforms like Heroku or No.de have had trouble keeping up with the fast-paced
+    nature of the evolution of the WebSocket protocol. Applications therefore end up
+    inevitably using long polling, but the seamless installation experience of 
+    Socket.IO we strive for (_"require() it and it just works"_) disappears.
+
+Some of these problems have solutions. In the case of proxies and personal programs,
+however, the solutions many times involve upgrading software. Experience has shown
+that relying on client software upgrades to deliver a business solution is
+fruitless: the very existence of this project has to do with a fragmented panorama
+of user agent distribution, with clients connecting with latest versions of the most
+modern user agents (Chrome, Firefox and Safari), but others with versions as low as
+IE 5.5.
+
+From the user perspective, an unsuccessful WebSocket connection can translate in
+up to at least 10 seconds of waiting for the realtime application to begin
+exchanging data. This **perceptively** hurts user experience.
+
+To summarize, **Engine** focuses on reliability and user experience first, marginal
+potential UX improvements and increased server performance second. `Engine` is the
+result of all the lessons learned with WebSocket in the wild.
+
+## Architecture
+
+The main premise of `Engine`, and the core of its existence, is the ability to
+swap transports on the fly. A connection starts as xhr-polling, but it can
+switch to WebSocket.
+
+The central problem this poses is: how do we switch transports without losing
+messages?
+
+`Engine` only switches from polling to another transport in between polling
+cycles. Since the server closes the connection after a certain timeout when
+there's no activity, and the polling transport implementation buffers messages
+in between connections, this ensures no message loss and optimal performance.
+
+Another benefit of this design is that we workaround almost all the limitations
+of **Flash Socket**, such as slow connection times, increased file size (we can
+safely lazy load it without hurting user experience), etc.
+
+## FAQ
+
+### Can I use engine without Socket.IO ?
+
+Absolutely. Although the recommended framework for building realtime applications
+is Socket.IO, since it provides fundamental features for real-world applications 
+such as multiplexing, reconnection support, etc.
+
+`Engine` is to Socket.IO what Connect is to Express. An essential piece for building
+realtime frameworks, but something you _probably_ won't be using for building
+actual applications.
+
+### Does the server serve the client?
+
+No. The main reason is that `Engine` is meant to be bundled with frameworks.
+Socket.IO includes `Engine`, therefore serving two clients is not necessary. If
+you use Socket.IO, including
+
+```html
+<script src="/socket.io/socket.io.js">
+```
+
+has you covered.
+
+### Can I implement `Engine` in other languages?
+
+Absolutely. The [engine.io-protocol](https://github.com/LearnBoost/engine.io-protocol)
+repository contains the most up to date description of the specification
+at all times, and the parser implementation in JavaScript.
+
+## License 
+
+(The MIT License)
+
+Copyright (c) 2014 Guillermo Rauch <guillermo at learnboost.com>
+
+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.
+
diff --git a/examples/latency/README.md b/examples/latency/README.md
new file mode 100644
index 0000000..d59a71f
--- /dev/null
+++ b/examples/latency/README.md
@@ -0,0 +1,18 @@
+
+# eio-latency
+
+## Running
+
+First, execute:
+
+```
+$ npm install
+```
+
+Then execute the server:
+
+```
+$ node index
+```
+
+And point your browser to `localhost:3000` (if PORT is set in your environment, then it will use that instead).
diff --git a/examples/latency/index.html b/examples/latency/index.html
new file mode 100644
index 0000000..a89d95d
--- /dev/null
+++ b/examples/latency/index.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<html>
+  <head>
+    <title>EIO Latency</title>
+    <link rel="stylesheet" href="/style.css" />
+  </head>
+  <body>
+    <h1>EIO Latency <span id="latency"></span></h1>
+    <h2 id="transport">(connecting)</h2>
+    <canvas id="chart" height="200"></canvas>
+
+    <script src="/index.js"></script>
+  </body>
+</html>
diff --git a/examples/latency/index.js b/examples/latency/index.js
new file mode 100644
index 0000000..bcaae71
--- /dev/null
+++ b/examples/latency/index.js
@@ -0,0 +1,30 @@
+
+/**
+ * Module dependencies.
+ */
+
+var express = require('express')
+  , app = express()
+  , server = require('http').createServer(app)
+  , enchilada = require('enchilada')
+  , io = require('engine.io').attach(server);
+
+app.use(enchilada({
+  src: __dirname + '/public',
+  debug: true
+}));
+app.use(express.static(__dirname + '/public'));
+app.get('/', function(req, res, next){
+  res.sendfile('index.html');
+});
+
+io.on('connection', function(socket){
+  socket.on('message', function(v){
+    socket.send('pong');
+  });
+});
+
+var port = process.env.PORT || 3000;
+server.listen(port, function(){
+  console.log('\033[96mlistening on localhost:' + port + ' \033[39m');
+});
diff --git a/examples/latency/package.json b/examples/latency/package.json
new file mode 100644
index 0000000..948b7fc
--- /dev/null
+++ b/examples/latency/package.json
@@ -0,0 +1,11 @@
+{
+  "name": "eio-latency",
+  "version": "0.1.0",
+  "dependencies": {
+    "express": "3.3.5",
+    "enchilada": "0.7.1",
+    "engine.io": "1.2.2",
+    "engine.io-client": "1.2.2",
+    "smoothie": "1.19.0"
+  }
+}
diff --git a/examples/latency/public/index.js b/examples/latency/public/index.js
new file mode 100644
index 0000000..5251218
--- /dev/null
+++ b/examples/latency/public/index.js
@@ -0,0 +1,57 @@
+
+/**
+ * Module dependencies.
+ */
+
+var SmoothieChart = require("smoothie").SmoothieChart
+  , TimeSeries = require("smoothie").TimeSeries
+  , eio = require("engine.io-client");
+
+
+// helper
+
+function $(id){ return document.getElementById(id); }
+
+// chart
+
+var smoothie;
+var time;
+
+function render(){
+  if (smoothie) smoothie.stop();
+  $('chart').width = document.body.clientWidth;
+  smoothie = new SmoothieChart();
+  smoothie.streamTo($('chart'), 1000);
+  time = new TimeSeries();
+  smoothie.addTimeSeries(time, {
+    strokeStyle: 'rgb(255, 0, 0)',
+    fillStyle: 'rgba(255, 0, 0, 0.4)',
+    lineWidth: 2
+  });
+}
+
+// socket
+var socket = new eio.Socket();
+var last;
+function send(){
+  last = new Date;
+  socket.send('ping');
+  $('transport').innerHTML = socket.transport.name;
+}
+socket.on('open', function(){
+  if ($('chart').getContext) {
+    render();
+    window.onresize = render;
+  }
+  send();
+});
+socket.on('close', function(){
+  if (smoothie) smoothie.stop();
+  $('transport').innerHTML = '(disconnected)';
+});
+socket.on('message', function(){
+  var latency = new Date - last;
+  $('latency').innerHTML = latency + 'ms';
+  if (time) time.append(+new Date, latency);
+  setTimeout(send, 100);
+});
diff --git a/examples/latency/public/style.css b/examples/latency/public/style.css
new file mode 100644
index 0000000..ab61214
--- /dev/null
+++ b/examples/latency/public/style.css
@@ -0,0 +1,5 @@
+
+body { margin: 0; padding: 0; font-family: Helvetica Neue; }
+h1 { margin: 100px 100px 10px; }
+h2 { color: #999; margin: 0 100px 30px; font-weight: normal; }
+#latency { color: red; }
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..ebb4e10
--- /dev/null
+++ b/index.js
@@ -0,0 +1,4 @@
+
+module.exports = process.env.EIO_COV
+  ? require('./lib-cov/engine.io')
+  : require('./lib/engine.io');
diff --git a/lib/engine.io.js b/lib/engine.io.js
new file mode 100644
index 0000000..2d1cf9e
--- /dev/null
+++ b/lib/engine.io.js
@@ -0,0 +1,126 @@
+/**
+ * Module dependencies.
+ */
+
+var http = require('http');
+
+/**
+ * Invoking the library as a function delegates to attach if the first argument
+ * is an `http.Server`.
+ *
+ * If there are no arguments or the first argument is an options object, then
+ * a new Server instance is returned.
+ *
+ * @param {http.Server} server (if specified, will be attached to by the new Server instance)
+ * @param {Object} options
+ * @return {Server} engine server
+ * @api public
+ */
+
+exports = module.exports = function() {
+  // backwards compatible use as `.attach`
+  // if first argument is an http server
+  if (arguments.length && arguments[0] instanceof http.Server) {
+    return attach.apply(this, arguments);
+  }
+
+  // if first argument is not an http server, then just make a regular eio server
+  return exports.Server.apply(null, arguments);
+};
+
+/**
+ * Protocol revision number.
+ *
+ * @api public
+ */
+
+exports.protocol = 1;
+
+/**
+ * Expose Server constructor.
+ *
+ * @api public
+ */
+
+exports.Server = require('./server');
+
+/**
+ * Expose Server constructor.
+ *
+ * @api public
+ */
+
+exports.Socket = require('./socket');
+
+/**
+ * Expose Transport constructor.
+ *
+ * @api public
+ */
+
+exports.Transport = require('./transport');
+
+/**
+ * Expose mutable list of available transports.
+ *
+ * @api public
+ */
+
+exports.transports = require('./transports');
+
+/**
+ * Exports parser.
+ *
+ * @api public
+ */
+
+exports.parser = require('engine.io-parser');
+
+/**
+ * Creates an http.Server exclusively used for WS upgrades.
+ *
+ * @param {Number} port
+ * @param {Function} callback
+ * @param {Object} options
+ * @return {Server} websocket.io server
+ * @api public
+ */
+
+exports.listen = listen;
+
+function listen(port, options, fn) {
+  if ('function' == typeof options) {
+    fn = options;
+    options = {};
+  }
+
+  var server = http.createServer(function (req, res) {
+    res.writeHead(501);
+    res.end('Not Implemented');
+  });
+
+  server.listen(port, fn);
+
+  // create engine server
+  var engine = exports.attach(server, options);
+  engine.httpServer = server;
+
+  return engine;
+};
+
+/**
+ * Captures upgrade requests for a http.Server.
+ *
+ * @param {http.Server} server
+ * @param {Object} options
+ * @return {Server} engine server
+ * @api public
+ */
+
+exports.attach = attach;
+
+function attach(server, options) {
+  var engine = new exports.Server(options);
+  engine.attach(server, options);
+  return engine;
+};
diff --git a/lib/server.js b/lib/server.js
new file mode 100644
index 0000000..b2a0b7f
--- /dev/null
+++ b/lib/server.js
@@ -0,0 +1,391 @@
+
+/**
+ * Module dependencies.
+ */
+
+var qs = require('querystring')
+  , parse = require('url').parse
+  , readFileSync = require('fs').readFileSync
+  , crypto = require('crypto')
+  , base64id = require('base64id')
+  , transports = require('./transports')
+  , EventEmitter = require('events').EventEmitter
+  , Socket = require('./socket')
+  , WebSocketServer = require('ws').Server
+  , debug = require('debug')('engine');
+
+/**
+ * Module exports.
+ */
+
+module.exports = Server;
+
+/**
+ * Server constructor.
+ *
+ * @param {Object} options
+ * @api public
+ */
+
+function Server(opts){
+  if (!(this instanceof Server)) {
+    return new Server(opts);
+  }
+
+  this.clients = {};
+  this.clientsCount = 0;
+
+  opts = opts || {};
+  this.pingTimeout = opts.pingTimeout || 60000;
+  this.pingInterval = opts.pingInterval || 25000;
+  this.upgradeTimeout = opts.upgradeTimeout || 10000;
+  this.maxHttpBufferSize = opts.maxHttpBufferSize || 10E7;
+  this.transports = opts.transports || Object.keys(transports);
+  this.allowUpgrades = false !== opts.allowUpgrades;
+  this.allowRequest = opts.allowRequest;
+  this.cookie = false !== opts.cookie ? (opts.cookie || 'io') : false;
+
+  // initialize websocket server
+  if (~this.transports.indexOf('websocket')) {
+    this.ws = new WebSocketServer({ noServer: true, clientTracking: false });
+  }
+}
+
+/**
+ * Protocol errors mappings.
+ */
+
+Server.errors = {
+  UNKNOWN_TRANSPORT: 0,
+  UNKNOWN_SID: 1,
+  BAD_HANDSHAKE_METHOD: 2,
+  BAD_REQUEST: 3
+};
+
+Server.errorMessages = {
+  0: 'Transport unknown',
+  1: 'Session ID unknown',
+  2: 'Bad handshake method',
+  3: 'Bad request'
+};
+
+/**
+ * Inherits from EventEmitter.
+ */
+
+Server.prototype.__proto__ = EventEmitter.prototype;
+
+/**
+ * Hash of open clients.
+ *
+ * @api public
+ */
+
+Server.prototype.clients;
+
+/**
+ * Returns a list of available transports for upgrade given a certain transport.
+ *
+ * @return {Array}
+ * @api public
+ */
+
+Server.prototype.upgrades = function(transport){
+  if (!this.allowUpgrades) return [];
+  return transports[transport].upgradesTo || [];
+};
+
+/**
+ * Verifies a request.
+ *
+ * @param {http.ServerRequest}
+ * @return {Boolean} whether the request is valid
+ * @api private
+ */
+
+Server.prototype.verify = function(req, upgrade, fn){
+  // transport check
+  var transport = req._query.transport;
+  if (!~this.transports.indexOf(transport)) {
+    debug('unknown transport "%s"', transport);
+    return fn(Server.errors.UNKNOWN_TRANSPORT, false);
+  }
+
+  // sid check
+  var sid = req._query.sid;
+  if (sid) {
+    if (!this.clients.hasOwnProperty(sid))
+      return fn(Server.errors.UNKNOWN_SID, false);
+    if (!upgrade && this.clients[sid].transport.name !== transport) {
+      debug('bad request: unexpected transport without upgrade');
+      return fn(Server.errors.BAD_REQUEST, false);
+    }
+  } else {
+    // handshake is GET only
+    if ('GET' != req.method) return fn(Server.errors.BAD_HANDSHAKE_METHOD, false);
+    if (!this.allowRequest) return fn(null, true);
+    return this.allowRequest(req, fn);
+  }
+
+  fn(null, true);
+};
+
+/**
+ * Prepares a request by processing the query string.
+ *
+ * @api private
+ */
+
+Server.prototype.prepare = function(req){
+  // try to leverage pre-existing `req._query` (e.g: from connect)
+  if (!req._query) {
+    req._query = ~req.url.indexOf('?') ? qs.parse(parse(req.url).query) : {};
+  }
+};
+
+/**
+ * Closes all clients.
+ *
+ * @api public
+ */
+
+Server.prototype.close = function(){
+  debug('closing all open clients');
+  for (var i in this.clients) {
+    this.clients[i].close();
+  }
+  return this;
+};
+
+/**
+ * Handles an Engine.IO HTTP request.
+ *
+ * @param {http.ServerRequest} request
+ * @param {http.ServerResponse|http.OutgoingMessage} response
+ * @api public
+ */
+
+Server.prototype.handleRequest = function(req, res){
+  debug('handling "%s" http request "%s"', req.method, req.url);
+  this.prepare(req);
+  req.res = res;
+
+  var self = this;
+  this.verify(req, false, function(err, success) {
+    if (!success) {
+      sendErrorMessage(req, res, err);
+      return;
+    }
+
+    if (req._query.sid) {
+      debug('setting new request for existing client');
+      self.clients[req._query.sid].transport.onRequest(req);
+    } else {
+      self.handshake(req._query.transport, req);
+    }
+  });
+};
+
+/**
+ * Sends an Engine.IO Error Message
+ *
+ * @param {http.ServerResponse} response
+ * @param {code} error code
+ * @api private
+ */
+
+ function sendErrorMessage(req, res, code) {
+    var headers = { 'Content-Type': 'application/json' };
+
+    if (req.headers.origin) {
+      headers['Access-Control-Allow-Credentials'] = 'true';
+      headers['Access-Control-Allow-Origin'] = req.headers.origin;
+    } else {
+      headers['Access-Control-Allow-Origin'] = '*';
+    }
+    res.writeHead(400, headers);
+    res.end(JSON.stringify({
+      code: code,
+      message: Server.errorMessages[code]
+    }));
+ }
+
+/**
+ * Handshakes a new client.
+ *
+ * @param {String} transport name
+ * @param {Object} request object
+ * @api private
+ */
+
+Server.prototype.handshake = function(transport, req){
+  var id = base64id.generateId();
+
+  debug('handshaking client "%s"', id);
+
+  var transportName = transport;
+  try {
+    var transport = new transports[transport](req);
+    if ('polling' == transportName) {
+      transport.maxHttpBufferSize = this.maxHttpBufferSize;
+    }
+
+    if (req._query && req._query.b64) {
+      transport.supportsBinary = false;
+    } else {
+      transport.supportsBinary = true;
+    }
+  }
+  catch (e) {
+    sendErrorMessage(req, req.res, Server.errors.BAD_REQUEST);
+    return;
+  }
+  var socket = new Socket(id, this, transport, req);
+  var self = this;
+
+  if (false !== this.cookie) {
+    transport.on('headers', function(headers){
+      headers['Set-Cookie'] = self.cookie + '=' + id;
+    });
+  }
+
+  transport.onRequest(req);
+
+  this.clients[id] = socket;
+  this.clientsCount++;
+
+  socket.once('close', function(){
+    delete self.clients[id];
+    self.clientsCount--;
+  });
+
+  this.emit('connection', socket);
+};
+
+/**
+ * Handles an Engine.IO HTTP Upgrade.
+ *
+ * @api public
+ */
+
+Server.prototype.handleUpgrade = function(req, socket, upgradeHead){
+  this.prepare(req);
+
+  var self = this;
+  this.verify(req, true, function(err, success) {
+    if (!success) {
+      socket.end();
+      return;
+    }
+
+    var head = new Buffer(upgradeHead.length);
+    upgradeHead.copy(head);
+    upgradeHead = null;
+
+    // delegate to ws
+    self.ws.handleUpgrade(req, socket, head, function(conn){
+      self.onWebSocket(req, conn);
+    });
+  });
+};
+
+/**
+ * Called upon a ws.io connection.
+ *
+ * @param {ws.Socket} websocket
+ * @api private
+ */
+
+Server.prototype.onWebSocket = function(req, socket){
+  if (!transports[req._query.transport].prototype.handlesUpgrades) {
+    debug('transport doesnt handle upgraded requests');
+    socket.close();
+    return;
+  }
+
+  // get client id
+  var id = req._query.sid;
+
+  // keep a reference to the ws.Socket
+  req.websocket = socket;
+
+  if (id) {
+    if (!this.clients[id]) {
+      debug('upgrade attempt for closed client');
+      socket.close();
+    } else if (this.clients[id].upgraded) {
+      debug('transport had already been upgraded');
+      socket.close();
+    } else {
+      debug('upgrading existing transport');
+      var transport = new transports[req._query.transport](req);
+      if (req._query && req._query.b64) {
+        transport.supportsBinary = false;
+      } else {
+        transport.supportsBinary = true;
+      }
+      this.clients[id].maybeUpgrade(transport);
+    }
+  } else {
+    this.handshake(req._query.transport, req);
+  }
+};
+
+/**
+ * Captures upgrade requests for a http.Server.
+ *
+ * @param {http.Server} server
+ * @param {Object} options
+ * @api public
+ */
+
+Server.prototype.attach = function(server, options){
+  var self = this;
+  var options = options || {};
+  var path = (options.path || '/engine.io').replace(/\/$/, '');
+
+  var destroyUpgrade = (options.destroyUpgrade !== undefined) ? options.destroyUpgrade : true;
+  var destroyUpgradeTimeout = options.destroyUpgradeTimeout || 1000;
+
+  // normalize path
+  path += '/';
+
+  function check (req) {
+    return path == req.url.substr(0, path.length);
+  }
+
+  // cache and clean up listeners
+  var listeners = server.listeners('request').slice(0);
+  server.removeAllListeners('request');
+  server.on('close', self.close.bind(self));
+
+  // add request handler
+  server.on('request', function(req, res){
+    if (check(req)) {
+      debug('intercepting request for path "%s"', path);
+      self.handleRequest(req, res);
+    } else {
+      for (var i = 0, l = listeners.length; i < l; i++) {
+        listeners[i].call(server, req, res);
+      }
+    }
+  });
+
+  if(~self.transports.indexOf('websocket')) {
+    server.on('upgrade', function (req, socket, head) {
+      if (check(req)) {
+        self.handleUpgrade(req, socket, head);
+      } else if (false !== options.destroyUpgrade) {
+        // default node behavior is to disconnect when no handlers
+        // but by adding a handler, we prevent that
+        // and if no eio thing handles the upgrade
+        // then the socket needs to die!
+        setTimeout(function() {
+           if (socket.writable && socket.bytesWritten <= 0) {
+             return socket.end();
+           }
+        }, options.destroyUpgradeTimeout);
+      }
+    });
+  }
+};
diff --git a/lib/socket.js b/lib/socket.js
new file mode 100644
index 0000000..08b2b8b
--- /dev/null
+++ b/lib/socket.js
@@ -0,0 +1,393 @@
+/**
+ * Module dependencies.
+ */
+
+var EventEmitter = require('events').EventEmitter;
+var debug = require('debug')('engine:socket');
+
+/**
+ * Module exports.
+ */
+
+module.exports = Socket;
+
+/**
+ * Client class (abstract).
+ *
+ * @api private
+ */
+
+function Socket (id, server, transport, req) {
+  this.id = id;
+  this.server = server;
+  this.upgraded = false;
+  this.readyState = 'opening';
+  this.writeBuffer = [];
+  this.packetsFn = [];
+  this.sentCallbackFn = [];
+  this.request = req;
+
+  // Cache IP since it might not be in the req later
+  this.remoteAddress = req.connection.remoteAddress;
+
+  this.checkIntervalTimer = null;
+  this.upgradeTimeoutTimer = null;
+  this.pingTimeoutTimer = null;
+
+  this.setTransport(transport);
+  this.onOpen();
+}
+
+/**
+ * Inherits from EventEmitter.
+ */
+
+Socket.prototype.__proto__ = EventEmitter.prototype;
+
+/**
+ * Called upon transport considered open.
+ *
+ * @api private
+ */
+
+Socket.prototype.onOpen = function () {
+  this.readyState = 'open';
+
+  // sends an `open` packet
+  this.transport.sid = this.id;
+  this.sendPacket('open', JSON.stringify({
+      sid: this.id
+    , upgrades: this.getAvailableUpgrades()
+    , pingInterval: this.server.pingInterval
+    , pingTimeout: this.server.pingTimeout
+  }));
+
+  this.emit('open');
+  this.setPingTimeout();
+};
+
+/**
+ * Called upon transport packet.
+ *
+ * @param {Object} packet
+ * @api private
+ */
+
+Socket.prototype.onPacket = function (packet) {
+  if ('open' == this.readyState) {
+    // export packet event
+    debug('packet');
+    this.emit('packet', packet);
+
+    // Reset ping timeout on any packet, incoming data is a good sign of
+    // other side's liveness
+    this.setPingTimeout();
+
+    switch (packet.type) {
+
+      case 'ping':
+        debug('got ping');
+        this.sendPacket('pong');
+        this.emit('heartbeat');
+        break;
+
+      case 'error':
+        this.onClose('parse error');
+        break;
+
+      case 'message':
+        this.emit('data', packet.data);
+        this.emit('message', packet.data);
+        break;
+    }
+  } else {
+    debug('packet received with closed socket');
+  }
+};
+
+/**
+ * Called upon transport error.
+ *
+ * @param {Error} error object
+ * @api private
+ */
+
+Socket.prototype.onError = function (err) {
+  debug('transport error');
+  this.onClose('transport error', err);
+};
+
+/**
+ * Sets and resets ping timeout timer based on client pings.
+ *
+ * @api private
+ */
+
+Socket.prototype.setPingTimeout = function () {
+  var self = this;
+  clearTimeout(self.pingTimeoutTimer);
+  self.pingTimeoutTimer = setTimeout(function () {
+    self.onClose('ping timeout');
+  }, self.server.pingInterval + self.server.pingTimeout);
+};
+
+/**
+ * Attaches handlers for the given transport.
+ *
+ * @param {Transport} transport
+ * @api private
+ */
+
+Socket.prototype.setTransport = function (transport) {
+  this.transport = transport;
+  this.transport.once('error', this.onError.bind(this));
+  this.transport.on('packet', this.onPacket.bind(this));
+  this.transport.on('drain', this.flush.bind(this));
+  this.transport.once('close', this.onClose.bind(this, 'transport close'));
+  //this function will manage packet events (also message callbacks)
+  this.setupSendCallback();
+};
+
+/**
+ * Upgrades socket to the given transport
+ *
+ * @param {Transport} transport
+ * @api private
+ */
+
+Socket.prototype.maybeUpgrade = function (transport) {
+  debug('might upgrade socket transport from "%s" to "%s"'
+    , this.transport.name, transport.name);
+
+  var self = this;
+
+  // set transport upgrade timer
+  self.upgradeTimeoutTimer = setTimeout(function () {
+    debug('client did not complete upgrade - closing transport');
+    clearInterval(self.checkIntervalTimer);
+    self.checkIntervalTimer = null;
+    if ('open' == transport.readyState) {
+      transport.close();
+    }
+  }, this.server.upgradeTimeout);
+
+  function onPacket(packet){
+    if ('ping' == packet.type && 'probe' == packet.data) {
+      transport.send([{ type: 'pong', data: 'probe' }]);
+      clearInterval(self.checkIntervalTimer);
+      self.checkIntervalTimer = setInterval(check, 100);
+    } else if ('upgrade' == packet.type && self.readyState != 'closed') {
+      debug('got upgrade packet - upgrading');
+      self.upgraded = true;
+      self.clearTransport();
+      self.setTransport(transport);
+      self.emit('upgrade', transport);
+      self.setPingTimeout();
+      self.flush();
+      clearInterval(self.checkIntervalTimer);
+      self.checkIntervalTimer = null;
+      clearTimeout(self.upgradeTimeoutTimer);
+      transport.removeListener('packet', onPacket);
+      if (self.readyState == 'closing') {
+        transport.close(function () {
+          self.onClose('forced close');
+        });
+      }
+    } else {
+      transport.close();
+    }
+  }
+
+  // we force a polling cycle to ensure a fast upgrade
+  function check(){
+    if ('polling' == self.transport.name && self.transport.writable) {
+      debug('writing a noop packet to polling for fast upgrade');
+      self.transport.send([{ type: 'noop' }]);
+    }
+  }
+
+  transport.on('packet', onPacket);
+};
+
+/**
+ * Clears listeners and timers associated with current transport.
+ *
+ * @api private
+ */
+
+Socket.prototype.clearTransport = function () {
+  // silence further transport errors and prevent uncaught exceptions
+  this.transport.on('error', function(){
+    debug('error triggered by discarded transport');
+  });
+  clearTimeout(this.pingTimeoutTimer);
+};
+
+/**
+ * Called upon transport considered closed.
+ * Possible reasons: `ping timeout`, `client error`, `parse error`,
+ * `transport error`, `server close`, `transport close`
+ */
+
+Socket.prototype.onClose = function (reason, description) {
+  if ('closed' != this.readyState) {
+    clearTimeout(this.pingTimeoutTimer);
+    clearInterval(this.checkIntervalTimer);
+    this.checkIntervalTimer = null;
+    clearTimeout(this.upgradeTimeoutTimer);
+    var self = this;
+    // clean writeBuffer in next tick, so developers can still
+    // grab the writeBuffer on 'close' event
+    process.nextTick(function() {
+      self.writeBuffer = [];
+    });
+    this.packetsFn = [];
+    this.sentCallbackFn = [];
+    this.clearTransport();
+    this.readyState = 'closed';
+    this.emit('close', reason, description);
+  }
+};
+
+/**
+ * Setup and manage send callback
+ *
+ * @api private
+ */
+
+Socket.prototype.setupSendCallback = function () {
+  var self = this;
+  //the message was sent successfully, execute the callback
+  this.transport.on('drain', function() {
+    if (self.sentCallbackFn.length > 0) {
+      var seqFn = self.sentCallbackFn.splice(0,1)[0];
+      if ('function' == typeof seqFn) {
+        debug('executing send callback');
+        seqFn(self.transport);
+      } else if (Array.isArray(seqFn)) {
+        debug('executing batch send callback');
+        for (var l = seqFn.length, i = 0; i < l; i++) {
+          if ('function' == typeof seqFn[i]) {
+            seqFn[i](self.transport);
+          }
+        }
+      }
+    }
+  });
+};
+
+/**
+ * Sends a message packet.
+ *
+ * @param {String} message
+ * @param {Function} callback
+ * @return {Socket} for chaining
+ * @api public
+ */
+
+Socket.prototype.send =
+Socket.prototype.write = function(data, callback){
+  this.sendPacket('message', data, callback);
+  return this;
+};
+
+/**
+ * Sends a packet.
+ *
+ * @param {String} packet type
+ * @param {String} optional, data
+ * @api private
+ */
+
+Socket.prototype.sendPacket = function (type, data, callback) {
+  if ('closing' != this.readyState) {
+    debug('sending packet "%s" (%s)', type, data);
+
+    var packet = { type: type };
+    if (data) packet.data = data;
+
+    // exports packetCreate event
+    this.emit('packetCreate', packet);
+
+    this.writeBuffer.push(packet);
+
+    //add send callback to object
+    this.packetsFn.push(callback);
+
+    this.flush();
+  }
+};
+
+/**
+ * Attempts to flush the packets buffer.
+ *
+ * @api private
+ */
+
+Socket.prototype.flush = function () {
+  if ('closed' != this.readyState && this.transport.writable
+    && this.writeBuffer.length) {
+    debug('flushing buffer to transport');
+    this.emit('flush', this.writeBuffer);
+    this.server.emit('flush', this, this.writeBuffer);
+    var wbuf = this.writeBuffer;
+    this.writeBuffer = [];
+    if (!this.transport.supportsFraming) {
+      this.sentCallbackFn.push(this.packetsFn);
+    } else {
+      this.sentCallbackFn.push.apply(this.sentCallbackFn, this.packetsFn);
+    }
+    this.packetsFn = [];
+    this.transport.send(wbuf);
+    this.emit('drain');
+    this.server.emit('drain', this);
+  }
+};
+
+/**
+ * Get available upgrades for this socket.
+ *
+ * @api private
+ */
+
+Socket.prototype.getAvailableUpgrades = function () {
+  var availableUpgrades = [];
+  var allUpgrades = this.server.upgrades(this.transport.name);
+  for (var i = 0, l = allUpgrades.length; i < l; ++i) {
+    var upg = allUpgrades[i];
+    if (this.server.transports.indexOf(upg) != -1) {
+      availableUpgrades.push(upg);
+    }
+  }
+  return availableUpgrades;
+};
+
+/**
+ * Closes the socket and underlying transport.
+ *
+ * @return {Socket} for chaining
+ * @api public
+ */
+
+Socket.prototype.close = function () {
+  if ('open' != this.readyState) return;
+
+  this.readyState = 'closing';
+
+  if (this.writeBuffer.length) {
+    this.once('drain', this.closeTransport.bind(this));
+    return;
+  }
+
+  this.closeTransport();
+};
+
+/**
+ * Closes the underlying transport.
+ *
+ * @api private
+ */
+
+Socket.prototype.closeTransport = function () {
+  this.transport.close(this.onClose.bind(this, 'forced close'));
+};
diff --git a/lib/transport.js b/lib/transport.js
new file mode 100644
index 0000000..993896b
--- /dev/null
+++ b/lib/transport.js
@@ -0,0 +1,114 @@
+
+/**
+ * Module dependencies.
+ */
+
+var EventEmitter = require('events').EventEmitter
+  , parser = require('engine.io-parser')
+  , debug = require('debug')('engine:transport');
+
+/**
+ * Expose the constructor.
+ */
+
+module.exports = Transport;
+
+/**
+ * Noop function.
+ *
+ * @api private
+ */
+
+function noop () {};
+
+/**
+ * Transport constructor.
+ *
+ * @param {http.ServerRequest} request
+ * @api public
+ */
+
+function Transport (req) {
+  this.readyState = 'opening';
+};
+
+/**
+ * Inherits from EventEmitter.
+ */
+
+Transport.prototype.__proto__ = EventEmitter.prototype;
+
+/**
+ * Called with an incoming HTTP request.
+ *
+ * @param {http.ServerRequest} request
+ * @api private
+ */
+
+Transport.prototype.onRequest = function (req) {
+  debug('setting request');
+  this.req = req;
+};
+
+/**
+ * Closes the transport.
+ *
+ * @api private
+ */
+
+Transport.prototype.close = function (fn) {
+  this.readyState = 'closing';
+  this.doClose(fn || noop);
+};
+
+/**
+ * Called with a transport error.
+ *
+ * @param {String} message error
+ * @param {Object} error description
+ * @api private
+ */
+
+Transport.prototype.onError = function (msg, desc) {
+  if (this.listeners('error').length) {
+    var err = new Error(msg);
+    err.type = 'TransportError';
+    err.description = desc;
+    this.emit('error', err);
+  } else {
+    debug('ignored transport error %s (%s)', msg, desc);
+  }
+};
+
+/**
+ * Called with parsed out a packets from the data stream.
+ *
+ * @param {Object} packet
+ * @api private
+ */
+
+Transport.prototype.onPacket = function (packet) {
+  this.emit('packet', packet);
+};
+
+/**
+ * Called with the encoded packet data.
+ *
+ * @param {String} data
+ * @api private
+ */
+
+Transport.prototype.onData = function (data) {
+  this.onPacket(parser.decodePacket(data));
+};
+
+/**
+ * Called upon transport close.
+ *
+ * @api private
+ */
+
+Transport.prototype.onClose = function () {
+  this.readyState = 'closed';
+  this.emit('close');
+};
diff --git a/lib/transports/index.js b/lib/transports/index.js
new file mode 100644
index 0000000..ecedb58
--- /dev/null
+++ b/lib/transports/index.js
@@ -0,0 +1,36 @@
+
+/**
+ * Module dependencies.
+ */
+
+var XHR = require('./polling-xhr');
+var JSONP = require('./polling-jsonp');
+
+/**
+ * Export transports.
+ */
+
+module.exports = exports = {
+  polling: polling,
+  websocket: require('./websocket')
+};
+
+/**
+ * Export upgrades map.
+ */
+
+exports.polling.upgradesTo = ['websocket'];
+
+/**
+ * Polling polimorphic constructor.
+ *
+ * @api private
+ */
+
+function polling (req) {
+  if ('string' == typeof req._query.j) {
+    return new JSONP(req);
+  } else {
+    return new XHR(req);
+  }
+}
diff --git a/lib/transports/polling-jsonp.js b/lib/transports/polling-jsonp.js
new file mode 100644
index 0000000..e9e416b
--- /dev/null
+++ b/lib/transports/polling-jsonp.js
@@ -0,0 +1,108 @@
+
+/**
+ * Module dependencies.
+ */
+
+var Polling = require('./polling');
+var qs = require('querystring');
+var rDoubleSlashes = /\\\\n/g;
+var rSlashes = /(\\)?\\n/g;
+
+/**
+ * Module exports.
+ */
+
+module.exports = JSONP;
+
+/**
+ * JSON-P polling transport.
+ *
+ * @api public
+ */
+
+function JSONP (req) {
+  Polling.call(this, req);
+
+  this.head = '___eio[' + (req._query.j || '').replace(/[^0-9]/g, '') + '](';
+  this.foot = ');';
+};
+
+/**
+ * Inherits from Polling.
+ */
+
+JSONP.prototype.__proto__ = Polling.prototype;
+
+/**
+ * Handles incoming data.
+ * Due to a bug in \n handling by browsers, we expect a escaped string.
+ *
+ * @api private
+ */
+
+JSONP.prototype.onData = function (data) {
+  // we leverage the qs module so that we get built-in DoS protection
+  // and the fast alternative to decodeURIComponent
+  data = qs.parse(data).d;
+  if ('string' == typeof data) {
+    //client will send already escaped newlines as \\\\n and newlines as \\n
+    // \\n must be replaced with \n and \\\\n with \\n
+    data = data.replace(rSlashes, function(match, slashes) {
+      return slashes ? match : '\n';
+    });
+    Polling.prototype.onData.call(this, data.replace(rDoubleSlashes, '\\n'));
+  }
+};
+
+/**
+ * Performs the write.
+ *
+ * @api private
+ */
+
+JSONP.prototype.doWrite = function (data) {
+  // we must output valid javascript, not valid json
+  // see: http://timelessrepo.com/json-isnt-a-javascript-subset
+  var js = JSON.stringify(data)
+    .replace(/\u2028/g, '\\u2028')
+    .replace(/\u2029/g, '\\u2029');
+
+  // prepare response
+  data = this.head + js + this.foot;
+
+  // explicit UTF-8 is required for pages not served under utf
+  var headers = {
+    'Content-Type': 'text/javascript; charset=UTF-8',
+    'Content-Length': Buffer.byteLength(data)
+  };
+
+  // prevent XSS warnings on IE
+  // https://github.com/LearnBoost/socket.io/pull/1333
+  var ua = this.req.headers['user-agent'];
+  if (ua && (~ua.indexOf(';MSIE') || ~ua.indexOf('Trident/'))) {
+    headers['X-XSS-Protection'] = '0';
+  }
+
+  this.res.writeHead(200, this.headers(this.req, headers));
+  this.res.end(data);
+};
+
+/**
+ * Returns headers for a response.
+ *
+ * @param {http.ServerRequest} request
+ * @param {Object} extra headers
+ * @api private
+ */
+
+JSONP.prototype.headers = function (req, headers) {
+  headers = headers || {};
+
+  // disable XSS protection for IE
+  if (/MSIE 8\.0/.test(req.headers['user-agent'])) {
+    headers['X-XSS-Protection'] = '0';
+  }
+
+  this.emit('headers', headers);
+  return headers;
+};
diff --git a/lib/transports/polling-xhr.js b/lib/transports/polling-xhr.js
new file mode 100644
index 0000000..2a8293c
--- /dev/null
+++ b/lib/transports/polling-xhr.js
@@ -0,0 +1,101 @@
+
+/**
+ * Module dependencies.
+ */
+
+var Polling = require('./polling');
+var Transport = require('../transport');
+var debug = require('debug')('engine:polling-xhr');
+
+/**
+ * Module exports.
+ */
+
+module.exports = XHR;
+
+/**
+ * Ajax polling transport.
+ *
+ * @api public
+ */
+
+function XHR(req){
+  Polling.call(this, req);
+}
+
+/**
+ * Inherits from Polling.
+ */
+
+XHR.prototype.__proto__ = Polling.prototype;
+
+/**
+ * Overrides `onRequest` to handle `OPTIONS`..
+ *
+ * @param {http.ServerRequest}
+ * @api private
+ */
+
+XHR.prototype.onRequest = function (req) {
+  if ('OPTIONS' == req.method) {
+    var res = req.res;
+    var headers = this.headers(req);
+    headers['Access-Control-Allow-Headers'] = 'Content-Type';
+    res.writeHead(200, headers);
+    res.end();
+  } else {
+    Polling.prototype.onRequest.call(this, req);
+  }
+};
+
+/**
+ * Frames data prior to write.
+ *
+ * @api private
+ */
+
+XHR.prototype.doWrite = function(data){
+  // explicit UTF-8 is required for pages not served under utf
+  var isString = typeof data == 'string';
+  var contentType = isString
+    ? 'text/plain; charset=UTF-8'
+    : 'application/octet-stream';
+  var contentLength = '' + (isString ? Buffer.byteLength(data) : data.length);
+
+  var headers = {
+    'Content-Type': contentType,
+    'Content-Length': contentLength
+  };
+
+  // prevent XSS warnings on IE
+  // https://github.com/LearnBoost/socket.io/pull/1333
+  var ua = this.req.headers['user-agent'];
+  if (ua && (~ua.indexOf(';MSIE') || ~ua.indexOf('Trident/'))) {
+    headers['X-XSS-Protection'] = '0';
+  }
+
+  this.res.writeHead(200, this.headers(this.req, headers));
+  this.res.end(data);
+};
+
+/**
+ * Returns headers for a response.
+ *
+ * @param {http.ServerRequest} request
+ * @param {Object} extra headers
+ * @api private
+ */
+
+XHR.prototype.headers = function(req, headers){
+  headers = headers || {};
+
+  if (req.headers.origin) {
+    headers['Access-Control-Allow-Credentials'] = 'true';
+    headers['Access-Control-Allow-Origin'] = req.headers.origin;
+  } else {
+    headers['Access-Control-Allow-Origin'] = '*';
+  }
+
+  this.emit('headers', headers);
+  return headers;
+};
diff --git a/lib/transports/polling.js b/lib/transports/polling.js
new file mode 100644
index 0000000..4a3a8aa
--- /dev/null
+++ b/lib/transports/polling.js
@@ -0,0 +1,279 @@
+
+/**
+ * Module requirements.
+ */
+
+var Transport = require('../transport')
+  , parser = require('engine.io-parser')
+  , debug = require('debug')('engine:polling');
+
+/**
+ * Exports the constructor.
+ */
+
+module.exports = Polling;
+
+/**
+ * HTTP polling constructor.
+ *
+ * @api public.
+ */
+
+function Polling (req) {
+  Transport.call(this, req);
+}
+
+/**
+ * Inherits from Transport.
+ *
+ * @api public.
+ */
+
+Polling.prototype.__proto__ = Transport.prototype;
+
+/**
+ * Transport name
+ *
+ * @api public
+ */
+
+Polling.prototype.name = 'polling';
+
+/**
+ * Overrides onRequest.
+ *
+ * @param {http.ServerRequest}
+ * @api private
+ */
+
+Polling.prototype.onRequest = function (req) {
+  var res = req.res;
+
+  if ('GET' == req.method) {
+    this.onPollRequest(req, res);
+  } else if ('POST' == req.method) {
+    this.onDataRequest(req, res);
+  } else {
+    res.writeHead(500);
+    res.end();
+  }
+};
+
+/**
+ * The client sends a request awaiting for us to send data.
+ *
+ * @api private
+ */
+
+Polling.prototype.onPollRequest = function (req, res) {
+  if (this.req) {
+    debug('request overlap');
+    // assert: this.res, '.req and .res should be (un)set together'
+    this.onError('overlap from client');
+    res.writeHead(500);
+    return;
+  }
+
+  debug('setting request');
+
+  this.req = req;
+  this.res = res;
+
+  var self = this;
+
+  function onClose () {
+    self.onError('poll connection closed prematurely');
+  }
+
+  function cleanup () {
+    req.removeListener('close', onClose);
+    self.req = self.res = null;
+  }
+
+  req.cleanup = cleanup;
+  req.on('close', onClose);
+
+  this.writable = true;
+  this.emit('drain');
+
+  // if we're still writable but had a pending close, trigger an empty send
+  if (this.writable && this.shouldClose) {
+    debug('triggering empty send to append close packet');
+    this.send([{ type: 'noop' }]);
+  }
+};
+
+/**
+ * The client sends a request with data.
+ *
+ * @api private
+ */
+
+Polling.prototype.onDataRequest = function (req, res) {
+  if (this.dataReq) {
+    // assert: this.dataRes, '.dataReq and .dataRes should be (un)set together'
+    this.onError('data request overlap from client');
+    res.writeHead(500);
+    return;
+  }
+
+  var isBinary = 'application/octet-stream' == req.headers['content-type'];
+
+  this.dataReq = req;
+  this.dataRes = res;
+
+  var chunks = isBinary ? new Buffer(0) : '';
+  var self = this;
+
+  function cleanup () {
+    chunks = isBinary ? new Buffer(0) : '';
+    req.removeListener('data', onData);
+    req.removeListener('end', onEnd);
+    req.removeListener('close', onClose);
+    self.dataReq = self.dataRes = null;
+  }
+
+  function onClose () {
+    cleanup();
+    self.onError('data request connection closed prematurely');
+  }
+
+  function onData (data) {
+    var contentLength;
+    if (typeof data == 'string') {
+      chunks += data;
+      contentLength = Buffer.byteLength(chunks);
+    } else {
+      chunks = Buffer.concat([chunks, data]);
+      contentLength = chunks.length;
+    }
+
+    if (contentLength > self.maxHttpBufferSize) {
+      chunks = '';
+      req.connection.destroy();
+    }
+  }
+
+  function onEnd () {
+    self.onData(chunks);
+
+    var headers = {
+      // text/html is required instead of text/plain to avoid an
+      // unwanted download dialog on certain user-agents (GH-43)
+      'Content-Type': 'text/html',
+      'Content-Length': 2
+    };
+
+    // prevent XSS warnings on IE
+    // https://github.com/LearnBoost/socket.io/pull/1333
+    var ua = req.headers['user-agent'];
+    if (ua && (~ua.indexOf(';MSIE') || ~ua.indexOf('Trident/'))) {
+      headers['X-XSS-Protection'] = '0';
+    }
+
+    res.writeHead(200, self.headers(req, headers));
+    res.end('ok');
+    cleanup();
+  }
+
+  req.on('close', onClose);
+  req.on('data', onData);
+  req.on('end', onEnd);
+  if (!isBinary) req.setEncoding('utf8');
+};
+
+/**
+ * Processes the incoming data payload.
+ *
+ * @param {String} encoded payload
+ * @api private
+ */
+
+Polling.prototype.onData = function (data) {
+  debug('received "%s"', data);
+  var self = this;
+  var callback = function(packet) {
+    if ('close' == packet.type) {
+      debug('got xhr close packet');
+      self.onClose();
+      return false;
+    }
+
+    self.onPacket(packet);
+  };
+
+  parser.decodePayload(data, callback);
+};
+
+/**
+ * Overrides onClose.
+ *
+ * @api private
+ */
+
+Polling.prototype.onClose = function () {
+  if (this.writable) {
+    // close pending poll request
+    this.send([{ type: 'noop' }]);
+  }
+  Transport.prototype.onClose.call(this);
+};
+
+/**
+ * Writes a packet payload.
+ *
+ * @param {Object} packet
+ * @api private
+ */
+
+Polling.prototype.send = function (packets) {
+  if (this.shouldClose) {
+    debug('appending close packet to payload');
+    packets.push({ type: 'close' });
+    this.shouldClose();
+    this.shouldClose = null;
+  }
+
+  var self = this;
+  parser.encodePayload(packets, this.supportsBinary, function(data) {
+    self.write(data);
+  });
+};
+
+/**
+ * Writes data as response to poll request.
+ *
+ * @param {String} data
+ * @api private
+ */
+
+Polling.prototype.write = function (data) {
+  debug('writing "%s"', data);
+  this.doWrite(data);
+  this.req.cleanup();
+  this.writable = false;
+};
+
+/**
+ * Closes the transport.
+ *
+ * @api private
+ */
+
+Polling.prototype.doClose = function (fn) {
+  debug('closing');
+
+  if (this.dataReq) {
+    debug('aborting ongoing data request');
+    this.dataReq.destroy();
+  }
+
+  if (this.writable) {
+    debug('transport writable - closing right away');
+    this.send([{ type: 'close' }]);
+    fn();
+  } else {
+    debug('transport not writable - buffering orderly close');
+    this.shouldClose = fn;
+  }
+};
diff --git a/lib/transports/websocket.js b/lib/transports/websocket.js
new file mode 100644
index 0000000..59b8eb7
--- /dev/null
+++ b/lib/transports/websocket.js
@@ -0,0 +1,110 @@
+
+/**
+ * Module dependencies.
+ */
+
+var Transport = require('../transport')
+  , parser = require('engine.io-parser')
+  , debug = require('debug')('engine:ws')
+
+/**
+ * Export the constructor.
+ */
+
+module.exports = WebSocket;
+
+/**
+ * WebSocket transport 
+ *
+ * @param {http.ServerRequest}
+ * @api public
+ */
+
+function WebSocket (req) {
+  Transport.call(this, req);
+  var self = this;
+  this.socket = req.websocket;
+  this.socket.on('message', this.onData.bind(this));
+  this.socket.once('close', this.onClose.bind(this));
+  this.socket.on('error', this.onError.bind(this));
+  this.socket.on('headers', function (headers) {
+    self.emit('headers', headers);
+  });
+  this.writable = true;
+};
+
+/**
+ * Inherits from Transport.
+ */
+
+WebSocket.prototype.__proto__ = Transport.prototype;
+
+/**
+ * Transport name
+ *
+ * @api public
+ */
+
+WebSocket.prototype.name = 'websocket';
+
+/**
+ * Advertise upgrade support.
+ *
+ * @api public
+ */
+
+WebSocket.prototype.handlesUpgrades = true;
+
+/**
+ * Advertise framing support.
+ *
+ * @api public
+ */
+
+WebSocket.prototype.supportsFraming = true;
+
+/**
+ * Processes the incoming data.
+ *
+ * @param {String} encoded packet
+ * @api private
+ */
+
+WebSocket.prototype.onData = function (data) {
+  debug('received "%s"', data);
+  Transport.prototype.onData.call(this, data);
+};
+
+/**
+ * Writes a packet payload.
+ *
+ * @param {Array} packets
+ * @api private
+ */
+
+WebSocket.prototype.send = function (packets) {
+  var self = this;
+  for (var i = 0, l = packets.length; i < l; i++) {
+    parser.encodePacket(packets[i], this.supportsBinary, function(data) {
+      debug('writing "%s"', data);
+      self.writable = false;
+      self.socket.send(data, function (err){
+        if (err) return self.onError('write error', err.stack);
+        self.writable = true;
+        self.emit('drain');
+      });
+    });
+  }
+};
+
+/**
+ * Closes the transport.
+ *
+ * @api private
+ */
+
+WebSocket.prototype.doClose = function (fn) {
+  debug('closing');
+  this.socket.close();
+  fn && fn();
+};
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..4920772
--- /dev/null
+++ b/package.json
@@ -0,0 +1,32 @@
+{
+  "name": "engine.io",
+  "version": "1.5.1",
+  "description": "The realtime engine behind Socket.IO. Provides the foundation of a bidirectional connection between client and server",
+  "main": "./lib/engine.io",
+  "author": "Guillermo Rauch <guillermo at learnboost.com>",
+  "homepage": "https://github.com/LearnBoost/engine.io",
+  "contributors": [
+    { "name": "Eugen Dueck", "web": "https://github.com/EugenDueck" },
+    { "name": "Afshin Mehrabani", "web": "https://github.com/afshinm" },
+    { "name": "Christoph Dorn", "web": "https://github.com/cadorn" },
+    { "name": "Mark Mokryn", "email": "mokesmokes at gmail.com"}
+  ],
+  "dependencies": {
+    "debug": "1.0.3",
+    "ws": "0.5.0",
+    "engine.io-parser": "1.2.1",
+    "base64id": "0.1.0"
+  },
+  "devDependencies": {
+    "mocha": "1.12.0",
+    "expect.js": "0.2.0",
+    "superagent": "0.15.4",
+    "engine.io-client": "1.5.1",
+    "s": "0.1.1"
+  },
+  "scripts": { "test" : "make test" },
+  "repository": {
+    "type": "git",
+    "url": "git at github.com:Automattic/engine.io.git"
+  }
+}
diff --git a/test/common.js b/test/common.js
new file mode 100644
index 0000000..a754306
--- /dev/null
+++ b/test/common.js
@@ -0,0 +1,29 @@
+
+/**
+ * Module dependencies.
+ */
+
+var eio = require('..');
+
+/**
+ * Listen shortcut that fires a callback on an epheemal port.
+ */
+
+exports.listen = function (opts, fn) {
+  if ('function' == typeof opts) {
+    fn = opts;
+    opts = {};
+  }
+
+  var e = eio.listen(null, opts, function () {
+    fn(e.httpServer.address().port);
+  });
+
+  return e;
+};
+
+/**
+ * Sprintf util.
+ */
+
+require('s').extend();
diff --git a/test/engine.io.js b/test/engine.io.js
new file mode 100644
index 0000000..01e2eb8
--- /dev/null
+++ b/test/engine.io.js
@@ -0,0 +1,230 @@
+
+/**
+ * Test dependencies.
+ */
+
+var net = require('net');
+var eio = require('..');
+var listen = require('./common').listen;
+var expect = require('expect.js');
+var request = require('superagent');
+var http = require('http');
+
+/**
+ * Tests.
+ */
+
+describe('engine', function () {
+
+  it('should expose protocol number', function () {
+    expect(eio.protocol).to.be.a('number');
+  });
+
+  it('should be the same version as client', function(){
+    expect(eio.protocol).to.be.a('number');
+    var version = require('../package').version;
+    expect(version).to.be(require('engine.io-client/package').version);
+  });
+
+  describe('engine()', function () {
+    it('should create a Server when require called with no arguments', function () {
+      var engine = eio();
+      expect(engine).to.be.an(eio.Server);
+    });
+  });
+
+  describe('listen', function () {
+    it('should open a http server that returns 501', function (done) {
+      var server = listen(function (port) {
+        request.get('http://localhost:%d/'.s(port), function (res) {
+          expect(res.status).to.be(501);
+          done();
+        });
+      });
+    });
+  });
+
+  describe('attach()', function () {
+    it('should work from require()', function () {
+      var server = http.createServer();
+      var engine = eio(server);
+
+      expect(engine).to.be.an(eio.Server);
+    });
+
+    it('should return an engine.Server', function () {
+      var server = http.createServer()
+        , engine = eio.attach(server);
+
+      expect(engine).to.be.an(eio.Server);
+    });
+
+    it('should attach engine to an http server', function (done) {
+      var server = http.createServer()
+        , engine = eio.attach(server);
+
+      server.listen(function () {
+        var uri = 'http://localhost:%d/engine.io/default/'.s(server.address().port);
+        request.get(uri, function (res) {
+          expect(res.status).to.be(400);
+          expect(res.body.code).to.be(0);
+          expect(res.body.message).to.be('Transport unknown');
+          server.once('close', done);
+          server.close();
+        });
+      });
+    });
+
+    it('should destroy upgrades not handled by engine', function (done) {
+      var server = http.createServer()
+        , engine = eio.attach(server);
+
+      server.listen(function () {
+        var client = net.createConnection(server.address().port);
+        client.setEncoding('ascii');
+        client.write([
+            'GET / HTTP/1.1'
+          , 'Upgrade: IRC/6.9'
+          , '', ''
+        ].join('\r\n'));
+
+        var check = setTimeout(function () {
+          done(new Error('Client should have ended'));
+        }, 20);
+
+        client.on('end', function () {
+          clearTimeout(check);
+          done();
+        });
+      });
+    });
+
+    it('should not destroy unhandled upgrades with destroyUpgrade:false', function (done) {
+      var server = http.createServer()
+        , engine = eio.attach(server, { destroyUpgrade: false, destroyUpgradeTimeout: 50 });
+
+      server.listen(function () {
+        var client = net.createConnection(server.address().port);
+        client.on('connect', function () {
+          client.setEncoding('ascii');
+          client.write([
+              'GET / HTTP/1.1'
+            , 'Upgrade: IRC/6.9'
+            , '', ''
+          ].join('\r\n'));
+
+          var check = setTimeout(function () {
+            client.removeListener('end', onEnd);
+            done();
+          }, 100);
+
+          function onEnd () {
+            done(new Error('Client should not end'));
+          }
+
+          client.on('end', onEnd);
+        });
+      });
+    });
+
+    it('should destroy unhandled upgrades with after a timeout', function (done) {
+      var server = http.createServer()
+        , engine = eio.attach(server, { destroyUpgradeTimeout: 200 });
+
+      server.listen(function () {
+        var client = net.createConnection(server.address().port);
+        client.on('connect', function () {
+          client.setEncoding('ascii');
+          client.write([
+              'GET / HTTP/1.1'
+            , 'Upgrade: IRC/6.9'
+            , '', ''
+          ].join('\r\n'));
+
+          // send from client to server
+          // tests that socket is still alive
+          // this will not keep the socket open as the server does not handle it
+          setTimeout(function() {
+            client.write('foo');
+          }, 100);
+
+          function onEnd () {
+            done();
+          }
+
+          client.on('end', onEnd);
+        });
+      });
+    });
+
+    it('should not destroy handled upgrades with after a timeout', function (done) {
+      var server = http.createServer()
+        , engine = eio.attach(server, { destroyUpgradeTimeout: 100 });
+
+      // write to the socket to keep engine.io from closing it by writing before the timeout
+      server.on('upgrade', function(req, socket) {
+        socket.write('foo');
+        socket.on('data', function(chunk) {
+          expect(chunk.toString()).to.be('foo');
+          socket.end();
+        });
+      });
+
+      server.listen(function () {
+        var client = net.createConnection(server.address().port);
+
+        client.on('connect', function () {
+          client.setEncoding('ascii');
+          client.write([
+              'GET / HTTP/1.1'
+            , 'Upgrade: IRC/6.9'
+            , '', ''
+          ].join('\r\n'));
+
+          // test that socket is still open by writing after the timeout period
+          setTimeout(function() {
+            client.write('foo');
+          }, 200);
+
+          client.on('data', function (data) {
+          });
+
+          client.on('end', done);
+        });
+      });
+    });
+
+    it('should preserve original request listeners', function (done) {
+      var listeners = 0
+        , server = http.createServer(function (req, res) {
+            expect(req && res).to.be.ok();
+            listeners++;
+          });
+
+      server.on('request', function (req, res) {
+        expect(req && res).to.be.ok();
+        res.writeHead(200);
+        res.end('');
+        listeners++;
+      });
+
+      eio.attach(server);
+
+      server.listen(function () {
+        var port = server.address().port;
+        request.get('http://localhost:%d/engine.io/default/'.s(port), function (res) {
+          expect(res.status).to.be(400);
+          expect(res.body.code).to.be(0);
+          expect(res.body.message).to.be('Transport unknown');
+          request.get('http://localhost:%d/test'.s(port), function (res) {
+            expect(res.status).to.be(200);
+            expect(listeners).to.eql(2);
+            server.once('close', done);
+            server.close();
+          });
+        });
+      });
+    });
+  });
+
+});
diff --git a/test/fixtures/ca.crt b/test/fixtures/ca.crt
new file mode 100644
index 0000000..974a2a8
--- /dev/null
+++ b/test/fixtures/ca.crt
@@ -0,0 +1,35 @@
+-----BEGIN CERTIFICATE-----
+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
+-----END CERTIFICATE-----
diff --git a/test/fixtures/ca.key b/test/fixtures/ca.key
new file mode 100644
index 0000000..4568779
--- /dev/null
+++ b/test/fixtures/ca.key
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+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
+-----END RSA PRIVATE KEY-----
diff --git a/test/fixtures/ca.key.org b/test/fixtures/ca.key.org
new file mode 100644
index 0000000..185be8d
--- /dev/null
+++ b/test/fixtures/ca.key.org
@@ -0,0 +1,54 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-EDE3-CBC,47A0291BAB679B36
+
+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
+-----END RSA PRIVATE KEY-----
diff --git a/test/fixtures/client.crt b/test/fixtures/client.crt
new file mode 100644
index 0000000..4c652a2
--- /dev/null
+++ b/test/fixtures/client.crt
@@ -0,0 +1,23 @@
+-----BEGIN CERTIFICATE-----
+MIIDxjCCAa4CAQEwDQYJKoZIhvcNAQEFBQAwaTELMAkGA1UEBhMCRkkxEzARBgNV
+BAgTClNvbWUtU3RhdGUxETAPBgNVBAcTCEhlbHNpbmtpMSEwHwYDVQQKExhJbnRl
+cm5ldCBXaWRnaXRzIFB0eSBMdGQxDzANBgNVBAMTBkZvbyBDQTAeFw0xNDEwMTEy
+MjQ3NDBaFw0xNTEwMTEyMjQ3NDBaMG0xCzAJBgNVBAYTAkZJMRMwEQYDVQQIEwpT
+b21lLVN0YXRlMREwDwYDVQQHEwhIZWxzaW5raTEhMB8GA1UEChMYSW50ZXJuZXQg
+V2lkZ2l0cyBQdHkgTHRkMRMwEQYDVQQDEwpGb28gQ2xpZW50MIGfMA0GCSqGSIb3
+DQEBAQUAA4GNADCBiQKBgQD66Rn8P8O+MK13sPxIIEMHXDRZheRLqGNlNsXzaBLW
+nKSlV+Wxi1OdCimtAh4ZAVRt1JkK9mQEAGdxC8TRwDMS02+EUK1H1zvh77Ek4ZcH
+W8p5CVEm53FTmO+jhL+7BQYXW1yi/XURBv2xm3Q95I7895agprMFI8HiOu/Hdi/i
+DQIDAQABMA0GCSqGSIb3DQEBBQUAA4ICAQCQJeIJFmt0fV+eeNGWcgeNJ8EqahDU
+5qn37Jew9W+t8qSsBkizPvnc/SEQDeFNnm8QAPMA1ffpYcdMllFs3eUIZ0bRZPxw
+SacGUmGRA2VAM8tcH3sXiqpwMyYt2r2Ccg52lsnPvnkytJZaMUi64sYj57ECbIj4
+lPgCNLbIgaR9EP4Pep8QZxdjrtBhG2XwKZjAMn4taJQdz797sjSW+Zo243BvKoqn
+ZaBNnvcN/TL5kyZd9WK02BeO+ChadKeIQbnBIYLNJ40EHb5YCKk/hU1x1uVyyXcd
+mtONDXjwJn4jBMo8fdHtq3iashhiGJGLJT5qQ3iM90fg6JJDthjMMFL/YKIzt1+q
+ORyU2jMcYH83sZ3aEHxiaKhXb/Sr55iMUfIEK+Xn16WXX0YsLoq2UJ0BDRrWqiyO
+QfQt6k8V4KDtoTDEmPw0/8oGLJv2/nzQyL5e+MAiORobuzFP+xIynWclNtRw2zCM
+DT5cqkwzBqCwVBNghehH0im1dBnQiQz56+TSx2X01aY2In29ZR887858EmtQP9gK
+y3lAboC2/HO7eQtTUlos9b4m/trst+eC6iewz45VbIOrB31PAN5DBiSdrr/ZMxom
+hyBdkMUD2a/BG+zctDRqpN/d6udiT9E74lGmYIfVNa5Yu5hb8zs5mnbZ05Xi4kBm
+EGC15ScHiTib5w==
+-----END CERTIFICATE-----
diff --git a/test/fixtures/client.csr b/test/fixtures/client.csr
new file mode 100644
index 0000000..ca1ae28
--- /dev/null
+++ b/test/fixtures/client.csr
@@ -0,0 +1,12 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIBrTCCARYCAQAwbTELMAkGA1UEBhMCRkkxEzARBgNVBAgTClNvbWUtU3RhdGUx
+ETAPBgNVBAcTCEhlbHNpbmtpMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0
+eSBMdGQxEzARBgNVBAMTCkZvbyBDbGllbnQwgZ8wDQYJKoZIhvcNAQEBBQADgY0A
+MIGJAoGBAPrpGfw/w74wrXew/EggQwdcNFmF5EuoY2U2xfNoEtacpKVX5bGLU50K
+Ka0CHhkBVG3UmQr2ZAQAZ3ELxNHAMxLTb4RQrUfXO+HvsSThlwdbynkJUSbncVOY
+76OEv7sFBhdbXKL9dREG/bGbdD3kjvz3lqCmswUjweI678d2L+INAgMBAAGgADAN
+BgkqhkiG9w0BAQUFAAOBgQDKGUqjkUxGOisFN70X7ZOW7H99veR9QlixKl5e0W+7
+UtJ+GUtH2WQEb4F72+ruHrdDWQI1VaH9hPOvTRCjlgXiT0RHXpGPbJK/Nc+Eq5dm
+kuk/tQeXv6+S1fgYOm0w09rE7pBjQtuAybB55lGZ7k84UE2xTc97Ru14nYFCsZ4z
+RA==
+-----END CERTIFICATE REQUEST-----
diff --git a/test/fixtures/client.key b/test/fixtures/client.key
new file mode 100644
index 0000000..e52bafb
--- /dev/null
+++ b/test/fixtures/client.key
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQD66Rn8P8O+MK13sPxIIEMHXDRZheRLqGNlNsXzaBLWnKSlV+Wx
+i1OdCimtAh4ZAVRt1JkK9mQEAGdxC8TRwDMS02+EUK1H1zvh77Ek4ZcHW8p5CVEm
+53FTmO+jhL+7BQYXW1yi/XURBv2xm3Q95I7895agprMFI8HiOu/Hdi/iDQIDAQAB
+AoGBAOFHTXd4YO1wky82Dy1LGiOPm8kNOC7d33BOv2iN9uwN9J4nzymbqNUE/OpD
+TnaxBPcfvNFk6+PT4QxUvsB8ytzDMZ3YC4xyJf5GPP/hfzyWCRjB557WZl1cx7nC
+2gA93PBZE7WT1SySXmjsiC7o/2T/0cUaawXOBczHP8oXoEkBAkEA/c1MHs13ojxh
+oOj/ibCpYpd2Zv5Hrc5tsh+otDdIrb79IAHnNw7WhMkLs6cLk1MY6jLeCvQtjlUY
+H5C/6Ez84QJBAP0VZMgWPw3FVNXPrj833OA6XjyWO+TADpnlrahuDQqWnR3C29Uc
+Iq/ApVX2pt2cNIZpiuJ4BYNc44cHjvu6vq0CQQChan1cJc9NhluNLELBfnLsOmpa
+bKSH3P8VR19TZsm5fvub7Lnx4WT7xKXFl5scEsCIyts/WjbTDDmwca4r/zLhAkB4
+wkeHbY4CnSDgsKr9AUPEPjWPBURo3vdYmY4mKvTQE5O+iqboZfdrEyoQ/ZMbdRhe
+9mdNrmU7DAyI9qNUHAQ1AkAlq/vdkrcq5SRR9uti/1M0/Jaw7l3JutBaW93kdvXx
+BX568ezO1PQtXwVSv+uJEkDoST1bkvhqt7hlMu/RkmfG
+-----END RSA PRIVATE KEY-----
diff --git a/test/fixtures/client.key.orig b/test/fixtures/client.key.orig
new file mode 100644
index 0000000..d9c39ba
--- /dev/null
+++ b/test/fixtures/client.key.orig
@@ -0,0 +1,18 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-EDE3-CBC,081B123E7DBC9687
+
+c8gE0LQMMtV7GhUcP7GHBotlNNAsbyG/gERB//BFlvRPjs43ALvm5xMGUkcsZkw9
+H6ljvvgyRkud06x1mGxqlZ4fLlty0/mdXf6791R9gA/9bKLakHGlwnoVUrEPIeh3
+Iu/3IF0Uz8o3uljSp9eehR8sW5V4o+Z1MszQ+GkcpCyYnDPykyN52QLTM3RcUJ0v
+2iJrgxYEAyMA14iIpYM8IsFDYIBvwVfQxyBZnQ+8dDQgVOokz56g+/9emgOsVjWj
+bdUKZ7+95RLxCq0hjJd8GnoDn7qtxIPOGVqYcdEwwNyzO43FnpjOFhNDWwsrNrgf
+vUHKhZRHB99CkuCvGg4fjVbFd/1/n3eE5VmiQuqoHzPnXH3RDIx2rZSjFo/NkzYx
+4XzZ4YOe6vOP7kR18CL+bl32WneMELoh+TPLmsSC3rIP48M8+oQINSZaBrl59Ocw
+NW36xgDNSQoia2splVNo51vtZomq1Hb3co6hD43D4xnrc6Aqucm3BsW6UCc/PVKv
+HAXLCb+3awIy5NJSYb0qRETE2rKB6LjmKfILtOrto6QYSlkJmQUxqoPXgRAWWNlw
+1Ngws84+6UjmGWDBlpZHn0hcO/B7KJAAS/xNSFYDoSu6dAbabxTI/dZCWhw1aN5c
+QeYPihCi66F6Vuq/QT89dHtZE4IMPH7R95Yp18tCcyVGxaEiphw3HJYepMxoJesQ
+YH8tWQwvD5LaADzNJIKBxMjCOK23GuXWQLJJRf4QWiXaQar69qXULxBT3iqBp/rQ
+VKsQByBwJlEo/YSEFjhhMgo0zSbqIRAY1XCDo+dgB2IB1KPAobhSCQ==
+-----END RSA PRIVATE KEY-----
diff --git a/test/fixtures/client.pfx b/test/fixtures/client.pfx
new file mode 100644
index 0000000..98ecf33
Binary files /dev/null and b/test/fixtures/client.pfx differ
diff --git a/test/fixtures/server.crt b/test/fixtures/server.crt
new file mode 100644
index 0000000..abef474
--- /dev/null
+++ b/test/fixtures/server.crt
@@ -0,0 +1,23 @@
+-----BEGIN CERTIFICATE-----
+MIIDxTCCAa0CAQEwDQYJKoZIhvcNAQEFBQAwaTELMAkGA1UEBhMCRkkxEzARBgNV
+BAgTClNvbWUtU3RhdGUxETAPBgNVBAcTCEhlbHNpbmtpMSEwHwYDVQQKExhJbnRl
+cm5ldCBXaWRnaXRzIFB0eSBMdGQxDzANBgNVBAMTBkZvbyBDQTAeFw0xNDEwMTEy
+MjQ2MzRaFw0xNTEwMTEyMjQ2MzRaMGwxCzAJBgNVBAYTAkZJMRMwEQYDVQQIEwpT
+b21lLVN0YXRlMREwDwYDVQQHEwhIZWxzaW5raTEhMB8GA1UEChMYSW50ZXJuZXQg
+V2lkZ2l0cyBQdHkgTHRkMRIwEAYDVQQDEwlsb2NhbGhvc3QwgZ8wDQYJKoZIhvcN
+AQEBBQADgY0AMIGJAoGBAMBAs08Mguowi6W8+BrqiByLr3FhpKnnRTtO/wfDDebg
+P3Da5vIapHt0NbbSIYkju1M1y/9S0uqCScSIIFeSr8wOvmB1c0jRX+P6oA6p8lJX
+G/HEfEuu3uK+Olkds7sHcrtuRaYLEAXNYCUTlT0VdfVvxV960m/5wMRpexjC5h+j
+AgMBAAEwDQYJKoZIhvcNAQEFBQADggIBAElgxIBym6RrXNc0WALCmvZLzWXPBOUc
+zCbN318AuRqbTJP56XHGU3IKUI5GDfAIIlH+tXjxsVDLCzx/1LQv77ko3OgcCTvL
+oxyOQe7k+L8LWmU5/F4V/ndP4VgqF34APfQrdHzsveD5p/93SoVYQQMUoEjDKIqM
+RJMxUbcTTLGvCb9LrAhQuBT/9s+OlmtS84JkX7HHp7I2SlqL44gzpGBIZZE8Pj/D
+h6q1t2WR5YM8ybScmuNb62ws9mZiej51e+QYnGCpRq7DSv6gSSo8y7L24vGxMFrS
+2X/ISgYfy3UE1ziddGYpEpEpRcKzYSPTSQc5Juzn/iKhlnIBf8ALzCC/s1GteM+p
+wRAGGsTueiiksuDmohI9qjJL9+kLVgL41alfXGpYkNStlWEXTryMv0VT/Exr/prK
+vadVfwa9/T+ywzmwDNPrZFN/LEtPSaMclMzmclLjebqoqUAF05UUdnTQabh5gMni
+VcHaxwZsBDeDhXqowhlHKfdXNZzi2skHA04g4FPxys7llwW0J/gj7hUYpqCxxsLp
+4O1tb30dxBSYKLXJDaEXl07kQ1RTi7vkzUPh3P4totQYq4/Tc1O03YXkOm+wxpC3
+ByxFHMt9glEySrpvZmRvmsmoMuBoFklqBd4gLeUerOVg521lpCBoAWE/tFvWkOmd
+MsONbxQDiiVP
+-----END CERTIFICATE-----
diff --git a/test/fixtures/server.csr b/test/fixtures/server.csr
new file mode 100644
index 0000000..9906b70
--- /dev/null
+++ b/test/fixtures/server.csr
@@ -0,0 +1,11 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIBrDCCARUCAQAwbDELMAkGA1UEBhMCRkkxEzARBgNVBAgTClNvbWUtU3RhdGUx
+ETAPBgNVBAcTCEhlbHNpbmtpMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0
+eSBMdGQxEjAQBgNVBAMTCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
+gYkCgYEAwECzTwyC6jCLpbz4GuqIHIuvcWGkqedFO07/B8MN5uA/cNrm8hqke3Q1
+ttIhiSO7UzXL/1LS6oJJxIggV5KvzA6+YHVzSNFf4/qgDqnyUlcb8cR8S67e4r46
+WR2zuwdyu25FpgsQBc1gJROVPRV19W/FX3rSb/nAxGl7GMLmH6MCAwEAAaAAMA0G
+CSqGSIb3DQEBBQUAA4GBAClj/K2DAH5S64T6s7jervmk4N956Ho3aTLBgE+ReXLj
+btcTdk3vFbQApAlG6MrSKys4HjpKpP/RENx3Js0HHeb8ELmWtIQNxRhwIpl0K5AD
+xorKj+mwngLtVyARb/M7O3E8jYHzBPzpsolKWIY4AavYdmHu+Zhgm4hPKUcW+bAv
+-----END CERTIFICATE REQUEST-----
diff --git a/test/fixtures/server.key b/test/fixtures/server.key
new file mode 100644
index 0000000..3465786
--- /dev/null
+++ b/test/fixtures/server.key
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXgIBAAKBgQDAQLNPDILqMIulvPga6ogci69xYaSp50U7Tv8Hww3m4D9w2uby
+GqR7dDW20iGJI7tTNcv/UtLqgknEiCBXkq/MDr5gdXNI0V/j+qAOqfJSVxvxxHxL
+rt7ivjpZHbO7B3K7bkWmCxAFzWAlE5U9FXX1b8VfetJv+cDEaXsYwuYfowIDAQAB
+AoGBAL7tQmXl2fmz/mu5kHhCpKwcuT6TpxEo4aN132aY+qxn1flBHAwiE2mbTmDi
+rHViq/2GNrK5UUed3p60RdJSlgwIkyqtcGxWhUJGYCR/hU60qeeLp3MhhOoOFbiV
+YTDsoC7V/SuWbX+1qG5FxnHSnTZhAIRkZXS4uTZ5WDcQm/7BAkEA+TlZ1IT9CeU/
+FpHpqc8RgR8377Ehjy8o4Z4EGFnxQlAUWASnhs6dw4isr3+c7hA1OEmqmcRClPVZ
+t1JbHAPC4QJBAMV60WSJzPUccCF47T9Ao2CeWFl/9dmgQQe9idpTNuKMXNtPJN44
+0MQvnb+xS828woJOoRI+/UTVLLBc4xwMtwMCQQDZTadExTw4v5l1nX5GoJUbp9PG
+/ARN64nSx0u8y9evwVErucs0oL0we+BOGZAEhz9QN/M3pceESDWUwYtNbv4hAkBB
+Ku2MqvjK7k6GjTxlgjQn/zkSl+qOnZa4MjEarhlPm5hM+wokl0U1aK07BAwK4b6i
+d8YpmkXEAEEWFiEQMZX3AkEA1SkdiFj1u7HnzO7onLJsnFzowX3pm1UFl0azOMlM
+2GkjYxWeJ/4VL7Y6QkhHE0Nj3my2+MJQI9xpYgMbw/l11w==
+-----END RSA PRIVATE KEY-----
diff --git a/test/fixtures/server.key.orig b/test/fixtures/server.key.orig
new file mode 100644
index 0000000..eca29e6
--- /dev/null
+++ b/test/fixtures/server.key.orig
@@ -0,0 +1,18 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-EDE3-CBC,8A14F3F31BF3CDF7
+
+84UpLjX5GbfCoP/+ZRLwHZHqmgKyc2cjQOQKhp2YTk+4E0JSO7KsKwJax55r5VqE
+ECGvbd8yvVZ3GRpU6bDCLelDN+Ob5ZSGhynI0f9is6Qle1/SbvaK+qL6MLf4H38l
+Y91eZxjE8hLvOMHBlbobLmy9UUzl2osBDAM1nm5d1X7pF7mN4xgY8s2G7UPq3ZZS
+NWwMsnwtb/9Ahm7WXfsJbebyKspkTSHYp/ozaOGW58fuKxkwRFd1UlyblZWU3ezP
+JCpvJQ7HS6Dfy6+GUaxcC6pyxqnoJHYccB5usJ2h4QD4Es3sT7Vw7M80JKw3vAWm
+TH7VkFX3yGfJ1p1jNzifN5687QqrjeI3/ecTs1rFhIC4TUPN9EDvw86Y6l6Mvo04
+Hl2cVzCnCrZYq0ICD0op3+7f9kuKl7bz54S/iRG2qQdICohPs7ra2yaUy+NFVDs1
+XdXyF5/xew+Rr2z7ygEd+OrvXxPV0zTFbicg9GXGeB/pIYAclmoSNXD5T3voN7y9
+5MjSGL375N3z+kqPzMNawYCnZLwQ5jYTDUkDTATYpjcIDVDQkzbl8mFp4i+Z9GhL
+H8FRAgmBbkgy3dxhFjxr/WzuaTkUCAbGhrtPd8WCPhBXJBzWdrBVR9SE8zT9n8lq
+ZwaPwkPLBrLe/XZFEQJ5cdvMVNy4QQwsyxHnCgO3VUc68UYXb39MvyM6t8+S1rSm
+SyvVAT+jB8T4VlE7tedQGuvyKMmeNIJe9znd3u4S+Aq2+vw6bOKeNYBMaupR5Gyl
+bJrscuTG1aLUe+XH9BuFBUoIwdXuBv4Ko/pDL0MYPghDAEGkp4Acmg==
+-----END RSA PRIVATE KEY-----
diff --git a/test/jsonp.js b/test/jsonp.js
new file mode 100644
index 0000000..934d1ff
--- /dev/null
+++ b/test/jsonp.js
@@ -0,0 +1,232 @@
+
+/**
+ * Module dependencies.
+ */
+
+var http = require('http');
+var eioc = require('engine.io-client');
+var listen = require('./common').listen;
+var expect = require('expect.js');
+var request = require('superagent');
+var WebSocket = require('ws');
+
+describe('JSONP', function () {
+  before(function () {
+    // we have to override the browser's functionality for JSONP
+    document = {
+      body: {
+        appendChild: function(){},
+        removeChild: function(){}
+      }
+    };
+
+    document.createElement = function (name) {
+      var self = this;
+
+      if('script' == name) {
+        var script = {};
+
+        script.__defineGetter__('parentNode', function () {
+          return document.body;
+        });
+
+        script.__defineSetter__('src', function (uri) {
+          request.get(uri).end(function(res) {
+            eval(res.text);
+          });
+        });
+        return script;
+      } else if ('form' == name) {
+        var form = {
+          style: {},
+          action: '',
+          parentNode: { removeChild: function(){} },
+          removeChild: function(){},
+          setAttribute: function(){},
+          appendChild: function(){},
+          submit: function(){
+            request
+            .post(this.action)
+            .type('form')
+            .send({ d: self.areaValue })
+            .end(function(){});
+          }
+        };
+        return form;
+      } else if ('textarea' == name) {
+        var textarea = {};
+
+        //a hack to be able to access the area data when form is sent
+        textarea.__defineSetter__('value', function (data) {
+          self.areaValue = data;
+        });
+        return textarea;
+      } else if (~name.indexOf('iframe')) {
+        var iframe = {};
+        setTimeout(function() {
+          if (iframe.onload) iframe.onload();
+        }, 0);
+
+        return iframe;
+      } else {
+        return {};
+      }
+    }
+
+    document.getElementsByTagName = function (name) {
+      return [{
+        parentNode: {
+          insertBefore: function () {}
+        }
+      }]
+    }
+  });
+
+  after(function () {
+    delete document.getElementsByTagName
+      , document.createElement;
+    delete global.document;
+  });
+
+  describe('handshake', function () {
+    it('should open with polling JSONP when requested', function (done) {
+      var engine = listen( { allowUpgrades: false, transports: ['polling'] }, function (port) {
+        var socket = new eioc.Socket('ws://localhost:' + port
+          , { transports: ['polling'], forceJSONP: true, upgrade: false });
+        engine.on('connection', function (socket) {
+          expect(socket.transport.name).to.be('polling');
+          expect(socket.transport.head).to.be('___eio[0](');
+          done();
+        });
+      });
+    });
+  });
+
+  describe('messages', function () {
+    var engine, port, socket;
+
+    beforeEach(function(done) {
+      engine = listen( { allowUpgrades: false, transports: ['polling'] }, function (p) {
+        port = p;
+
+        socket = new eioc.Socket('ws://localhost:' + port
+          , { transports: ['polling'], forceJSONP: true, upgrade: false });
+
+        done();
+      });
+    });
+
+    it('should arrive from client to server and back (pollingJSONP)', function (done) {
+      engine.on('connection', function (conn) {
+        conn.on('message', function (msg) {
+          conn.send('a');
+        });
+      });
+
+      socket.on('open', function () {
+        socket.send('a');
+        socket.on('message', function (msg) {
+          expect(socket.transport.query.j).to.not.be(undefined);
+          expect(msg).to.be('a');
+          done();
+        });
+      });
+    });
+
+    it('should not fail JSON.parse for stringified messages', function (done) {
+      engine.on('connection', function(conn) {
+        conn.on('message', function(message) {
+          expect(JSON.parse(message)).to.be.eql({test : 'a\r\nb\n\n\n\nc'});
+          done();
+        });
+      });
+      socket.on('open', function () {
+        socket.send(JSON.stringify({test : 'a\r\nb\n\n\n\nc'}));
+      });
+    });
+
+    it('should parse newlines in message correctly', function (done) {
+      engine.on('connection', function(conn) {
+        conn.on('message', function(message) {
+          expect(message).to.be.equal('a\r\nb\n\n\n\nc');
+          done();
+        });
+      });
+      socket.on('open', function () {
+        socket.send('a\r\nb\n\n\n\nc');
+      });
+    });
+
+    it('should arrive from server to client and back with binary data (pollingJSONP)', function(done) {
+      var binaryData = new Buffer(5);
+      for (var i = 0; i < 5; i++) binaryData[i] = i;
+      engine.on('connection', function (conn) {
+        conn.on('message', function (msg) {
+          conn.send(msg);
+        });
+      });
+
+      socket.on('open', function() {
+        socket.send(binaryData);
+        socket.on('message', function (msg) {
+          for (var i = 0; i < msg.length; i++) expect(msg[i]).to.be(i);
+          done();
+        });
+      });
+    });
+  });
+
+  describe('close', function () {
+    it('should trigger when server closes a client', function (done) {
+      var engine = listen( { allowUpgrades: false, transports: ['polling'] }, function (port) {
+          var socket = new eioc.Socket('ws://localhost:' + port
+            , { transports: ['polling'], forceJSONP: true, upgrade: false })
+            , total = 2;
+
+        engine.on('connection', function (conn) {
+          conn.on('close', function (reason) {
+            expect(reason).to.be('forced close');
+            --total || done();
+          });
+          setTimeout(function () {
+            conn.close();
+          }, 10);
+        });
+
+        socket.on('open', function () {
+          socket.on('close', function (reason) {
+            expect(reason).to.be('transport close');
+            --total || done();
+          });
+        });
+      });
+    });
+
+    it('should trigger when client closes', function (done) {
+      var engine = listen( { allowUpgrades: false, transports: ['polling'] }, function (port) {
+        var socket = new eioc.Socket('ws://localhost:' + port
+          , { transports: ['polling'], forceJSONP: true, upgrade: false })
+          , total = 2;
+
+        engine.on('connection', function (conn) {
+          conn.on('close', function (reason) {
+            expect(reason).to.be('transport close');
+            --total || done();
+          });
+        });
+
+        socket.on('open', function () {
+          socket.send('a');
+          socket.on('close', function (reason) {
+            expect(reason).to.be('forced close');
+            --total || done();
+          });
+
+          setTimeout(function () {
+            socket.close();
+          }, 10);
+        });
+      });
+    });
+  });
+});
diff --git a/test/server.js b/test/server.js
new file mode 100644
index 0000000..fa07772
--- /dev/null
+++ b/test/server.js
@@ -0,0 +1,2010 @@
+
+/**
+ * Tests dependencies.
+ */
+
+var http = require('http');
+var https = require('https');
+var fs = require('fs');
+var eio = require('..');
+var eioc = require('engine.io-client');
+var listen = require('./common').listen;
+var expect = require('expect.js');
+var request = require('superagent');
+var WebSocket = require('ws');
+
+/**
+ * Tests.
+ */
+
+describe('server', function () {
+
+  describe('verification', function () {
+    it('should disallow non-existent transports', function (done) {
+      var engine = listen(function (port) {
+        request.get('http://localhost:%d/engine.io/default/'.s(port))
+          .query({ transport: 'tobi' }) // no tobi transport - outrageous
+          .end(function (res) {
+            expect(res.status).to.be(400);
+            expect(res.body.code).to.be(0);
+            expect(res.body.message).to.be('Transport unknown');
+            expect(res.header['access-control-allow-origin']).to.be('*');
+            done();
+          });
+      });
+    });
+
+    it('should disallow `constructor` as transports', function (done) {
+      // make sure we check for actual properties - not those present on every {}
+      var engine = listen(function (port) {
+        request.get('http://localhost:%d/engine.io/default/'.s(port))
+          .set('Origin', 'http://engine.io')
+          .query({ transport: 'constructor' })
+          .end(function (res) {
+            expect(res.status).to.be(400);
+            expect(res.body.code).to.be(0);
+            expect(res.body.message).to.be('Transport unknown');
+            expect(res.header['access-control-allow-credentials']).to.be('true');
+            expect(res.header['access-control-allow-origin']).to.be('http://engine.io');
+            done();
+          });
+      });
+    });
+
+    it('should disallow non-existent sids', function (done) {
+      var engine = listen(function (port) {
+        request.get('http://localhost:%d/engine.io/default/'.s(port))
+          .set('Origin', 'http://engine.io')
+          .query({ transport: 'polling', sid: 'test' })
+          .end(function (res) {
+            expect(res.status).to.be(400);
+            expect(res.body.code).to.be(1);
+            expect(res.body.message).to.be('Session ID unknown');
+            expect(res.header['access-control-allow-credentials']).to.be('true');
+            expect(res.header['access-control-allow-origin']).to.be('http://engine.io');
+            done();
+          });
+      });
+    });
+  });
+
+  describe('handshake', function () {
+    it('should send the io cookie', function (done) {
+      var engine = listen(function (port) {
+        request.get('http://localhost:%d/engine.io/default/'.s(port))
+          .query({ transport: 'polling', b64: 1 })
+          .end(function (res) {
+            // hack-obtain sid
+            var sid = res.text.match(/"sid":"([^"]+)"/)[1];
+            expect(res.headers['set-cookie'][0]).to.be('io=' + sid);
+            done();
+          });
+      });
+    });
+
+    it('should send the io cookie custom name', function (done) {
+      var engine = listen({ cookie: 'woot' }, function (port) {
+        request.get('http://localhost:%d/engine.io/default/'.s(port))
+          .query({ transport: 'polling', b64: 1 })
+          .end(function (res) {
+            var sid = res.text.match(/"sid":"([^"]+)"/)[1];
+            expect(res.headers['set-cookie'][0]).to.be('woot=' + sid);
+            done();
+          });
+      });
+    });
+
+    it('should not send the io cookie', function (done) {
+      var engine = listen({ cookie: false }, function (port) {
+        request.get('http://localhost:%d/engine.io/default/'.s(port))
+          .query({ transport: 'polling' })
+          .end(function (res) {
+            expect(res.headers['set-cookie']).to.be(undefined);
+            done();
+          });
+      });
+    });
+
+    it('should register a new client', function (done) {
+      var engine = listen({ allowUpgrades: false }, function (port) {
+        expect(Object.keys(engine.clients)).to.have.length(0);
+        expect(engine.clientsCount).to.be(0);
+
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port));
+        socket.on('open', function () {
+          expect(Object.keys(engine.clients)).to.have.length(1);
+          expect(engine.clientsCount).to.be(1);
+          done();
+        });
+      });
+    });
+
+    it('should exchange handshake data', function (done) {
+      var engine = listen({ allowUpgrades: false }, function (port) {
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port));
+        socket.on('handshake', function (obj) {
+          expect(obj.sid).to.be.a('string');
+          expect(obj.pingTimeout).to.be.a('number');
+          expect(obj.upgrades).to.be.an('array');
+          done();
+        });
+      });
+    });
+
+    it('should allow custom ping timeouts', function (done) {
+      var engine = listen({ allowUpgrades: false, pingTimeout: 123 }, function (port) {
+        var socket = new eioc.Socket('http://localhost:%d'.s(port));
+        socket.on('handshake', function (obj) {
+          expect(obj.pingTimeout).to.be(123);
+          done();
+        });
+      });
+    });
+
+    it('should trigger a connection event with a Socket', function (done) {
+      var engine = listen({ allowUpgrades: false }, function (port) {
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port));
+        engine.on('connection', function (socket) {
+          expect(socket).to.be.an(eio.Socket);
+          done();
+        });
+      });
+    });
+
+    it('should open with polling by default', function (done) {
+      var engine = listen({ allowUpgrades: false }, function (port) {
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port));
+        engine.on('connection', function (socket) {
+          expect(socket.transport.name).to.be('polling');
+          done();
+        });
+      });
+    });
+
+    it('should be able to open with ws directly', function (done) {
+      var engine = listen({ transports: ['websocket'] }, function (port) {
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['websocket'] });
+        engine.on('connection', function (socket) {
+          expect(socket.transport.name).to.be('websocket');
+          done();
+        });
+      });
+    });
+
+    it('should not suggest any upgrades for websocket', function (done) {
+      var engine = listen({ transports: ['websocket'] }, function (port) {
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['websocket'] });
+        socket.on('handshake', function (obj) {
+          expect(obj.upgrades).to.have.length(0);
+          done();
+        });
+      });
+    });
+
+    it('should not suggest upgrades when none are availble', function (done) {
+      var engine = listen({ transports: ['polling'] }, function (port) {
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port), { });
+        socket.on('handshake', function (obj) {
+          expect(obj.upgrades).to.have.length(0);
+          done();
+        });
+      });
+    });
+
+    it('should only suggest available upgrades', function (done) {
+      var engine = listen({ transports: ['polling'] }, function (port) {
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port), { });
+        socket.on('handshake', function (obj) {
+          expect(obj.upgrades).to.have.length(0);
+          done();
+        });
+      });
+    });
+
+    it('should suggest all upgrades when no transports are disabled', function (done) {
+      var engine = listen({}, function (port) {
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port), { });
+        socket.on('handshake', function (obj) {
+          expect(obj.upgrades).to.have.length(1);
+          expect(obj.upgrades).to.have.contain('websocket');
+          done();
+        });
+      });
+    });
+
+    it('default to polling when proxy doesn\'t support websocket', function (done) {
+      var engine = listen({ allowUpgrades: false }, function (port) {
+
+        engine.on('connection', function (socket) {
+          socket.on('message', function (msg) {
+            if ('echo' == msg) socket.send(msg);
+          });
+        });
+
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port));
+        socket.on('open', function () {
+          request.get('http://localhost:%d/engine.io/'.s(port))
+          .set({ connection: 'close' })
+          .query({ transport: 'websocket', sid: socket.id })
+          .end(function (err, res) {
+            expect(err).to.be(null);
+            expect(res.status).to.be(400);
+            expect(res.body.code).to.be(3);
+            socket.send('echo');
+            socket.on('message', function (msg) {
+              expect(msg).to.be('echo');
+              done();
+            });
+          });
+        });
+      });
+    });
+
+    it('should allow arbitrary data through query string', function (done) {
+      var engine = listen({ allowUpgrades: false }, function (port) {
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port), { query: { a: 'b' } });
+        engine.on('connection', function (conn) {
+          expect(conn.request._query).to.have.keys('transport', 'a');
+          expect(conn.request._query.a).to.be('b');
+          done();
+        });
+      });
+    });
+
+    it('should allow data through query string in uri', function (done) {
+      var engine = listen({ allowUpgrades: false }, function (port) {
+        var socket = new eioc.Socket('ws://localhost:%d?a=b&c=d'.s(port));
+        engine.on('connection', function (conn) {
+          expect(conn.request._query.EIO).to.be.a('string');
+          expect(conn.request._query.a).to.be('b');
+          expect(conn.request._query.c).to.be('d');
+          done();
+        });
+      });
+    });
+
+
+
+    it('should disallow bad requests', function (done) {
+      var engine = listen(function (port) {
+        request.get('http://localhost:%d/engine.io/default/'.s(port))
+          .set('Origin', 'http://engine.io')
+          .query({ transport: 'websocket' })
+          .end(function (res) {
+            expect(res.status).to.be(400);
+            expect(res.body.code).to.be(3);
+            expect(res.body.message).to.be('Bad request');
+            expect(res.header['access-control-allow-credentials']).to.be('true');
+            expect(res.header['access-control-allow-origin']).to.be('http://engine.io');
+            done();
+          });
+      });
+    });
+  });
+
+  describe('close', function () {
+    it('should be able to access non-empty writeBuffer at closing (server)', function(done) {
+      var opts = {allowUpgrades: false};
+      var engine = listen(opts, function (port) {
+        var socket = new eioc.Socket('http://localhost:%d'.s(port));
+        engine.on('connection', function (conn) {
+          conn.on('close', function (reason) {
+            expect(conn.writeBuffer.length).to.be(1);
+            setTimeout(function () {
+              expect(conn.writeBuffer.length).to.be(0); // writeBuffer has been cleared
+            }, 10);
+            done();
+          });
+          conn.writeBuffer.push({ type: 'message', data: 'foo'});
+          conn.onError('');
+        });
+      });
+    });
+
+    it('should be able to access non-empty writeBuffer at closing (client)', function(done) {
+      var opts = {allowUpgrades: false};
+      var engine = listen(opts, function (port) {
+        var socket = new eioc.Socket('http://localhost:%d'.s(port));
+        socket.on('open', function() {
+          socket.on('close', function (reason) {
+            expect(socket.writeBuffer.length).to.be(1);
+            expect(socket.callbackBuffer.length).to.be(1);
+            setTimeout(function() {
+              expect(socket.writeBuffer.length).to.be(0);
+              expect(socket.callbackBuffer.length).to.be(0);
+            }, 10);
+            done();
+          });
+          socket.writeBuffer.push({ type: 'message', data: 'foo'});
+          socket.callbackBuffer.push(function() {});
+          socket.onError('');
+        });
+      });
+    });
+
+    it('should trigger on server if the client does not pong', function (done) {
+      var opts = { allowUpgrades: false, pingInterval: 5, pingTimeout: 5 };
+      var engine = listen(opts, function (port) {
+        var socket = new eioc.Socket('http://localhost:%d'.s(port));
+        socket.sendPacket = function (){};
+        engine.on('connection', function (conn) {
+          conn.on('close', function (reason) {
+            expect(reason).to.be('ping timeout');
+            done();
+          });
+        });
+      });
+    });
+
+    it('should trigger on server even when there is no outstanding polling request (GH-198)', function (done) {
+      var opts = { allowUpgrades: false, pingInterval: 500, pingTimeout: 500 };
+      var engine = listen(opts, function (port) {
+        var socket = new eioc.Socket('http://localhost:%d'.s(port));
+        engine.on('connection', function (conn) {
+          conn.on('close', function (reason) {
+            expect(reason).to.be('ping timeout');
+            done();
+          });
+          // client abruptly disconnects, no polling request on this tick since we've just connected
+          socket.sendPacket = socket.onPacket = function (){};
+          socket.close();
+          // then server app tries to close the socket, since client disappeared
+          conn.close();
+        });
+      });
+    });
+
+    it('should trigger on client if server does not meet ping timeout', function (done) {
+      var opts = { allowUpgrades: false, pingInterval: 50, pingTimeout: 30 };
+      var engine = listen(opts, function (port) {
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port));
+        socket.on('open', function () {
+          // override onPacket to simulate an inactive server after handshake
+          socket.onPacket = function(){};
+          socket.on('close', function (reason, err) {
+            expect(reason).to.be('ping timeout');
+            done();
+          });
+        });
+      });
+    });
+
+    it('should trigger on both ends upon ping timeout', function (done) {
+      var opts = { allowUpgrades: false, pingTimeout: 10, pingInterval: 10 };
+      var engine = listen(opts, function (port) {
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port))
+          , total = 2;
+
+        function onClose (reason, err) {
+          expect(reason).to.be('ping timeout');
+          --total || done();
+        }
+
+        engine.on('connection', function (conn) {
+          conn.on('close', onClose);
+        });
+
+        socket.on('open', function () {
+          // override onPacket to simulate an inactive server after handshake
+          socket.onPacket = socket.sendPacket = function(){};
+          socket.on('close', onClose);
+        });
+      });
+    });
+
+    it('should trigger when server closes a client', function (done) {
+      var engine = listen({ allowUpgrades: false }, function (port) {
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port))
+          , total = 2;
+
+        engine.on('connection', function (conn) {
+          conn.on('close', function (reason) {
+            expect(reason).to.be('forced close');
+            --total || done();
+          });
+          setTimeout(function () {
+            conn.close();
+          }, 10);
+        });
+
+        socket.on('open', function () {
+          socket.on('close', function (reason) {
+            expect(reason).to.be('transport close');
+            --total || done();
+          });
+        });
+      });
+    });
+
+    it('should trigger when server closes a client (ws)', function (done) {
+      var opts = { allowUpgrades: false, transports: ['websocket'] };
+      var engine = listen(opts, function (port) {
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['websocket'] })
+          , total = 2;
+
+        engine.on('connection', function (conn) {
+          conn.on('close', function (reason) {
+            expect(reason).to.be('forced close');
+            --total || done();
+          });
+          setTimeout(function () {
+            conn.close();
+          }, 10);
+        });
+
+        socket.on('open', function () {
+          socket.on('close', function (reason) {
+            expect(reason).to.be('transport close');
+            --total || done();
+          });
+        });
+      });
+    });
+
+    it('should trigger when client closes', function (done) {
+      var engine = listen({ allowUpgrades: false }, function (port) {
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port))
+          , total = 2;
+
+        engine.on('connection', function (conn) {
+          conn.on('close', function (reason) {
+            expect(reason).to.be('transport close');
+            --total || done();
+          });
+        });
+
+        socket.on('open', function () {
+          socket.on('close', function (reason) {
+            expect(reason).to.be('forced close');
+            --total || done();
+          });
+
+          setTimeout(function () {
+            socket.close();
+          }, 10);
+        });
+      });
+    });
+
+    it('should trigger when client closes (ws)', function (done) {
+      var opts = { allowUpgrades: false, transports: ['websocket'] };
+      var engine = listen(opts, function (port) {
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['websocket'] })
+          , total = 2;
+
+        engine.on('connection', function (conn) {
+          conn.on('close', function (reason) {
+            expect(reason).to.be('transport close');
+            --total || done();
+          });
+        });
+
+        socket.on('open', function () {
+          socket.on('close', function (reason) {
+            expect(reason).to.be('forced close');
+            --total || done();
+          });
+
+          setTimeout(function () {
+            socket.close();
+          }, 10);
+        });
+      });
+    });
+
+    it('should trigger when calling socket.close() in payload', function (done) {
+      var engine = listen({ allowUpgrades: false }, function (port) {
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port));
+
+        engine.on('connection', function (conn) {
+          conn.send(null, function () {socket.close();});
+          conn.send('this should not be handled');
+
+          conn.on('close', function (reason) {
+            expect(reason).to.be('transport close');
+            done();
+          });
+        });
+
+        socket.on('open', function () {
+          socket.on('message', function (msg) {
+            expect(msg).to.not.be('this should not be handled');
+          });
+
+          socket.on('close', function (reason) {
+            expect(reason).to.be('forced close');
+          });
+        });
+      });
+    });
+
+    it('should abort upgrade if socket is closed (GH-35)', function (done) {
+      var engine = listen({ allowUpgrades: true }, function (port) {
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port));
+        socket.on('open', function () {
+          socket.close();
+          // we wait until complete to see if we get an uncaught EPIPE
+          setTimeout(function(){
+            done();
+          }, 100);
+        });
+      });
+    });
+
+    it('should trigger if a poll request is ongoing and the underlying ' +
+       'socket closes, as in a browser tab close', function ($done) {
+      var engine = listen({ allowUpgrades: false }, function (port) {
+        // hack to access the sockets created by node-xmlhttprequest
+        // see: https://github.com/driverdan/node-XMLHttpRequest/issues/44
+        var request = require('http').request;
+        var sockets = [];
+        http.request = function(opts){
+          var req = request.apply(null, arguments);
+          req.on('socket', function(socket){
+            sockets.push(socket);
+          });
+          return req;
+        };
+
+        function done(){
+          http.request = request;
+          $done();
+        }
+
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port))
+          , serverSocket;
+
+        engine.on('connection', function(s){
+          serverSocket = s;
+        });
+
+        socket.transport.on('poll', function(){
+          // we set a timer to wait for the request to actually reach
+          setTimeout(function(){
+            // at this time server's `connection` should have been fired
+            expect(serverSocket).to.be.an('object');
+
+            // OPENED readyState is expected - we qre actually polling
+            expect(socket.transport.pollXhr.xhr.readyState).to.be(1);
+
+            // 2 requests sent to the server over an unique port means
+            // we should have been assigned 2 sockets
+            expect(sockets.length).to.be(2);
+
+            // expect the socket to be open at this point
+            expect(serverSocket.readyState).to.be('open');
+
+            // kill the underlying connection
+            sockets[1].end();
+            serverSocket.on('close', function(reason, err){
+              expect(reason).to.be('transport error');
+              expect(err.message).to.be('poll connection closed prematurely');
+              done();
+            });
+          }, 50);
+        });
+      });
+    });
+
+    it('should not trigger with connection: close header', function($done){
+      var engine = listen({ allowUpgrades: false }, function(port){
+        // intercept requests to add connection: close
+        var request = http.request;
+        http.request = function(){
+          var opts = arguments[0];
+          opts.headers = opts.headers || {};
+          opts.headers.Connection = 'close';
+          return request.apply(this, arguments);
+        };
+
+        function done(){
+          http.request = request;
+          $done();
+        }
+
+        engine.on('connection', function(socket){
+          socket.on('message', function(msg){
+            expect(msg).to.equal('test');
+            socket.send('woot');
+          });
+        });
+
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port));
+        socket.on('open', function(){
+          socket.send('test');
+        });
+        socket.on('message', function(msg){
+          expect(msg).to.be('woot');
+          done();
+        });
+      });
+    });
+
+    it('should not trigger early with connection `ping timeout`' +
+       'after post handshake timeout', function (done) {
+      // first timeout should trigger after `pingInterval + pingTimeout`,
+      // not just `pingTimeout`.
+      var opts = { allowUpgrades: false, pingInterval: 300, pingTimeout: 100 };
+      var engine = listen(opts, function (port) {
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port));
+        var clientCloseReason = null;
+
+        socket.on('handshake', function() {
+          socket.onPacket = function(){};
+        });
+        socket.on('open', function () {
+          socket.on('close', function (reason) {
+            clientCloseReason = reason;
+          });
+        });
+
+        setTimeout(function() {
+          expect(clientCloseReason).to.be(null);
+          done();
+        }, 200);
+      });
+    });
+
+    it('should not trigger early with connection `ping timeout` ' +
+       'after post ping timeout', function (done) {
+      // ping timeout should trigger after `pingInterval + pingTimeout`,
+      // not just `pingTimeout`.
+      var opts = { allowUpgrades: false, pingInterval: 80, pingTimeout: 50 };
+      var engine = listen(opts, function (port) {
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port));
+        var clientCloseReason = null;
+
+        engine.on('connection', function(conn){
+          conn.on('heartbeat', function() {
+            conn.onPacket = function(){};
+          });
+        });
+
+        socket.on('open', function () {
+          socket.on('close', function (reason) {
+            clientCloseReason = reason;
+          });
+        });
+
+        setTimeout(function() {
+          expect(clientCloseReason).to.be(null);
+          done();
+        }, 100);
+      });
+    });
+
+    it('should trigger early with connection `transport close` ' +
+       'after missing pong', function (done) {
+      // ping timeout should trigger after `pingInterval + pingTimeout`,
+      // not just `pingTimeout`.
+      var opts = { allowUpgrades: false, pingInterval: 80, pingTimeout: 50 };
+      var engine = listen(opts, function (port) {
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port));
+        var clientCloseReason = null;
+
+        socket.on('open', function () {
+          socket.on('close', function (reason) {
+            clientCloseReason = reason;
+          });
+        });
+
+        engine.on('connection', function(conn){
+          conn.on('heartbeat', function() {
+            setTimeout(function() {
+              conn.close();
+            }, 20);
+            setTimeout(function() {
+              expect(clientCloseReason).to.be('transport close');
+              done();
+            }, 100);
+          });
+        });
+      });
+    });
+
+    it('should trigger with connection `ping timeout` ' +
+       'after `pingInterval + pingTimeout`', function (done) {
+      var opts = { allowUpgrades: false, pingInterval: 300, pingTimeout: 100 };
+      var engine = listen(opts, function (port) {
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port));
+        var clientCloseReason = null;
+
+        socket.on('open', function () {
+          socket.on('close', function (reason) {
+            clientCloseReason = reason;
+          });
+        });
+
+        engine.on('connection', function(conn){
+          conn.once('heartbeat', function() {
+            setTimeout(function() {
+              socket.onPacket = function(){};
+              expect(clientCloseReason).to.be(null);
+            }, 150);
+            setTimeout(function() {
+              expect(clientCloseReason).to.be(null);
+            }, 350);
+            setTimeout(function() {
+              expect(clientCloseReason).to.be('ping timeout');
+              done();
+            }, 500);
+          });
+        });
+      });
+    });
+
+    it('should abort the polling data request if it is ' +
+       'in progress', function (done) {
+      var engine = listen({ transports: [ 'polling' ] }, function (port) {
+        var socket = new eioc.Socket('http://localhost:%d'.s(port));
+
+        engine.on('connection', function (conn) {
+          var onDataRequest = conn.transport.onDataRequest;
+          conn.transport.onDataRequest = function (req, res) {
+            engine.httpServer.close(done);
+            onDataRequest.call(conn.transport, req, res);
+            req.removeAllListeners();
+            conn.close();
+          };
+        });
+
+        socket.on('open', function () {
+          socket.send('test');
+        });
+      });
+    });
+
+    // tests https://github.com/LearnBoost/engine.io-client/issues/207
+    // websocket test, transport error
+    it('should trigger transport close before open for ws', function(done){
+      var opts = { transports: ['websocket'] };
+      var engine = listen(opts, function (port) {
+        var socket = new eioc.Socket('ws://invalidserver:%d'.s(port));
+        socket.on('open', function(){
+          done(new Error('Test invalidation'));
+        });
+        socket.on('close', function(reason){
+          expect(reason).to.be('transport error');
+          done();
+        });
+      });
+    });
+
+    // tests https://github.com/LearnBoost/engine.io-client/issues/207
+    // polling test, transport error
+    it('should trigger transport close before open for xhr', function(done){
+      var opts = { transports: ['polling'] };
+      var engine = listen(opts, function (port) {
+        var socket = new eioc.Socket('http://invalidserver:%d'.s(port));
+        socket.on('open', function(){
+          done(new Error('Test invalidation'));
+        });
+        socket.on('close', function(reason){
+          expect(reason).to.be('transport error');
+          done();
+        });
+      });
+    });
+
+    // tests https://github.com/LearnBoost/engine.io-client/issues/207
+    // websocket test, force close
+    it('should trigger force close before open for ws', function(done){
+      var opts = { transports: ['websocket'] };
+      var engine = listen(opts, function (port) {
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port));
+        socket.on('open', function(){
+          done(new Error('Test invalidation'));
+        });
+        socket.on('close', function(reason){
+          expect(reason).to.be('forced close');
+          done();
+        });
+        socket.close();
+      });
+    });
+
+    // tests https://github.com/LearnBoost/engine.io-client/issues/207
+    // polling test, force close
+    it('should trigger force close before open for xhr', function(done){
+      var opts = { transports: ['polling'] };
+      var engine = listen(opts, function (port) {
+        var socket = new eioc.Socket('http://localhost:%d'.s(port));
+        socket.on('open', function(){
+          done(new Error('Test invalidation'));
+        });
+        socket.on('close', function(reason){
+          expect(reason).to.be('forced close');
+          done();
+        });
+        socket.close();
+      });
+    });
+
+  });
+
+  describe('messages', function () {
+    this.timeout(5000);
+
+    it('should arrive from server to client', function (done) {
+      var engine = listen({ allowUpgrades: false }, function (port) {
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port));
+        engine.on('connection', function (conn) {
+          conn.send('a');
+        });
+        socket.on('open', function () {
+          socket.on('message', function (msg) {
+            expect(msg).to.be('a');
+            done();
+          });
+        });
+      });
+    });
+
+    it('should arrive from server to client (multiple)', function (done) {
+      var engine = listen({ allowUpgrades: false }, function (port) {
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port))
+          , expected = ['a', 'b', 'c']
+          , i = 0;
+
+        engine.on('connection', function (conn) {
+          conn.send('a');
+          // we use set timeouts to ensure the messages are delivered as part
+          // of different.
+          setTimeout(function () {
+            conn.send('b');
+
+            setTimeout(function () {
+              // here we make sure we buffer both the close packet and
+              // a regular packet
+              conn.send('c');
+              conn.close();
+            }, 50);
+          }, 50);
+
+          conn.on('close', function () {
+            // since close fires right after the buffer is drained
+            setTimeout(function () {
+              expect(i).to.be(3);
+              done();
+            }, 50);
+          });
+        });
+        socket.on('open', function () {
+          socket.on('message', function (msg) {
+            expect(msg).to.be(expected[i++]);
+          });
+        });
+      });
+    });
+
+    it('should not be receiving data when getting a message longer than maxHttpBufferSize when polling', function(done) {
+      var opts = { allowUpgrades: false, transports: ['polling'], maxHttpBufferSize: 5 };
+      var engine = listen(opts, function (port) {
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port));
+        engine.on('connection', function (conn) {
+          conn.on('message', function(msg) {
+            console.log(msg);
+          });
+        });
+        socket.on('open', function () {
+          socket.send('aasdasdakjhasdkjhasdkjhasdkjhasdkjhasdkjhasdkjha');
+        });
+      });
+      setTimeout(done, 1000);
+    });
+
+    it('should receive data when getting a message shorter than maxHttpBufferSize when polling', function(done) {
+      var opts = { allowUpgrades: false, transports: ['polling'], maxHttpBufferSize: 5 };
+      var engine = listen(opts, function (port) {
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port));
+        engine.on('connection', function (conn) {
+          conn.on('message', function(msg) {
+            expect(msg).to.be('a');
+            done();
+          });
+        });
+        socket.on('open', function () {
+          socket.send('a');
+        });
+      });
+    });
+
+
+    it('should arrive from server to client (ws)', function (done) {
+      var opts = { allowUpgrades: false, transports: ['websocket'] };
+      var engine = listen(opts, function (port) {
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['websocket'] });
+        engine.on('connection', function (conn) {
+          conn.send('a');
+        });
+        socket.on('open', function () {
+          socket.on('message', function (msg) {
+            expect(msg).to.be('a');
+            done();
+          });
+        });
+      });
+    });
+
+    it('should arrive from server to client (multiple, ws)', function (done) {
+      var opts = { allowUpgrades: false, transports: ['websocket'] };
+      var engine = listen(opts, function (port) {
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['websocket'] })
+          , expected = ['a', 'b', 'c']
+          , i = 0;
+
+        engine.on('connection', function (conn) {
+          conn.send('a');
+          setTimeout(function () {
+            conn.send('b');
+            setTimeout(function () {
+              conn.send('c');
+              conn.close();
+            }, 50);
+          }, 50);
+          conn.on('close', function () {
+            setTimeout(function () {
+              expect(i).to.be(3);
+              done();
+            }, 50);
+          });
+        });
+
+        socket.on('open', function () {
+          socket.on('message', function (msg) {
+            expect(msg).to.be(expected[i++]);
+          });
+        });
+      });
+    });
+
+    it('should arrive from server to client (multiple, no delay, ws)', function (done) {
+      var opts = { allowUpgrades: false, transports: ['websocket'] };
+      var engine = listen(opts, function (port) {
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['websocket'] })
+          , expected = ['a', 'b', 'c']
+          , i = 0;
+
+        engine.on('connection', function (conn) {
+          conn.on('close', function () {
+            setTimeout(function () {
+              expect(i).to.be(3);
+              done();
+            }, 50);
+          });
+          conn.send('a');
+          conn.send('b');
+          conn.send('c');
+          conn.close();
+        });
+
+        socket.on('open', function () {
+          socket.on('message', function (msg) {
+            expect(msg).to.be(expected[i++]);
+          });
+        });
+      });
+    });
+
+    it('should arrive when binary data is sent as Int8Array (ws)', function (done) {
+      var binaryData = new Int8Array(5);
+      for (var i = 0; i < binaryData.length; i++) {
+        binaryData[i] = i;
+      }
+
+      var opts = { allowUpgrades: false, transports: ['websocket'] };
+      var engine = listen(opts, function(port) {
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['websocket'] });
+
+        engine.on('connection', function (conn) {
+          conn.send(binaryData);
+        });
+
+        socket.on('open', function () {
+          socket.on('message', function(msg) {
+            for (var i = 0; i < binaryData.length; i++) {
+              var num = msg.readInt8(i);
+              expect(num).to.be(i);
+            }
+            done();
+          });
+        });
+      });
+    });
+
+    it('should arrive when binary data is sent as Int32Array (ws)', function (done) {
+      var binaryData = new Int32Array(5);
+      for (var i = 0; i < binaryData.length; i++) {
+        binaryData[i] = (i + 100) * 9823;
+      }
+
+      var opts = { allowUpgrades: false, transports: ['websocket'] };
+      var engine = listen(opts, function(port) {
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['websocket'] });
+
+        engine.on('connection', function (conn) {
+          conn.send(binaryData);
+        });
+
+        socket.on('open', function () {
+          socket.on('message', function(msg) {
+            for (var i = 0, ii = 0; i < binaryData.length; i += 4, ii++) {
+              var num = msg.readInt32LE(i);
+              expect(num).to.be((ii + 100) * 9823);
+            }
+            done();
+          });
+        });
+      });
+    });
+
+    it('should arrive when binary data is sent as Int32Array, given as ArrayBuffer(ws)', function (done) {
+      var binaryData = new Int32Array(5);
+      for (var i = 0; i < binaryData.length; i++) {
+        binaryData[i] = (i + 100) * 9823;
+      }
+
+      var opts = { allowUpgrades: false, transports: ['websocket'] };
+      var engine = listen(opts, function(port) {
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['websocket'] });
+
+        engine.on('connection', function (conn) {
+          conn.send(binaryData.buffer);
+        });
+
+        socket.on('open', function () {
+          socket.on('message', function(msg) {
+            for (var i = 0, ii = 0; i < binaryData.length; i += 4, ii++) {
+              var num = msg.readInt32LE(i);
+              expect(num).to.be((ii + 100) * 9823);
+            }
+            done();
+          });
+        });
+      });
+    });
+
+    it('should arrive when binary data is sent as Buffer (ws)', function (done) {
+      var binaryData = Buffer(5);
+      for (var i = 0; i < binaryData.length; i++) {
+        binaryData.writeInt8(i, i);
+      }
+
+      var opts = { allowUpgrades: false, transports: ['websocket'] };
+      var engine = listen(opts, function(port) {
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['websocket'] });
+
+        engine.on('connection', function (conn) {
+          conn.send(binaryData);
+        });
+
+        socket.on('open', function () {
+          socket.on('message', function(msg) {
+            for (var i = 0; i < binaryData.length; i++) {
+              var num = msg.readInt8(i);
+              expect(num).to.be(i);
+            }
+            done();
+          });
+        });
+      });
+    });
+
+    it('should arrive when binary data sent as Buffer (polling)', function (done) {
+      var binaryData = Buffer(5);
+      for (var i = 0; i < binaryData.length; i++) {
+        binaryData.writeInt8(i, i);
+      }
+
+      var opts = { allowUpgrades: false, transports: ['polling'] };
+      var engine = listen(opts, function(port) {
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['polling'] });
+
+        engine.on('connection', function (conn) {
+          conn.send(binaryData);
+        });
+
+        socket.on('open', function() {
+          socket.on('message', function(msg) {
+            for (var i = 0; i < binaryData.length; i++) {
+              var num = msg.readInt8(i);
+              expect(num).to.be(i);
+            }
+
+            done();
+          });
+        });
+      });
+    });
+
+    it('should arrive as ArrayBuffer if requested when binary data sent as Buffer (ws)', function (done) {
+      var binaryData = Buffer(5);
+      for (var i = 0; i < binaryData.length; i++) {
+        binaryData.writeInt8(i, i);
+      }
+
+      var opts = { allowUpgrades: false, transports: ['websocket'] };
+      var engine = listen(opts, function(port) {
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['websocket'] });
+        socket.binaryType = 'arraybuffer';
+
+        engine.on('connection', function (conn) {
+          conn.send(binaryData);
+        });
+
+        socket.on('open', function() {
+          socket.on('message', function(msg) {
+            expect(msg instanceof ArrayBuffer).to.be(true);
+            var intArray = new Int8Array(msg);
+            for (var i = 0; i < binaryData.length; i++) {
+              expect(intArray[i]).to.be(i);
+            }
+
+            done();
+          });
+        });
+      });
+    });
+
+    it('should arrive as ArrayBuffer if requested when binary data sent as Buffer (polling)', function (done) {
+      var binaryData = Buffer(5);
+      for (var i = 0; i < binaryData.length; i++) {
+        binaryData.writeInt8(i, i);
+      }
+
+      var opts = { allowUpgrades: false, transports: ['polling'] };
+      var engine = listen(opts, function(port) {
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['polling'] });
+        socket.binaryType = 'arraybuffer';
+
+        engine.on('connection', function (conn) {
+          conn.send(binaryData);
+        });
+
+        socket.on('open', function() {
+          socket.on('message', function(msg) {
+            expect(msg instanceof ArrayBuffer).to.be(true);
+            var intArray = new Int8Array(msg);
+            for (var i = 0; i < binaryData.length; i++) {
+              expect(intArray[i]).to.be(i);
+            }
+
+            done();
+          });
+        });
+      });
+    });
+
+
+    it('should trigger a flush/drain event', function(done){
+      var engine = listen({ allowUpgrades: false }, function(port){
+        engine.on('connection', function(socket){
+          var totalEvents = 4;
+
+          engine.on('flush', function(sock, buf){
+            expect(sock).to.be(socket);
+            expect(buf).to.be.an('array');
+            --totalEvents || done();
+          });
+          socket.on('flush', function(buf){
+            expect(buf).to.be.an('array');
+            --totalEvents || done();
+          });
+
+          engine.on('drain', function(sock){
+            expect(sock).to.be(socket);
+            expect(socket.writeBuffer.length).to.be(0);
+            --totalEvents || done();
+          });
+          socket.on('drain', function(){
+            expect(socket.writeBuffer.length).to.be(0);
+            --totalEvents || done();
+          });
+
+          socket.send('aaaa');
+        });
+
+        new eioc.Socket('ws://localhost:%d'.s(port));
+      });
+    });
+
+    it('should interleave with pongs if many messages buffered ' +
+       'after connection open', function (done) {
+      this.slow(4000);
+      this.timeout(8000);
+
+      var opts = {
+        transports: ['websocket'],
+        pingInterval: 200,
+        pingTimeout: 100
+      };
+
+      var engine = listen(opts, function (port) {
+        var messageCount = 100;
+        var messagePayload = new Array(256 * 256).join('a');
+        var connection = null;
+        engine.on('connection', function (conn) {
+          connection = conn;
+        });
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['websocket'] });
+        socket.on('open', function () {
+          for (var i=0;i<messageCount;i++) {
+//            connection.send('message: ' + i);   // works
+            connection.send(messagePayload + '|message: ' + i);   // does not work
+          }
+          var receivedCount = 0;
+          socket.on('message', function (msg) {
+            receivedCount += 1;
+            if (receivedCount === messageCount) {
+              done();
+            }
+          });
+        });
+      });
+    });
+
+    it('should support chinese', function(done){
+      var engine = listen({ allowUpgrades: false }, function (port) {
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port));
+        var shi = '石室詩士施氏,嗜獅,誓食十獅。';
+        var shi2 = '氏時時適市視獅。';
+        engine.on('connection', function (conn) {
+          conn.send('.');
+          conn.send(shi);
+          conn.send(shi2);
+          conn.once('message', function(msg0){
+            expect(msg0).to.be('.');
+            conn.once('message', function(msg){
+              expect(msg).to.be(shi);
+              conn.once('message', function(msg2){
+                expect(msg2).to.be(shi2);
+                done();
+              });
+            });
+          });
+        });
+        socket.on('open', function(){
+          socket.once('message', function(msg0){
+            expect(msg0).to.be('.');
+            socket.once('message', function(msg){
+              expect(msg).to.be(shi);
+              socket.once('message', function(msg2){
+                expect(msg2).to.be(shi2);
+                socket.send('.');
+                socket.send(shi);
+                socket.send(shi2);
+              });
+            });
+          });
+        });
+      });
+    });
+
+    it('should send and receive data with key and cert (polling)', function(done){
+      var srvOpts = {
+        key: fs.readFileSync('test/fixtures/server.key'),
+        cert: fs.readFileSync('test/fixtures/server.crt'),
+        ca: fs.readFileSync('test/fixtures/ca.crt'),
+        requestCert: true,
+        rejectUnauthorized: true
+      };
+
+      var opts = {
+        key: fs.readFileSync('test/fixtures/client.key'),
+        cert: fs.readFileSync('test/fixtures/client.crt'),
+        ca: fs.readFileSync('test/fixtures/ca.crt'),
+        transports: ['polling'],
+      };
+
+      var srv = https.createServer(srvOpts, function(req, res){
+        res.writeHead(200);
+        res.end('hello world\n');
+      });
+
+      var engine = eio({ transports: ['polling'], allowUpgrades: false });
+      engine.attach(srv);
+      srv.listen(null, function() {
+        var port = srv.address().port;
+        var socket = new eioc.Socket('https://localhost:%d'.s(port), opts);
+
+        engine.on('connection', function (conn) {
+          conn.on('message', function(msg) {
+            expect(msg).to.be('hello');
+            done();
+          });
+        });
+
+        socket.on('open', function() {
+          socket.send('hello');
+        });
+      });
+    });
+
+    it('should send and receive data with ca when not requiring auth (polling)', function(done){
+      var srvOpts = {
+        key: fs.readFileSync('test/fixtures/server.key'),
+        cert: fs.readFileSync('test/fixtures/server.crt'),
+        ca: fs.readFileSync('test/fixtures/ca.crt'),
+        requestCert: true
+      };
+
+      var opts = {
+        ca: fs.readFileSync('test/fixtures/ca.crt'),
+        transports: ['polling']
+      };
+
+      var srv = https.createServer(srvOpts, function(req, res){
+        res.writeHead(200);
+        res.end('hello world\n');
+      });
+
+      var engine = eio({ transports: ['polling'], allowUpgrades: false });
+      engine.attach(srv);
+      srv.listen(null, function() {
+        var port = srv.address().port;
+        var socket = new eioc.Socket('https://localhost:%d'.s(port), opts);
+
+        engine.on('connection', function (conn) {
+          conn.on('message', function(msg) {
+            expect(msg).to.be('hello');
+            done();
+          });
+        });
+
+        socket.on('open', function() {
+          socket.send('hello');
+        });
+      });
+    });
+
+    it('should send and receive data with key and cert (ws)', function(done){
+      var srvOpts = {
+        key: fs.readFileSync('test/fixtures/server.key'),
+        cert: fs.readFileSync('test/fixtures/server.crt'),
+        ca: fs.readFileSync('test/fixtures/ca.crt'),
+        requestCert: true,
+        rejectUnauthorized: true
+      };
+
+      var opts = {
+        key: fs.readFileSync('test/fixtures/client.key'),
+        cert: fs.readFileSync('test/fixtures/client.crt'),
+        ca: fs.readFileSync('test/fixtures/ca.crt'),
+        transports: ['websocket']
+      };
+
+      var srv = https.createServer(srvOpts, function(req, res){
+        res.writeHead(200);
+        res.end('hello world\n');
+      });
+
+      var engine = eio({ transports: ['websocket'], allowUpgrades: false });
+      engine.attach(srv);
+      srv.listen(null, function() {
+        var port = srv.address().port;
+        var socket = new eioc.Socket('https://localhost:%d'.s(port), opts);
+
+        engine.on('connection', function (conn) {
+          conn.on('message', function(msg) {
+            expect(msg).to.be('hello');
+            done();
+          });
+        });
+
+        socket.on('open', function() {
+          socket.send('hello');
+        });
+      });
+    });
+
+    it('should send and receive data with pfx (polling)', function(done){
+      var srvOpts = {
+        key: fs.readFileSync('test/fixtures/server.key'),
+        cert: fs.readFileSync('test/fixtures/server.crt'),
+        ca: fs.readFileSync('test/fixtures/ca.crt'),
+        requestCert: true,
+        rejectUnauthorized: true
+      };
+
+      var opts = {
+        pfx: fs.readFileSync('test/fixtures/client.pfx'),
+        ca: fs.readFileSync('test/fixtures/ca.crt'),
+        transports: ['polling']
+      };
+
+      var srv = https.createServer(srvOpts, function(req, res){
+        res.writeHead(200);
+        res.end('hello world\n');
+      });
+
+      var engine = eio({ transports: ['polling'], allowUpgrades: false });
+      engine.attach(srv);
+      srv.listen(null, function() {
+        var port = srv.address().port;
+        var socket = new eioc.Socket('https://localhost:%d'.s(port), opts);
+
+        engine.on('connection', function (conn) {
+          conn.on('message', function(msg) {
+            expect(msg).to.be('hello');
+            done();
+          });
+        });
+
+        socket.on('open', function() {
+          socket.send('hello');
+        });
+      });
+    });
+
+    it('should send and receive data with pfx (ws)', function(done){
+      var srvOpts = {
+        key: fs.readFileSync('test/fixtures/server.key'),
+        cert: fs.readFileSync('test/fixtures/server.crt'),
+        ca: fs.readFileSync('test/fixtures/ca.crt'),
+        requestCert: true,
+        rejectUnauthorized: true
+      };
+
+      var opts = {
+        pfx: fs.readFileSync('test/fixtures/client.pfx'),
+        ca: fs.readFileSync('test/fixtures/ca.crt'),
+        transports: ['websocket']
+      };
+
+      var srv = https.createServer(srvOpts, function(req, res){
+        res.writeHead(200);
+        res.end('hello world\n');
+      });
+
+      var engine = eio({ transports: ['websocket'], allowUpgrades: false });
+      engine.attach(srv);
+      srv.listen(null, function() {
+        var port = srv.address().port;
+        var socket = new eioc.Socket('https://localhost:%d'.s(port), opts);
+
+        engine.on('connection', function (conn) {
+          conn.on('message', function(msg) {
+            expect(msg).to.be('hello');
+            done();
+          });
+        });
+
+        socket.on('open', function() {
+          socket.send('hello');
+        });
+      });
+    });
+  });
+
+  describe('send', function() {
+    describe('writeBuffer', function() {
+      it('should not empty until `drain` event (polling)', function (done) {
+        var engine = listen({ allowUpgrades: false }, function (port) {
+          var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['polling'] });
+          var totalEvents = 2;
+          socket.on('open', function() {
+            socket.send('a');
+            socket.send('b');
+            // writeBuffer should be nonempty, with 'a' still in it
+            expect(socket.writeBuffer.length).to.eql(2);
+          });
+          socket.transport.on('drain', function() {
+            expect(socket.writeBuffer.length).to.eql(--totalEvents);
+            totalEvents || done();
+          });
+        });
+      });
+
+      it('should not empty until `drain` event (websocket)', function (done) {
+        var engine = listen({ allowUpgrades: false }, function (port) {
+          var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['websocket'] });
+          var totalEvents = 2;
+          socket.on('open', function() {
+            socket.send('a');
+            socket.send('b');
+            // writeBuffer should be nonempty, with 'a' still in it
+            expect(socket.writeBuffer.length).to.eql(2);
+          });
+          socket.transport.on('drain', function() {
+            expect(socket.writeBuffer.length).to.eql(--totalEvents);
+            totalEvents || done();
+          });
+        });
+      });
+    });
+
+    describe('callback', function() {
+      it('should execute in order when message sent (client) (polling)', function (done) {
+        var engine = listen({ allowUpgrades: false }, function (port) {
+          var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['polling'] });
+          var i = 0;
+          var j = 0;
+
+          engine.on('connection', function(conn) {
+            conn.on('message', function(msg) {
+              conn.send(msg);
+            });
+          });
+
+          socket.on('open', function () {
+            socket.on('message', function(msg) {
+              // send another packet until we've sent 3 total
+              if (++i < 3) {
+                expect(i).to.eql(j);
+                sendFn();
+              } else {
+                done();
+              }
+            });
+
+            function sendFn() {
+              socket.send(j, (function(value) {
+                j++;
+              })(j));
+            }
+
+            sendFn();
+          });
+        });
+      });
+
+      it('should execute in order when message sent (client) (websocket)', function (done) {
+        var engine = listen({ allowUpgrades: false }, function (port) {
+          var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['websocket'] });
+          var i = 0;
+          var j = 0;
+
+          engine.on('connection', function(conn) {
+            conn.on('message', function(msg) {
+              conn.send(msg);
+            });
+          });
+
+          socket.on('open', function () {
+            socket.on('message', function(msg) {
+              // send another packet until we've sent 3 total
+              if (++i < 3) {
+                expect(i).to.eql(j);
+                sendFn();
+              } else {
+                done();
+              }
+            });
+
+            function sendFn() {
+              socket.send(j, (function(value) {
+                j++;
+              })(j));
+            }
+
+            sendFn();
+          });
+        });
+      });
+
+      it('should execute in order with payloads (client) (polling)', function (done) {
+        var engine = listen({ allowUpgrades: false }, function (port) {
+          var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['polling'] });
+          var i = 0;
+          var lastCbFired = 0;
+
+          engine.on('connection', function(conn) {
+            conn.on('message', function(msg) {
+              conn.send(msg);
+            });
+          });
+
+          socket.on('open', function () {
+            socket.on('message', function(msg) {
+              expect(msg).to.eql(i + 1);
+              i++;
+            });
+
+            function cb(value) {
+              expect(value).to.eql(lastCbFired + 1);
+              lastCbFired = value;
+              if (value == 3) {
+                done();
+              }
+            }
+
+            // 2 and 3 will be in the same payload
+            socket.once('flush', function() {
+              socket.send(2, function() { cb(2); });
+              socket.send(3, function() { cb(3); });
+            });
+
+            socket.send(1, function() { cb(1); });
+          });
+        });
+      });
+
+      it('should execute in order with payloads (client) (websocket)', function (done) {
+        var engine = listen({ allowUpgrades: false }, function (port) {
+          var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['websocket'] });
+          var i = 0;
+          var lastCbFired = 0;
+
+          engine.on('connection', function(conn) {
+            conn.on('message', function(msg) {
+              conn.send(msg);
+            });
+          });
+
+          socket.on('open', function () {
+            socket.on('message', function(msg) {
+              expect(msg).to.eql(i + 1);
+              i++;
+            });
+
+            function cb(value) {
+              expect(value).to.eql(lastCbFired + 1);
+              lastCbFired = value;
+              if (value == 3) {
+                done();
+              }
+            }
+
+            // 2 and 3 will be in the same payload
+            socket.once('flush', function() {
+              socket.send(2, function() { cb(2); });
+              socket.send(3, function() { cb(3); });
+            });
+
+            socket.send(1, function() { cb(1); });
+          });
+        });
+      });
+
+      it('should execute when message sent (polling)', function (done) {
+        var engine = listen({ allowUpgrades: false }, function (port) {
+          var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['polling'] });
+          var i = 0;
+          var j = 0;
+
+          engine.on('connection', function (conn) {
+            conn.send('a', function (transport) {
+              i++;
+            });
+          });
+          socket.on('open', function () {
+            socket.on('message', function (msg) {
+              j++;
+            });
+          });
+
+          setTimeout(function() {
+            expect(i).to.be(j);
+            done();
+          }, 10);
+        });
+      });
+
+      it('should execute when message sent (websocket)', function (done) {
+        var engine = listen({ allowUpgrades: false }, function (port) {
+          var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['websocket'] });
+          var i = 0;
+          var j = 0;
+
+          engine.on('connection', function (conn) {
+            conn.send('a', function (transport) {
+              i++;
+            });
+          });
+
+          socket.on('open', function () {
+            socket.on('message', function (msg) {
+              j++;
+            });
+          });
+
+          setTimeout(function () {
+            expect(i).to.be(j);
+            done();
+          }, 10);
+        });
+      });
+
+      it('should execute once for each send', function (done) {
+        var engine = listen(function (port) {
+          var socket = new eioc.Socket('ws://localhost:%d'.s(port));
+          var a = 0;
+          var b = 0;
+          var c = 0;
+          var all = 0;
+
+          engine.on('connection', function (conn) {
+            conn.send('a');
+            conn.send('b');
+            conn.send('c');
+          });
+
+          socket.on('open', function () {
+            socket.on('message', function (msg) {
+              if (msg === 'a') a ++;
+              if (msg === 'b') b ++;
+              if (msg === 'c') c ++;
+
+              if(++all === 3) {
+                expect(a).to.be(1);
+                expect(b).to.be(1);
+                expect(c).to.be(1);
+                done();
+              }
+            });
+          });
+        });
+      });
+
+      it('should execute in multipart packet', function (done) {
+        var engine = listen(function (port) {
+          var socket = new eioc.Socket('ws://localhost:%d'.s(port));
+          var i = 0;
+          var j = 0;
+
+          engine.on('connection', function (conn) {
+            conn.send('b', function (transport) {
+              i++;
+            });
+
+            conn.send('a', function (transport) {
+              i++;
+            });
+
+          });
+          socket.on('open', function () {
+            socket.on('message', function (msg) {
+              j++;
+            });
+          });
+
+          setTimeout(function () {
+            expect(i).to.be(j);
+            done();
+          }, 200);
+        });
+      });
+
+      it('should execute in multipart packet (polling)', function (done) {
+        var engine = listen(function (port) {
+          var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['polling'] });
+          var i = 0;
+          var j = 0;
+
+          engine.on('connection', function (conn) {
+            conn.send('d', function (transport) {
+              i++;
+            });
+
+            conn.send('c', function (transport) {
+              i++;
+            });
+
+            conn.send('b', function (transport) {
+              i++;
+            });
+
+            conn.send('a', function (transport) {
+              i++;
+            });
+
+          });
+          socket.on('open', function () {
+            socket.on('message', function (msg) {
+              j++;
+            });
+          });
+
+          setTimeout(function () {
+            expect(i).to.be(j);
+            done();
+          }, 200);
+        });
+      });
+
+      it('should clean callback references when socket gets closed with pending callbacks', function (done) {
+        var engine = listen({ allowUpgrades: false }, function (port) {
+          var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['polling'] });
+
+          engine.on('connection', function (conn) {
+            socket.transport.on('pollComplete', function () {
+              conn.send('a', function (transport) {
+                done(new Error('Test invalidation'));
+              });
+
+              if (!conn.writeBuffer.length) {
+                done(new Error('Test invalidation'));
+              }
+
+              // force to close the socket when we have one or more packet(s) in buffer
+              socket.close();
+            });
+
+            conn.on('close', function (reason) {
+              expect(conn.packetsFn).to.be.empty();
+              expect(conn.sentCallbackFn).to.be.empty();
+              done();
+            });
+          });
+        });
+      });
+
+      it('should not execute when it is not actually sent (polling)', function (done) {
+        var engine = listen({ allowUpgrades: false }, function (port) {
+          var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['polling'] });
+
+          socket.transport.on('pollComplete', function(msg) {
+            socket.close();
+          });
+
+          engine.on('connection', function (conn) {
+            var err;
+            conn.send('a');
+            conn.send('b', function (transport) {
+              err = new Error('Test invalidation');
+            });
+            conn.on('close', function (reason) {
+              done(err);
+            });
+          });
+        });
+      });
+    });
+  });
+
+  describe('packet', function() {
+    it('should emit when socket receives packet', function (done) {
+      var engine = listen({ allowUpgrades: false }, function (port) {
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port));
+        engine.on('connection', function (conn) {
+          conn.on('packet', function (packet) {
+            expect(packet.type).to.be('message');
+            expect(packet.data).to.be('a');
+            done();
+          });
+        });
+        socket.on('open', function () {
+          socket.send('a');
+        });
+      });
+    });
+
+    it('should emit when receives ping', function (done) {
+      var engine = listen({ allowUpgrades: false, pingInterval: 4 }, function (port) {
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port));
+        engine.on('connection', function (conn) {
+          conn.on('packet', function (packet) {
+            conn.close();
+            expect(packet.type).to.be('ping');
+            done();
+          });
+        });
+      });
+    });
+  });
+
+  describe('packetCreate', function() {
+    it('should emit before socket send message', function (done) {
+      var engine = listen({ allowUpgrades: false }, function (port) {
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port));
+        engine.on('connection', function (conn) {
+          conn.on('packetCreate', function(packet) {
+            expect(packet.type).to.be('message');
+            expect(packet.data).to.be('a');
+            done();
+          });
+          conn.send('a');
+        });
+      });
+    });
+
+    it('should emit before send pong', function (done) {
+      var engine = listen({ allowUpgrades: false, pingInterval: 4 }, function (port) {
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port));
+        engine.on('connection', function (conn) {
+          conn.on('packetCreate', function (packet) {
+            conn.close();
+            expect(packet.type).to.be('pong');
+            done();
+          });
+        });
+      });
+    });
+  });
+
+  describe('upgrade', function () {
+    it('should upgrade', function (done) {
+      var engine = listen(function (port) {
+        // it takes both to send 50 to verify
+        var ready = 2, closed = 2;
+        function finish () {
+          setTimeout(function () {
+            socket.close();
+          }, 10);
+        }
+
+        // server
+        engine.on('connection', function (conn) {
+          var lastSent = 0, lastReceived = 0, upgraded = false;
+          var interval = setInterval(function () {
+            lastSent++;
+            conn.send(lastSent);
+            if (50 == lastSent) {
+              clearInterval(interval);
+              --ready || finish();
+            }
+          }, 2);
+
+          expect(conn.request._query.transport).to.be('polling');
+
+          conn.on('message', function (msg) {
+            expect(conn.request._query).to.be.an('object');
+            lastReceived++;
+            expect(msg).to.eql(lastReceived);
+          });
+
+          conn.on('upgrade', function (to) {
+            expect(conn.request._query.transport).to.be('polling');
+            upgraded = true;
+            expect(to.name).to.be('websocket');
+            expect(conn.transport.name).to.be('websocket');
+          });
+
+          conn.on('close', function (reason) {
+            expect(reason).to.be('transport close');
+            expect(lastSent).to.be(50);
+            expect(lastReceived).to.be(50);
+            expect(upgraded).to.be(true);
+            --closed || done();
+          });
+        });
+
+        // client
+        var socket = new eioc.Socket('ws://localhost:%d'.s(port));
+        socket.on('open', function () {
+          var lastSent = 0, lastReceived = 0, upgrades = 0;
+          var interval = setInterval(function () {
+            lastSent++;
+            socket.send(lastSent);
+            if (50 == lastSent) {
+              clearInterval(interval);
+              --ready || finish();
+            }
+          }, 2);
+          socket.on('upgrading', function (to) {
+            // we want to make sure for the sake of this test that we have a buffer
+            expect(to.name).to.equal('websocket');
+            upgrades++;
+
+            // force send a few packets to ensure we test buffer transfer
+            lastSent++;
+            socket.send(lastSent);
+            lastSent++;
+            socket.send(lastSent);
+
+            expect(socket.writeBuffer).to.not.be.empty();
+          });
+          socket.on('upgrade', function (to) {
+            expect(to.name).to.equal('websocket');
+            upgrades++;
+          });
+          socket.on('message', function (msg) {
+            lastReceived++;
+            expect(lastReceived).to.eql(msg);
+          });
+          socket.on('close', function (reason) {
+            expect(reason).to.be('forced close');
+            expect(lastSent).to.be(50);
+            expect(lastReceived).to.be(50);
+            expect(upgrades).to.be(2);
+            --closed || done();
+          });
+        });
+      });
+
+      // attach another engine to make sure it doesn't break upgrades
+      var e2 = eio.attach(engine.httpServer, { path: '/foo' });
+    });
+  });
+
+});

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-engine.io.git



More information about the Pkg-javascript-commits mailing list