mirror of
https://github.com/socketio/socket.io.git
synced 2026-01-12 08:27:57 -05:00
Compare commits
99 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4395b86c60 | ||
|
|
43d58186d8 | ||
|
|
2066ddd8fe | ||
|
|
db50f3e8ee | ||
|
|
b79faf8998 | ||
|
|
ce88922bea | ||
|
|
5c4c681a83 | ||
|
|
7ca0606670 | ||
|
|
f6036007fc | ||
|
|
aa027ab571 | ||
|
|
b4f24c6995 | ||
|
|
3a53c63778 | ||
|
|
d8e3ccc637 | ||
|
|
cef5fb3574 | ||
|
|
cfbae2ac15 | ||
|
|
1072add73f | ||
|
|
648b1d0792 | ||
|
|
63624e5996 | ||
|
|
1ee5285136 | ||
|
|
5308452b8a | ||
|
|
2ee09436ce | ||
|
|
d2ecaff462 | ||
|
|
023566e03b | ||
|
|
c306a3c303 | ||
|
|
80f1d9780b | ||
|
|
ff10eeecba | ||
|
|
cd9cbb500b | ||
|
|
de8d573948 | ||
|
|
ea9e5ed2cc | ||
|
|
d648fc5a72 | ||
|
|
109a59ca9a | ||
|
|
d304ce19d1 | ||
|
|
f534a260b3 | ||
|
|
cff4669d57 | ||
|
|
4b0a1f22c8 | ||
|
|
e79bdb00e9 | ||
|
|
d11ca00b49 | ||
|
|
f3ba4173c7 | ||
|
|
569103e19a | ||
|
|
06445a0faa | ||
|
|
2506b06961 | ||
|
|
992eda86b4 | ||
|
|
6fa8b1f051 | ||
|
|
a91c6f26f4 | ||
|
|
aa9f2596cb | ||
|
|
e2a97588ef | ||
|
|
0b904d79c2 | ||
|
|
f99ac54df5 | ||
|
|
b306cc77d7 | ||
|
|
ba70be4e0b | ||
|
|
0b17ec9cb8 | ||
|
|
71e77561bb | ||
|
|
f5b2028577 | ||
|
|
7f08d8fd59 | ||
|
|
ffb0574a76 | ||
|
|
e57e27ee43 | ||
|
|
3c76d732e6 | ||
|
|
e058ae5201 | ||
|
|
715c46de13 | ||
|
|
eb0e1d3d2c | ||
|
|
1d6687cecc | ||
|
|
f62bae4e8b | ||
|
|
28535071bb | ||
|
|
45f1712d57 | ||
|
|
3029d0cf2f | ||
|
|
5c9fb03c50 | ||
|
|
39fab0ab9b | ||
|
|
c958a12c79 | ||
|
|
d298113a3e | ||
|
|
3c6fc999dc | ||
|
|
56205392c8 | ||
|
|
967238acb7 | ||
|
|
3c61f64d29 | ||
|
|
e91c785ac8 | ||
|
|
d0369801a1 | ||
|
|
e51b37da92 | ||
|
|
c7bec15fa9 | ||
|
|
4c60ed190d | ||
|
|
d959b96363 | ||
|
|
a9dc5e081b | ||
|
|
c168c89f1a | ||
|
|
be4c847692 | ||
|
|
9649b41f25 | ||
|
|
6a0f80e4fc | ||
|
|
4240385f6f | ||
|
|
22a93d06cb | ||
|
|
4ea687b57e | ||
|
|
227671d41d | ||
|
|
7bf4ae1f05 | ||
|
|
b28d4288d5 | ||
|
|
835aee46bd | ||
|
|
054e818bd9 | ||
|
|
107806b304 | ||
|
|
426795395c | ||
|
|
df929876de | ||
|
|
1002105f7a | ||
|
|
8f4f82a413 | ||
|
|
e1745e20ac | ||
|
|
681f004fe9 |
242
History.md
Normal file
242
History.md
Normal file
@@ -0,0 +1,242 @@
|
||||
|
||||
0.6.18 / 2011-05-16
|
||||
===================
|
||||
|
||||
* Updated client with multiple fixes.
|
||||
* Fixed returning 'ws' for location even when the client connects via 'wss'.
|
||||
|
||||
0.6.17 / 2011-03-30
|
||||
==================
|
||||
|
||||
* Fixed the 'possible EventEmitter memory leak detected' bug for the XHR transport
|
||||
* Reconnection support added to chat example. [3rd-Eden]
|
||||
|
||||
0.6.16 / 2011-03-04
|
||||
===================
|
||||
|
||||
* Fixed cross domain xhr-polling in Safari [tifroz]
|
||||
|
||||
0.6.15 / 2011-02-23
|
||||
===================
|
||||
|
||||
* Fixed memory leak in WebSocket transport [belorion]
|
||||
|
||||
0.6.14 / 2011-02-18
|
||||
===================
|
||||
|
||||
* Fixed logging scope issue [shripad]
|
||||
|
||||
0.6.13 / 2011-02-18
|
||||
===================
|
||||
|
||||
* Fixed references to listener when logging
|
||||
|
||||
0.6.12 / 2011-02-18
|
||||
===================
|
||||
|
||||
* Fixed noDelay missing file descriptor problem
|
||||
|
||||
0.6.11 / 2011-02-15
|
||||
===================
|
||||
|
||||
* Fixed; Make sure to not execute any other connection operations after WebSocket
|
||||
write error.
|
||||
* Added more error logging
|
||||
|
||||
0.6.10 / 2011-02-09
|
||||
===================
|
||||
|
||||
* Added SSL chat example (`make example-ssl`)
|
||||
* Fixed; possible write errors when a connection error event fires
|
||||
|
||||
0.6.9 / 2011-02-06
|
||||
==================
|
||||
|
||||
* 0.3 compatibility
|
||||
* Updated socket.io client to 0.6.2
|
||||
* Fixed Flash inline policy serving for Firefox 4
|
||||
* Updated expresso
|
||||
* Added comments and version number to socket.io/index
|
||||
|
||||
0.6.8 / 2011-01-10
|
||||
==================
|
||||
|
||||
* Fixed issue with terminating connection twice
|
||||
|
||||
0.6.7 / 2011-01-09
|
||||
==================
|
||||
|
||||
* Fixed situation where the connection drops but the client can still autoreconnect
|
||||
through a different socket. In this case we still want to clear the FD but not
|
||||
call onDisconnect immediately.
|
||||
|
||||
0.6.6 / 2011-01-09
|
||||
==================
|
||||
|
||||
* Note for Flash socket and inline policy on Firefox
|
||||
* Destroy the fds on disconnect
|
||||
* Restored 20 secs of polling so that node doesn't timeout the connections
|
||||
|
||||
0.6.5 / 2011-01-09
|
||||
==================
|
||||
|
||||
* Make sure not to trigger multiple timeouts when closing
|
||||
* Important fix for polling transports.
|
||||
|
||||
0.6.4 / 2011-01-05
|
||||
==================
|
||||
|
||||
* Don't destroy the connection in _onClose. Destroying it will prevent the buffers from being flushed and will result in corrupted responses for the xhr-polling transport.
|
||||
* Added try/catch block around JSON.parse and return an empty object literal if JSON parsing fails.
|
||||
* Added missing .connect() to example
|
||||
|
||||
0.6.3 / 2010-12-23
|
||||
==================
|
||||
|
||||
* Changed polling default duration to 50 seconds
|
||||
* might > will Adjusted to 85 column limit
|
||||
* Support for resources that include slashes. Thanks @schamane
|
||||
* Lazy loading of transports. Thanks @technoweenie Fixed README transports list
|
||||
* OpenSSL clarifications (thanks @bmnds)
|
||||
* Support for HAProxy load balancing (thanks Brian McKelvey) Backported Parser from 0.7
|
||||
* Fixed HTTP API in example (was outdated). Thanks deedubs
|
||||
* 0.3 compatibility (thanks Arnout)
|
||||
* `client.broadcast` now 300% faster Cleaned up chat example
|
||||
* fixed bad pluralization.
|
||||
* cleaned up grammar, missing punctuation, etc.
|
||||
* Restored global `netserver` for flashsocket Now supporting `flashPolicyServer` option (thanks Arnout) Tests passing with and without sudo/root user Fixed noDelay/timeout/utf-8 for draft 76 (accidental typo)
|
||||
* Close the netServer when the main http server closes, this way the event loop does not keep running. NOTE: this is patch for node 0.2.X, this is not required for node 0.3.X
|
||||
* Fallback to try{}catch handling for node < 0.2.4 , node 0.3.X seems to capture the errors correctly using the error event.
|
||||
* Added the flash policy server, it's enabled by default but can be turned off if needed. Socket.io will automatically fallback to serving the policy file inline if server is disabled or unable to start up.
|
||||
* Make sure to only write to open transports (thanks JohnDav)
|
||||
* _open is still false, so destroy the connection immediately upon websocket error
|
||||
* Make sure .connection is not null on 'end'
|
||||
* Proper fix for invalid websocket key
|
||||
|
||||
0.6.1 / 2010-11-08
|
||||
|
||||
* Restored flash policy server, but with these changes:
|
||||
- It's contingent on the listener flashPolicyServer option
|
||||
- It's started by default if socket.io is started with root access
|
||||
- It correctly closes the netserver upon all the dependent http servers being closed
|
||||
- The handler for the inline request is still there regardless. This is important in the following circumstances, and has no performance hit
|
||||
- The port 843 is filtered
|
||||
- Flash at some point enables us to skip 843 checking altogether
|
||||
- Tests compatibility
|
||||
* Fixed connection timeout, noDelay and socket encoding for draft 76 (had been accidentally moved into the `else` block)
|
||||
* Some stylistic fixes
|
||||
|
||||
0.6.0 / 2010-11-01
|
||||
==================
|
||||
|
||||
* Make sure to only write to open transports (thanks JohnDav)
|
||||
* _open is still false, so destroy the connection immediately upon websocket error
|
||||
* Make sure to disconnect directly onClose if the client is not handshaked and he can't possibly reconnect
|
||||
* Make sure to end and destroy connection onDisconnect (for timeouts)
|
||||
* Added missing .listen() call to example. Fixes #80. Thanks @machee
|
||||
* Invalid transport test completed
|
||||
* Initial stab at trying to detect invalid transport responses
|
||||
* Make sure to provide a default for `log` if no log key was provided (internal)
|
||||
* Removed unnecessary file extension verification when serving the client
|
||||
* Removed unnecessary Client check upon connection
|
||||
* Added support for /socket.io/WebSocketMain.swf
|
||||
* Added test for /socket.io/WebSocketMain.swf
|
||||
* Client serving ETag testing
|
||||
* Added htmlfile transport tests
|
||||
* Added extra byte to IE iframe bytes padding
|
||||
* Invalid session id test
|
||||
* end() before destroy()ing the socket for non-WebSocket or non-valid Upgrade requests
|
||||
* Added test for non-socket.io requests
|
||||
* Simplified index.js tests
|
||||
* Moved listener tests into listener.js
|
||||
* Make sure to call .end() when listening on connection 'end' event
|
||||
* Make sure the file descriptor is destroyed on disconnection
|
||||
* Fix for websocket client tracking test
|
||||
* Inline (same port) flash socket policy request.
|
||||
* If the server is not run with root privileges, then the flashsocket
|
||||
transport will instead listen to all new connections on the main port
|
||||
for policy requests. Flash policy requests happen to both port 843 and
|
||||
the destination port:
|
||||
http://www.lightsphere.com/dev/articles/flash_socket_policy.html
|
||||
|
||||
* [websocket test] Fix sending message to client upon connecting
|
||||
* [websocket test] Fix for connection and handshake test
|
||||
* [client files serving] Leverage end() write() call
|
||||
* [client serving] Make sure to not do a useless file lookup when file is cached
|
||||
* Finished json encoding test
|
||||
* Look for the heartbeat in the decoded message
|
||||
* Refactored websocket transports tests to match polling/multipart helpers
|
||||
* Added coverage testing to Makefile
|
||||
* Added heartbeat test to multipart
|
||||
* Added buffered messages test for multipart
|
||||
* Added assertions for `connected` property for all the tests
|
||||
* Multipart clients tracking test
|
||||
* Multipart client>server message sending test
|
||||
* Make sure to only close the client stream when the roundtrip is complete
|
||||
* Multipart connection and handshake tests:
|
||||
- Implemented HTTP client on top of net.Stream with multipart boundary parsing for testing
|
||||
- Test for connection / server>client message sending
|
||||
* Removed unnecessary check for this.connection (since we now access the socket through req.connection for all transports)
|
||||
* Test for `duration` parameter
|
||||
* Added `make example` to Makefile
|
||||
* Added clients tracking test for long polling
|
||||
* Added message buffering test for long polling
|
||||
* Improve this.request/this.response/this.connection
|
||||
* Add 'end' listener onConnect, applies to all transports
|
||||
* Improved error handling onConnect
|
||||
* Remove legacy `flush` calls
|
||||
* Removed unnecessary closeTimeout clearing in jsonp polling
|
||||
* Make sure to close on disconnect if _open = true
|
||||
* Clear disconnection timeout on disconnection (double check)
|
||||
* Make sure to clear closeTimeout for polling transports on close.
|
||||
* Replaced empty with null in log option
|
||||
* Comma first style for client serving tests
|
||||
* Long polling integration tests
|
||||
* Test for heartbeat message
|
||||
* Added heartbeat timeout test
|
||||
* Support for listener#log false
|
||||
* Corrected onConnect signature to support a request and a socket, or a request and a response.
|
||||
* Removed error checking for non-upgradeable sockets, since they'll be destroyed, and error handling is done onConnect
|
||||
* Added tests for websocket client tracking
|
||||
* Added tests for websocket message buffering
|
||||
* Make sure disconnect timeout is cleared on websocket re-connect
|
||||
* Updated the flash socket with error detection, and readystate detection.
|
||||
* This is needed because when a error occures we close down the connection,
|
||||
* and the stream will become unwriteable.
|
||||
* Also changed to a single write instead of multiple writes.
|
||||
* Moved error handling to onConnect to avoid messing with the http.Server global error handlers
|
||||
* Do special error handling for websocket
|
||||
* Clearing heartbeat interval upon closing the connection
|
||||
* Added error listeners, if theses errors are not correcly caught, they will leak memory.
|
||||
* This caused http://speedo.no.de/ to go up from 1mb per connection after a ECONNECTRESET message
|
||||
* Added encode=UTF-8 in jsonp-polling.js and xhr-polling.js since UTF-8 is the default encoding for http.ServerResponse.write
|
||||
* Replaced string.length with Buffer.byteLength in jsonp-polling.js, listener.js and xhr-polling.js because content-length header requires number of bytes and not the number of symbols in string
|
||||
* Fix COR headers/requests for different ports on Safari.
|
||||
* Clearing the references to request, response and connection upon disconnect.
|
||||
* Every require is blocking and requiring the sys module over and over and over again just makes no sense + it hurt performance.. Not to mention.. that it's already included.
|
||||
* Socket.IO-node now serves the client out of the box for easier implementation
|
||||
* Memory caching and ETag support for static files
|
||||
* Tests
|
||||
* Simplified demo even further thanks to new static file serving
|
||||
* Failing to pass an origin header would throw an exception and crash the server. Added some handling.
|
||||
* .connected renamed to ._open, and adopted proper `connected` (fixes #41)
|
||||
* example/client updated to latest socket.io client
|
||||
* Better checking of WebSocket connections
|
||||
* Better handling of SSL location (thanks @jdub)
|
||||
* Fix for cross-domain websocket (fixes #42)
|
||||
* Removed clients/clientsIndex and only using the index (fixes #28)
|
||||
* Fixed WebSocket location header for ws/wss (Thanks @jdub, Fixes #40)
|
||||
* Cross domain issues with xhr-polling addressed. Thanks Niko Kaiser (@nicokaiser)
|
||||
* Added origin verification for incoming data.
|
||||
* Make sure pathname is set (thanks steadicat & swarmation team)
|
||||
* Fix for accessing routes that being with the namespace but are not a connection attempt. Thanks @steadicat from swarmation
|
||||
* JSONP-polling support
|
||||
* Graceful closing of connection for invalid websocket clients
|
||||
* Make it possible to just require 'socket.io'
|
||||
* Make sure to abort the connect() method upon bad upgrade / origin verification
|
||||
* Support for automatic JSON encoding/decoding
|
||||
* Simplified chat example to take advantage of JSON encoding/decoding
|
||||
* Removed fs sync call from example
|
||||
* Better `how to use`
|
||||
* Make sure to send content-type text/plain to `ok` POST responses
|
||||
|
||||
5
Makefile
5
Makefile
@@ -7,4 +7,7 @@ test-cov:
|
||||
example:
|
||||
node ./example/server.js
|
||||
|
||||
.PHONY: example
|
||||
example-ssl:
|
||||
node ./example/server-ssl.js
|
||||
|
||||
.PHONY: example
|
||||
|
||||
85
README.md
85
README.md
@@ -1,7 +1,7 @@
|
||||
Socket.IO Server: Sockets for the rest of us
|
||||
============================================
|
||||
|
||||
The `Socket.IO` server provides seamless supports for a variety of transports intended for realtime communication
|
||||
The `Socket.IO` server provides seamless support for a variety of transports intended for realtime communication.
|
||||
|
||||
- WebSocket
|
||||
- WebSocket over Flash (+ XML security policy support)
|
||||
@@ -12,33 +12,39 @@ The `Socket.IO` server provides seamless supports for a variety of transports in
|
||||
|
||||
## Requirements
|
||||
|
||||
- Node v0.1.103+
|
||||
- [Socket.IO client](http://github.com/LearnBoost/Socket.IO) to connect from the browser
|
||||
- Node v0.1.103+ with `crypto` module support (make sure you have OpenSSL
|
||||
headers when installing Node to get it)
|
||||
- The [Socket.IO client](http://github.com/LearnBoost/Socket.IO), to connect from the browser
|
||||
|
||||
## How to use
|
||||
|
||||
To run the demo:
|
||||
To run the demo, execute the following:
|
||||
|
||||
git clone git://github.com/LearnBoost/Socket.IO-node.git socket.io
|
||||
cd socket.io/example/
|
||||
sudo node server.js
|
||||
|
||||
and point your browser to http://localhost:8080. In addition to 8080, if the transport `flashsocket` is enabled, a server will be initialized to listen to requests on the port 843.
|
||||
and point your browser to `http://localhost:8080`. In addition to `8080`, if the transport `flashsocket` is enabled, a server will be initialized to listen for requests on port `843`.
|
||||
|
||||
### Implementing it on your project
|
||||
|
||||
`Socket.IO` is designed not to take over an entire port or Node `http.Server` instance. This means that if you choose your HTTP server to listen on the port 80, `socket.io` can intercept requests directed to it and the normal requests will still be served.
|
||||
`Socket.IO` is designed not to take over an entire port or Node `http.Server` instance. This means that if you choose to have your HTTP server listen on port `80`, `socket.io` can intercept requests directed to it, and normal requests will still be served.
|
||||
|
||||
By default, the server will intercept requests that contain `socket.io` in the path / resource part of the URI. You can change this (look at the available options below).
|
||||
By default, the server will intercept requests that contain `socket.io` in the path / resource part of the URI. You can change this as shown in the available options below.
|
||||
|
||||
On the server, install socket.io with NPM:
|
||||
|
||||
npm install socket.io
|
||||
|
||||
Then:
|
||||
|
||||
var http = require('http'),
|
||||
io = require('./path/to/socket.io'),
|
||||
io = require('socket.io'),
|
||||
|
||||
server = http.createServer(function(req, res){
|
||||
// your normal server code
|
||||
res.writeHeader(200, {'Content-Type': 'text/html'});
|
||||
res.writeBody('<h1>Hello world</h1>');
|
||||
res.finish();
|
||||
res.writeHead(200, {'Content-Type': 'text/html'});
|
||||
res.end('<h1>Hello world</h1>');
|
||||
});
|
||||
|
||||
server.listen(80);
|
||||
@@ -52,17 +58,18 @@ By default, the server will intercept requests that contain `socket.io` in the p
|
||||
client.on('disconnect', function(){ … })
|
||||
});
|
||||
|
||||
On the client side:
|
||||
On the client:
|
||||
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script>
|
||||
var socket = new io.Socket();
|
||||
socket.connect();
|
||||
socket.on('connect', function(){ … })
|
||||
socket.on('message', function(){ … })
|
||||
socket.on('disconnect', function(){ … })
|
||||
</script>
|
||||
|
||||
The [client side](http://github.com/learnboost/socket.io) files will be served automatically by `Socket.IO-node`.
|
||||
The [client-side](http://github.com/learnboost/socket.io) files are served automatically by `Socket.IO-node`.
|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -76,15 +83,15 @@ Public Properties:
|
||||
|
||||
- *server*
|
||||
|
||||
The instance of _process.http.Server_
|
||||
An instance of _process.http.Server_.
|
||||
|
||||
- *options*
|
||||
|
||||
The passed in options combined with the defaults
|
||||
The passed-in options, combined with the defaults.
|
||||
|
||||
- *clients*
|
||||
|
||||
An object of clients indexed by their session ids.
|
||||
An object of clients, indexed by session ID.
|
||||
|
||||
Methods:
|
||||
|
||||
@@ -94,11 +101,11 @@ Methods:
|
||||
|
||||
- *removeListener(event, λ)*
|
||||
|
||||
Remove a listener from the listener array for the specified event.
|
||||
Removes a listener from the listener array for the specified event.
|
||||
|
||||
- *broadcast(message, [except])*
|
||||
|
||||
Broadcasts a message to all clients. There's an optional second argument which is an array of session ids or a single session id to avoid broadcasting to.
|
||||
Broadcasts a message to all clients. Optionally, you can pass a single session ID or array of session IDs to avoid broadcasting to, as the second argument.
|
||||
|
||||
Options:
|
||||
|
||||
@@ -106,11 +113,19 @@ Options:
|
||||
|
||||
socket.io
|
||||
|
||||
The resource is what allows the `socket.io` server to identify incoming connections by `socket.io` clients. Make sure they're in sync.
|
||||
The resource is what allows the `socket.io` server to identify incoming connections from `socket.io` clients. Make sure they're in sync.
|
||||
|
||||
- *flashPolicyServer*
|
||||
|
||||
true
|
||||
|
||||
Create a Flash Policy file server on port `843` (this is restricted port and you will need to have root permission). If you disable the FlashPolicy file server, Socket.IO will automatically fall back to serving the policy file inline.
|
||||
|
||||
|
||||
- *transports*
|
||||
|
||||
['websocket', 'server-events', 'flashsocket', 'htmlfile', 'xhr-multipart', 'xhr-polling']
|
||||
['websocket', 'flashsocket', 'htmlfile', 'xhr-multipart', 'xhr-polling',
|
||||
'jsonp-polling']
|
||||
|
||||
A list of the accepted transports.
|
||||
|
||||
@@ -122,21 +137,21 @@ Options:
|
||||
|
||||
ƒ(){ sys.log }
|
||||
|
||||
The logging function. Defaults to outputting to stdout through `sys.log`
|
||||
The logging function. Defaults to outputting to `stdout` through `sys.log`
|
||||
|
||||
Events:
|
||||
|
||||
- *clientConnect(client)*
|
||||
|
||||
Fired when a client is connected. Receives the Client instance as parameter
|
||||
Fired when a client is connected. Receives the Client instance as parameter.
|
||||
|
||||
- *clientMessage(message, client)*
|
||||
|
||||
Fired when a message from a client is received. Receives the message and Client instance as parameter
|
||||
Fired when a message from a client is received. Receives the message and Client instance as parameters.
|
||||
|
||||
- *clientDisconnect(client)*
|
||||
|
||||
Fired when a client is disconnected. Receives the Client instance as parameter
|
||||
Fired when a client is disconnected. Receives the Client instance as a parameter.
|
||||
|
||||
Important note: `this` in the event listener refers to the `Listener` instance.
|
||||
|
||||
@@ -148,43 +163,43 @@ Public Properties:
|
||||
|
||||
- *listener*
|
||||
|
||||
The `Listener` instance this client belongs to.
|
||||
The `Listener` instance to which this client belongs.
|
||||
|
||||
- *connected*
|
||||
|
||||
Whether the client is connected
|
||||
Whether the client is connected.
|
||||
|
||||
- *connections*
|
||||
|
||||
Number of times the client connected
|
||||
Number of times the client has connected.
|
||||
|
||||
Methods:
|
||||
|
||||
- *send(message)*
|
||||
|
||||
Sends a message to the client
|
||||
Sends a message to the client.
|
||||
|
||||
- *broadcast(message)*
|
||||
|
||||
Sends a message to all other clients. Equivalent to Listener::broadcast(message, client.sessionId)
|
||||
Sends a message to all other clients. Equivalent to Listener::broadcast(message, client.sessionId).
|
||||
|
||||
## Protocol
|
||||
|
||||
One of the design goals is that you should be able to implement whatever protocol you desire without `Socket.IO` getting in the way. `Socket.IO` has a minimal, unobtrusive protocol layer. It consists of two parts:
|
||||
One of the design goals is that you should be able to implement whatever protocol you desire without `Socket.IO` getting in the way. `Socket.IO` has a minimal, unobtrusive protocol layer, consisting of two parts:
|
||||
|
||||
* Connection handshake
|
||||
|
||||
This is required to simulate a full duplex socket with transports such as XHR Polling or Server-sent Events (which is a "one-way socket"). The basic idea is that the first message received from the server will be a JSON object that contains a session id that will be used for further communication exchanged between the client and the server.
|
||||
This is required to simulate a full duplex socket with transports such as XHR Polling or Server-sent Events (which is a "one-way socket"). The basic idea is that the first message received from the server will be a JSON object that contains a session ID used for further communications exchanged between the client and server.
|
||||
|
||||
The concept of session also benefits naturally full-duplex WebSocket, in the event of an accidental disconnection and a quick reconnection. Messages that the server intends to deliver to the client are cached temporarily until the reconnection.
|
||||
The concept of session also naturally benefits a full-duplex WebSocket, in the event of an accidental disconnection and a quick reconnection. Messages that the server intends to deliver to the client are cached temporarily until reconnection.
|
||||
|
||||
The implementation of reconnection logic (potentially with retries) is left for the user. By default, transports that are keep-alive or open all the time (like WebSocket) have a timeout of 0 if a disconnection is detected.
|
||||
|
||||
* Message batching
|
||||
|
||||
In order to optimize the resources, messages are buffered. In the event of the server trying to send multiple messages while the client is temporarily disconnected (eg: xhr polling), messages are stacked, then encoded in a lightweight way and sent to the client whenever he becomes available.
|
||||
Messages are buffered in order to optimize resources. In the event of the server trying to send multiple messages while a client is temporarily disconnected (eg: xhr polling), the messages are stacked and then encoded in a lightweight way, and sent to the client whenever it becomes available.
|
||||
|
||||
Despite this extra layer, your messages are delivered unaltered to the different event listeners. You can JSON.stringify() objects, send XML, or maybe plain text.
|
||||
Despite this extra layer, the messages are delivered unaltered to the various event listeners. You can `JSON.stringify()` objects, send XML, or even plain text.
|
||||
|
||||
## Credits
|
||||
|
||||
@@ -215,4 +230,4 @@ 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.
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
21
example/cert.crt
Normal file
21
example/cert.crt
Normal file
@@ -0,0 +1,21 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDXTCCAkWgAwIBAgIJAMUSOvlaeyQHMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
|
||||
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
|
||||
aWRnaXRzIFB0eSBMdGQwHhcNMTAxMTE2MDkzMjQ5WhcNMTMxMTE1MDkzMjQ5WjBF
|
||||
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
|
||||
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
|
||||
CgKCAQEAz+LXZOjcQCJq3+ZKUFabj71oo/ex/XsBcFqtBThjjTw9CVEVwfPQQp4X
|
||||
wtPiB204vnYXwQ1/R2NdTQqCZu47l79LssL/u2a5Y9+0NEU3nQA5qdt+1FAE0c5o
|
||||
exPimXOrR3GWfKz7PmZ2O0117IeCUUXPG5U8umhDe/4mDF4ZNJiKc404WthquTqg
|
||||
S7rLQZHhZ6D0EnGnOkzlmxJMYPNHSOY1/6ivdNUUcC87awNEA3lgfhy25IyBK3QJ
|
||||
c+aYKNTbt70Lery3bu2wWLFGtmNiGlQTS4JsxImRsECTI727ObS7/FWAQsqW+COL
|
||||
0Sa5BuMFrFIpjPrEe0ih7vRRbdmXRwIDAQABo1AwTjAdBgNVHQ4EFgQUDnV4d6mD
|
||||
tOnluLoCjkUHTX/n4agwHwYDVR0jBBgwFoAUDnV4d6mDtOnluLoCjkUHTX/n4agw
|
||||
DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAFwV4MQfTo+qMv9JMiyno
|
||||
IEiqfOz4RgtmBqRnXUffcjS2dhc7/z+FPZnM79Kej8eLHoVfxCyWRHFlzm93vEdv
|
||||
wxOCrD13EDOi08OOZfxWyIlCa6Bg8cMAKqQzd2OvQOWqlRWBTThBJIhWflU33izX
|
||||
Qn5GdmYqhfpc+9ZHHGhvXNydtRQkdxVK2dZNzLBvBlLlRmtoClU7xm3A+/5dddeP
|
||||
AQHEPtyFlUw49VYtZ3ru6KqPms7MKvcRhYLsy9rwSfuuniMlx4d0bDR7TOkw0QQS
|
||||
A0N8MGQRQpzl4mw4jLzyM5d5QtuGBh2P6hPGa0YQxtI3RPT/p6ENzzBiAKXiSfzo
|
||||
xw==
|
||||
-----END CERTIFICATE-----
|
||||
61
example/chat-ssl.html
Normal file
61
example/chat-ssl.html
Normal file
@@ -0,0 +1,61 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>socket.io client test</title>
|
||||
|
||||
<script src="/json.js"></script> <!-- for ie -->
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script>
|
||||
function message(obj){
|
||||
var el = document.createElement('p');
|
||||
if ('announcement' in obj) el.innerHTML = '<em>' + esc(obj.announcement) + '</em>';
|
||||
else if ('message' in obj) el.innerHTML = '<b>' + esc(obj.message[0]) + ':</b> ' + esc(obj.message[1]);
|
||||
document.getElementById('chat').appendChild(el);
|
||||
document.getElementById('chat').scrollTop = 1000000;
|
||||
}
|
||||
|
||||
function send(){
|
||||
var val = document.getElementById('text').value;
|
||||
socket.send(val);
|
||||
message({ message: ['you', val] });
|
||||
document.getElementById('text').value = '';
|
||||
}
|
||||
|
||||
function esc(msg){
|
||||
return msg.replace(/</g, '<').replace(/>/g, '>');
|
||||
};
|
||||
|
||||
var socket = new io.Socket(null, {port: 443, secure: true, rememberTransport: false});
|
||||
socket.connect();
|
||||
socket.on('message', function(obj){
|
||||
if ('buffer' in obj){
|
||||
document.getElementById('form').style.display='block';
|
||||
document.getElementById('chat').innerHTML = '';
|
||||
|
||||
for (var i in obj.buffer) message(obj.buffer[i]);
|
||||
} else message(obj);
|
||||
});
|
||||
</script>
|
||||
|
||||
<h1>Sample chat client</h1>
|
||||
<div id="chat"><p>Connecting...</p></div>
|
||||
<form id="form" onsubmit="send(); return false">
|
||||
<input type="text" autocomplete="off" id="text"><input type="submit" value="Send">
|
||||
</form>
|
||||
|
||||
<style>
|
||||
#chat { height: 300px; overflow: auto; width: 800px; border: 1px solid #eee; font: 13px Helvetica, Arial; }
|
||||
#chat p { padding: 8px; margin: 0; }
|
||||
#chat p:nth-child(odd) { background: #F6F6F6; }
|
||||
#form { width: 782px; background: #333; padding: 5px 10px; display: none; }
|
||||
#form input[type=text] { width: 700px; padding: 5px; background: #fff; border: 1px solid #fff; }
|
||||
#form input[type=submit] { cursor: pointer; background: #999; border: none; padding: 6px 8px; -moz-border-radius: 8px; -webkit-border-radius: 8px; margin-left: 5px; text-shadow: 0 1px 0 #fff; }
|
||||
#form input[type=submit]:hover { background: #A2A2A2; }
|
||||
#form input[type=submit]:active { position: relative; top: 2px; }
|
||||
</style>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -13,6 +13,8 @@
|
||||
var el = document.createElement('p');
|
||||
if ('announcement' in obj) el.innerHTML = '<em>' + esc(obj.announcement) + '</em>';
|
||||
else if ('message' in obj) el.innerHTML = '<b>' + esc(obj.message[0]) + ':</b> ' + esc(obj.message[1]);
|
||||
|
||||
if( obj.message && window.console && console.log ) console.log(obj.message[0], obj.message[1]);
|
||||
document.getElementById('chat').appendChild(el);
|
||||
document.getElementById('chat').scrollTop = 1000000;
|
||||
}
|
||||
@@ -38,11 +40,17 @@
|
||||
for (var i in obj.buffer) message(obj.buffer[i]);
|
||||
} else message(obj);
|
||||
});
|
||||
|
||||
socket.on('connect', function(){ message({ message: ['System', 'Connected']})});
|
||||
socket.on('disconnect', function(){ message({ message: ['System', 'Disconnected']})});
|
||||
socket.on('reconnect', function(){ message({ message: ['System', 'Reconnected to server']})});
|
||||
socket.on('reconnecting', function( nextRetry ){ message({ message: ['System', 'Attempting to re-connect to the server, next attempt in ' + nextRetry + 'ms']})});
|
||||
socket.on('reconnect_failed', function(){ message({ message: ['System', 'Reconnected to server FAILED.']})});
|
||||
</script>
|
||||
|
||||
<h1>Sample chat client</h1>
|
||||
<div id="chat"><p>Connecting...</p></div>
|
||||
<form id="form" onsubmit="send(); return false">
|
||||
<form id="form" onSubmit="send(); return false">
|
||||
<input type="text" autocomplete="off" id="text"><input type="submit" value="Send">
|
||||
</form>
|
||||
|
||||
@@ -58,4 +66,4 @@
|
||||
</style>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
27
example/key.key
Normal file
27
example/key.key
Normal file
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEAz+LXZOjcQCJq3+ZKUFabj71oo/ex/XsBcFqtBThjjTw9CVEV
|
||||
wfPQQp4XwtPiB204vnYXwQ1/R2NdTQqCZu47l79LssL/u2a5Y9+0NEU3nQA5qdt+
|
||||
1FAE0c5oexPimXOrR3GWfKz7PmZ2O0117IeCUUXPG5U8umhDe/4mDF4ZNJiKc404
|
||||
WthquTqgS7rLQZHhZ6D0EnGnOkzlmxJMYPNHSOY1/6ivdNUUcC87awNEA3lgfhy2
|
||||
5IyBK3QJc+aYKNTbt70Lery3bu2wWLFGtmNiGlQTS4JsxImRsECTI727ObS7/FWA
|
||||
QsqW+COL0Sa5BuMFrFIpjPrEe0ih7vRRbdmXRwIDAQABAoIBAGe4+9VqZfJN+dsq
|
||||
8Osyuz01uQ8OmC0sAWTIqUlQgENIyf9rCJsUBlYmwR5BT6Z69XP6QhHdpSK+TiAR
|
||||
XUz0EqG9HYzcxHIBaACP7j6iRoQ8R4kbbiWKo0z3WqQGIOqFjvD/mKEuQdE5mEYw
|
||||
eOUCG6BnX1WY2Yr8WKd2AA/tp0/Y4d8z04u9eodMpSTbHTzYMJb5SbBN1vo6FY7q
|
||||
8zSuO0BMzXlAxUsCwHsk1GQHFr8Oh3zIR7bQGtMBouI+6Lhh7sjFYsfxJboqMTBV
|
||||
IKaA216M6ggHG7MU1/jeKcMGDmEfqQLQoyWp29rMK6TklUgipME2L3UD7vTyAVzz
|
||||
xbVOpZkCgYEA8CXW4sZBBrSSrLR5SB+Ubu9qNTggLowOsC/kVKB2WJ4+xooc5HQo
|
||||
mFhq1v/WxPQoWIxdYsfg2odlL+JclK5Qcy6vXmRSdAQ5lK9gBDKxZSYc3NwAw2HA
|
||||
zyHCTK+I0n8PBYQ+yGcrxu0WqTGnlLW+Otk4CejO34WlgHwbH9bbY5UCgYEA3ZvT
|
||||
C4+OoMHXlmICSt29zUrYiL33IWsR3/MaONxTEDuvgkOSXXQOl/8Ebd6Nu+3WbsSN
|
||||
bjiPC/JyL1YCVmijdvFpl4gjtgvfJifs4G+QHvO6YfsYoVANk4u6g6rUuBIOwNK4
|
||||
RwYxwDc0oysp+g7tPxoSgDHReEVKJNzGBe9NGGsCgYEA4O4QP4gCEA3B9BF2J5+s
|
||||
n9uPVxmiyvZUK6Iv8zP4pThTBBMIzNIf09G9AHPQ7djikU2nioY8jXKTzC3xGTHM
|
||||
GJZ5m6fLsu7iH+nDvSreDSeNkTBfZqGAvoGYQ8uGE+L+ZuRfCcXYsxIOT5s6o4c3
|
||||
Dle2rVFpsuKzCY00urW796ECgYBn3go75+xEwrYGQSer6WR1nTgCV29GVYXKPooy
|
||||
zmmMOT1Yw80NSkEw0pFD4cTyqVYREsTrPU0mn1sPfrOXxnGfZSVFpcR/Je9QVfQ7
|
||||
eW7GYxwfom335aqHVj10SxRqteP+UoWWnHujCPz94VRKZMakBddYCIGSan+G6YdS
|
||||
7sdmwwKBgBc2qj0wvGXDF2kCLwSGfWoMf8CS1+5fIiUIdT1e/+7MfDdbmLMIFVjF
|
||||
QKS3zVViXCbrG5SY6wS9hxoc57f6E2A8vcaX6zy2xkZlGHQCpWRtEM5R01OWJQaH
|
||||
HsHMmQZGUQVoDm1oRkDhrTFK4K3ukc3rAxzeTZ96utOQN8/KJsTv
|
||||
-----END RSA PRIVATE KEY-----
|
||||
66
example/server-ssl.js
Normal file
66
example/server-ssl.js
Normal file
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Important note: this application is not suitable for benchmarks!
|
||||
*/
|
||||
|
||||
var https = require('https')
|
||||
, url = require('url')
|
||||
, fs = require('fs')
|
||||
, io = require('../')
|
||||
, sys = require(process.binding('natives').util ? 'util' : 'sys')
|
||||
, server;
|
||||
|
||||
server = https.createServer({
|
||||
key: fs.readFileSync(__dirname + '/key.key')
|
||||
, cert: fs.readFileSync(__dirname + '/cert.crt')
|
||||
}, function(req, res){
|
||||
// your normal server code
|
||||
var path = url.parse(req.url).pathname;
|
||||
switch (path){
|
||||
case '/':
|
||||
res.writeHead(200, {'Content-Type': 'text/html'});
|
||||
res.write('<h1>Welcome. Try the <a href="/chat-ssl.html">SSL Chat</a> example.</h1>');
|
||||
res.end();
|
||||
break;
|
||||
|
||||
case '/json.js':
|
||||
case '/chat-ssl.html':
|
||||
fs.readFile(__dirname + path, function(err, data){
|
||||
if (err) return send404(res);
|
||||
res.writeHead(200, {'Content-Type': path == 'json.js' ? 'text/javascript' : 'text/html'})
|
||||
res.write(data, 'utf8');
|
||||
res.end();
|
||||
});
|
||||
break;
|
||||
|
||||
default: send404(res);
|
||||
}
|
||||
}),
|
||||
|
||||
send404 = function(res){
|
||||
res.writeHead(404);
|
||||
res.write('404');
|
||||
res.end();
|
||||
};
|
||||
|
||||
server.listen(443);
|
||||
|
||||
// socket.io, I choose you
|
||||
// simplest chat application evar
|
||||
var io = io.listen(server)
|
||||
, buffer = [];
|
||||
|
||||
io.on('connection', function(client){
|
||||
client.send({ buffer: buffer });
|
||||
client.broadcast({ announcement: client.sessionId + ' connected' });
|
||||
|
||||
client.on('message', function(message){
|
||||
var msg = { message: [client.sessionId, message] };
|
||||
buffer.push(msg);
|
||||
if (buffer.length > 15) buffer.shift();
|
||||
client.broadcast(msg);
|
||||
});
|
||||
|
||||
client.on('disconnect', function(){
|
||||
client.broadcast({ announcement: client.sessionId + ' disconnected' });
|
||||
});
|
||||
});
|
||||
@@ -1,58 +1,63 @@
|
||||
var http = require('http'),
|
||||
url = require('url'),
|
||||
fs = require('fs'),
|
||||
io = require('../'),
|
||||
sys = require('sys'),
|
||||
|
||||
/**
|
||||
* Important note: this application is not suitable for benchmarks!
|
||||
*/
|
||||
|
||||
var http = require('http')
|
||||
, url = require('url')
|
||||
, fs = require('fs')
|
||||
, io = require('../')
|
||||
, sys = require(process.binding('natives').util ? 'util' : 'sys')
|
||||
, server;
|
||||
|
||||
server = http.createServer(function(req, res){
|
||||
// your normal server code
|
||||
var path = url.parse(req.url).pathname;
|
||||
switch (path){
|
||||
case '/':
|
||||
res.writeHead(200, {'Content-Type': 'text/html'});
|
||||
res.write('<h1>Welcome. Try the <a href="/chat.html">chat</a> example.</h1>');
|
||||
res.end();
|
||||
break;
|
||||
|
||||
case '/json.js':
|
||||
case '/chat.html':
|
||||
fs.readFile(__dirname + path, function(err, data){
|
||||
if (err) return send404(res);
|
||||
res.writeHead(200, {'Content-Type': path == 'json.js' ? 'text/javascript' : 'text/html'})
|
||||
res.write(data, 'utf8');
|
||||
res.end();
|
||||
});
|
||||
break;
|
||||
|
||||
default: send404(res);
|
||||
}
|
||||
// your normal server code
|
||||
var path = url.parse(req.url).pathname;
|
||||
switch (path){
|
||||
case '/':
|
||||
res.writeHead(200, {'Content-Type': 'text/html'});
|
||||
res.write('<h1>Welcome. Try the <a href="/chat.html">chat</a> example.</h1>');
|
||||
res.end();
|
||||
break;
|
||||
|
||||
case '/json.js':
|
||||
case '/chat.html':
|
||||
fs.readFile(__dirname + path, function(err, data){
|
||||
if (err) return send404(res);
|
||||
res.writeHead(200, {'Content-Type': path == 'json.js' ? 'text/javascript' : 'text/html'})
|
||||
res.write(data, 'utf8');
|
||||
res.end();
|
||||
});
|
||||
break;
|
||||
|
||||
default: send404(res);
|
||||
}
|
||||
}),
|
||||
|
||||
send404 = function(res){
|
||||
res.writeHead(404);
|
||||
res.write('404');
|
||||
res.end();
|
||||
res.writeHead(404);
|
||||
res.write('404');
|
||||
res.end();
|
||||
};
|
||||
|
||||
server.listen(8080);
|
||||
|
||||
|
||||
// socket.io, I choose you
|
||||
// simplest chat application evar
|
||||
var io = io.listen(server),
|
||||
buffer = [];
|
||||
|
||||
var io = io.listen(server)
|
||||
, buffer = [];
|
||||
|
||||
io.on('connection', function(client){
|
||||
client.send({ buffer: buffer });
|
||||
client.broadcast({ announcement: client.sessionId + ' connected' });
|
||||
client.send({ buffer: buffer });
|
||||
client.broadcast({ announcement: client.sessionId + ' connected' });
|
||||
|
||||
client.on('message', function(message){
|
||||
var msg = { message: [client.sessionId, message] };
|
||||
buffer.push(msg);
|
||||
if (buffer.length > 15) buffer.shift();
|
||||
client.broadcast(msg);
|
||||
});
|
||||
|
||||
client.on('message', function(message){
|
||||
var msg = { message: [client.sessionId, message] };
|
||||
buffer.push(msg);
|
||||
if (buffer.length > 15) buffer.shift();
|
||||
client.broadcast(msg);
|
||||
});
|
||||
|
||||
client.on('disconnect', function(){
|
||||
client.broadcast({ announcement: client.sessionId + ' disconnected' });
|
||||
});
|
||||
});
|
||||
client.on('disconnect', function(){
|
||||
client.broadcast({ announcement: client.sessionId + ' disconnected' });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,7 +4,8 @@ var urlparse = require('url').parse
|
||||
, options = require('./utils').options
|
||||
, encode = require('./utils').encode
|
||||
, decode = require('./utils').decode
|
||||
, merge = require('./utils').merge;
|
||||
, merge = require('./utils').merge
|
||||
, util = require(process.binding('natives').util ? 'util' : 'sys');
|
||||
|
||||
var Client = module.exports = function(listener, req, res, options, head){
|
||||
process.EventEmitter.call(this);
|
||||
@@ -22,7 +23,7 @@ var Client = module.exports = function(listener, req, res, options, head){
|
||||
this._onConnect(req, res);
|
||||
};
|
||||
|
||||
require('sys').inherits(Client, process.EventEmitter);
|
||||
util.inherits(Client, process.EventEmitter);
|
||||
|
||||
Client.prototype.send = function(message){
|
||||
if (!this._open || !(this.connection.readyState === 'open' || this.connection.readyState === 'writeOnly')){
|
||||
@@ -47,7 +48,11 @@ Client.prototype._onMessage = function(data){
|
||||
case '~h~':
|
||||
return this._onHeartbeat(messages[i].substr(3));
|
||||
case '~j~':
|
||||
messages[i] = JSON.parse(messages[i].substr(3));
|
||||
try {
|
||||
messages[i] = JSON.parse(messages[i].substr(3));
|
||||
} catch(e) {
|
||||
messages[i] = {};
|
||||
}
|
||||
break;
|
||||
}
|
||||
this.emit('message', messages[i]);
|
||||
@@ -56,32 +61,44 @@ Client.prototype._onMessage = function(data){
|
||||
};
|
||||
|
||||
Client.prototype._onConnect = function(req, res){
|
||||
var self = this;
|
||||
|
||||
var self = this
|
||||
, attachConnection = !this.connection;
|
||||
|
||||
this.request = req;
|
||||
this.response = res;
|
||||
this.connection = req.connection;
|
||||
|
||||
if(!attachConnection) attachConnection = !attachConnection && this.connection.eventsAttached === undefined;
|
||||
this.connection.eventsAttached = true;
|
||||
|
||||
this.connection.addListener('end', function(){
|
||||
self._onClose();
|
||||
self.connection.end();
|
||||
});
|
||||
if (attachConnection){
|
||||
function destroyConnection(){
|
||||
self._onClose();
|
||||
self.connection && self.connection.destroy()
|
||||
};
|
||||
this.connection.addListener('end', destroyConnection);
|
||||
this.connection.addListener('timeout', destroyConnection);
|
||||
this.connection.addListener('error', destroyConnection);
|
||||
}
|
||||
|
||||
if (req){
|
||||
req.addListener('error', function(err){
|
||||
req.end && req.end() || req.destroy && req.destroy();
|
||||
});
|
||||
if (res) res.addListener('error', function(err){
|
||||
res.end && res.end() || res.destroy && res.destroy();
|
||||
});
|
||||
req.connection.addListener('error', function(err){
|
||||
req.connection.end && req.connection.end() || req.connection.destroy && req.connection.destroy();
|
||||
});
|
||||
|
||||
function destroyRequest(){
|
||||
req.destroy && req.destroy();
|
||||
};
|
||||
req.addListener('error', destroyRequest);
|
||||
req.addListener('timeout', destroyRequest);
|
||||
if (res){
|
||||
function destroyResponse(){
|
||||
res.destroy && res.destroy();
|
||||
}
|
||||
res.addListener('error', destroyResponse);
|
||||
res.addListener('timeout', destroyResponse);
|
||||
}
|
||||
if (this._disconnectTimeout) clearTimeout(this._disconnectTimeout);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Client.prototype._payload = function(){
|
||||
var payload = [];
|
||||
|
||||
@@ -100,7 +117,6 @@ Client.prototype._payload = function(){
|
||||
|
||||
if (payload.length) this._write(encode(payload));
|
||||
if (this.connections === 1) this.listener._onClientConnect(this);
|
||||
|
||||
if (this.options.timeout) this._heartbeat();
|
||||
};
|
||||
|
||||
@@ -121,36 +137,33 @@ Client.prototype._onHeartbeat = function(h){
|
||||
}
|
||||
};
|
||||
|
||||
Client.prototype._onClose = function(){
|
||||
if (this._open){
|
||||
var self = this;
|
||||
if ('_heartbeatInterval' in this) clearTimeout(this._heartbeatInterval);
|
||||
if ('_heartbeatTimeout' in this) clearTimeout(this._heartbeatTimeout);
|
||||
this._open = false;
|
||||
this._disconnectTimeout = setTimeout(function(){
|
||||
self._onDisconnect();
|
||||
}, this.options.closeTimeout);
|
||||
Client.prototype._onClose = function(skipDisconnect){
|
||||
if (!this._open) return this;
|
||||
var self = this;
|
||||
if (this._heartbeatInterval) clearTimeout(this._heartbeatInterval);
|
||||
if (this._heartbeatTimeout) clearTimeout(this._heartbeatTimeout);
|
||||
this._open = false;
|
||||
this.request = null;
|
||||
this.response = null;
|
||||
if (skipDisconnect !== false){
|
||||
if (this.handshaked){
|
||||
this._disconnectTimeout = setTimeout(function(){
|
||||
self._onDisconnect();
|
||||
}, this.options.closeTimeout);
|
||||
} else
|
||||
this._onDisconnect();
|
||||
}
|
||||
};
|
||||
|
||||
Client.prototype._onDisconnect = function(){
|
||||
if (this._open) this._onClose();
|
||||
if (this._open) this._onClose(true);
|
||||
if (this._disconnectTimeout) clearTimeout(this._disconnectTimeout);
|
||||
if (this.connected){
|
||||
this._writeQueue = [];
|
||||
this._open = false;
|
||||
this.connected = false;
|
||||
this.request = null;
|
||||
this.response = null;
|
||||
if (this.connection){
|
||||
this.connection.end();
|
||||
this.connection.destroy();
|
||||
this.connection = null;
|
||||
}
|
||||
if (this.handshaked){
|
||||
this.emit('disconnect');
|
||||
this.listener._onClientDisconnect(this);
|
||||
}
|
||||
this._writeQueue = [];
|
||||
this.connected = false;
|
||||
if (this.handshaked){
|
||||
this.emit('disconnect');
|
||||
this.listener._onClientDisconnect(this);
|
||||
this.handshaked = false;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -161,7 +174,6 @@ Client.prototype._queue = function(message){
|
||||
};
|
||||
|
||||
Client.prototype._generateSessionId = function(){
|
||||
if (this.sessionId) return this.listener.options.log('This client already has a session id');
|
||||
this.sessionId = Math.random().toString().substr(2);
|
||||
return this;
|
||||
};
|
||||
@@ -182,4 +194,4 @@ Client.prototype._verifyOrigin = function(origin){
|
||||
return false;
|
||||
};
|
||||
|
||||
for (var i in options) Client.prototype[i] = options[i];
|
||||
for (var i in options) Client.prototype[i] = options[i];
|
||||
|
||||
@@ -1,4 +1,26 @@
|
||||
exports.Listener = require('./listener');
|
||||
|
||||
/**
|
||||
* Listener creation shorcut
|
||||
*
|
||||
* @param {Server} node HTTP server
|
||||
* @param {Object} options
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.listen = function(server, options){
|
||||
return new exports.Listener(server, options);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Listener constructor
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.Listener = require('./listener');
|
||||
|
||||
/**
|
||||
* Version
|
||||
*/
|
||||
|
||||
exports.version = '0.6.18';
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
var url = require('url')
|
||||
, sys = require('sys')
|
||||
, util = require(process.binding('natives').util ? 'util' : 'sys')
|
||||
, fs = require('fs')
|
||||
, options = require('./utils').options
|
||||
, Client = require('./client')
|
||||
, clientVersion = require('./../../support/socket.io-client/lib/io').io.version
|
||||
, transports = {
|
||||
'flashsocket': require('./transports/flashsocket')
|
||||
, 'htmlfile': require('./transports/htmlfile')
|
||||
, 'websocket': require('./transports/websocket')
|
||||
, 'xhr-multipart': require('./transports/xhr-multipart')
|
||||
, 'xhr-polling': require('./transports/xhr-polling')
|
||||
, 'jsonp-polling': require('./transports/jsonp-polling')
|
||||
};
|
||||
, transports = {};
|
||||
|
||||
var Listener = module.exports = function(server, options){
|
||||
process.EventEmitter.call(this);
|
||||
@@ -20,14 +13,17 @@ var Listener = module.exports = function(server, options){
|
||||
this.options({
|
||||
origins: '*:*',
|
||||
resource: 'socket.io',
|
||||
transports: ['websocket', 'flashsocket', 'htmlfile', 'xhr-multipart', 'xhr-polling', 'jsonp-polling'],
|
||||
flashPolicyServer: true,
|
||||
transports: ['websocket', 'flashsocket', 'htmlfile', 'xhr-multipart',
|
||||
'xhr-polling', 'jsonp-polling'],
|
||||
transportOptions: {},
|
||||
log: sys.log
|
||||
log: util.log
|
||||
}, options);
|
||||
|
||||
if (!this.options.log) this.options.log = function(){};
|
||||
|
||||
this.clients = this.clientsIndex = {};
|
||||
this._clientCount = 0;
|
||||
this._clientFiles = {};
|
||||
|
||||
var listeners = this.server.listeners('request');
|
||||
@@ -47,18 +43,22 @@ var Listener = module.exports = function(server, options){
|
||||
}
|
||||
});
|
||||
|
||||
for (var i in transports)
|
||||
if ('init' in transports[i]) transports[i].init(this);
|
||||
|
||||
this.options.transports.forEach(function(name) {
|
||||
if (!(name in transports))
|
||||
transports[name] = require('./transports/' + name);
|
||||
if ('init' in transports[name]) transports[name].init(self);
|
||||
});
|
||||
|
||||
this.options.log('socket.io ready - accepting connections');
|
||||
};
|
||||
|
||||
sys.inherits(Listener, process.EventEmitter);
|
||||
util.inherits(Listener, process.EventEmitter);
|
||||
for (var i in options) Listener.prototype[i] = options[i];
|
||||
|
||||
Listener.prototype.broadcast = function(message, except){
|
||||
for (var i = 0, k = Object.keys(this.clients), l = k.length; i < l; i++){
|
||||
if (this.clients[k[i]] && (!except || [].concat(except).indexOf(this.clients[k[i]].sessionId) == -1)){
|
||||
if (!except || ((typeof except == 'number' || typeof except == 'string') && k[i] != except)
|
||||
|| (Array.isArray(except) && except.indexOf(k[i]) == -1)){
|
||||
this.clients[k[i]].send(message);
|
||||
}
|
||||
}
|
||||
@@ -68,20 +68,20 @@ Listener.prototype.broadcast = function(message, except){
|
||||
Listener.prototype.check = function(req, res, httpUpgrade, head){
|
||||
var path = url.parse(req.url).pathname, parts, cn;
|
||||
if (path && path.indexOf('/' + this.options.resource) === 0){
|
||||
parts = path.substr(1).split('/');
|
||||
if (this._serveClient(parts.slice(1).join('/'), req, res)) return true;
|
||||
if (!(parts[1] in transports)) return false;
|
||||
if (parts[2]){
|
||||
cn = this.clients[parts[2]];
|
||||
parts = path.substr(2 + this.options.resource.length).split('/');
|
||||
if (this._serveClient(parts.join('/'), req, res)) return true;
|
||||
if (!(parts[0] in transports)) return false;
|
||||
if (parts[1]){
|
||||
cn = this.clients[parts[1]];
|
||||
if (cn){
|
||||
cn._onConnect(req, res);
|
||||
} else {
|
||||
req.connection.end();
|
||||
req.connection.destroy();
|
||||
this.options.log('Couldnt find client with session id "' + parts[2] + '"');
|
||||
this.options.log('Couldnt find client with session id "' + parts[1] + '"');
|
||||
}
|
||||
} else {
|
||||
this._onConnection(parts[1], req, res, httpUpgrade, head);
|
||||
this._onConnection(parts[0], req, res, httpUpgrade, head);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -163,4 +163,4 @@ Listener.prototype._onClientDisconnect = function(client){
|
||||
Listener.prototype._onConnection = function(transport, req, res, httpUpgrade, head){
|
||||
this.options.log('Initializing client with transport "'+ transport +'"');
|
||||
new transports[transport](this, req, res, this.options.transportOptions[transport], head);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,36 +1,81 @@
|
||||
var net = require('net')
|
||||
, util = require(process.binding('natives').util ? 'util' : 'sys')
|
||||
, WebSocket = require('./websocket')
|
||||
, listeners = []
|
||||
, netserver = null;
|
||||
, netserver;
|
||||
|
||||
var Flashsocket = module.exports = function(){
|
||||
WebSocket.apply(this, arguments);
|
||||
};
|
||||
|
||||
require('sys').inherits(Flashsocket, WebSocket);
|
||||
util.inherits(Flashsocket, WebSocket);
|
||||
|
||||
Flashsocket.httpUpgrade = true;
|
||||
|
||||
Flashsocket.init = function(listener){
|
||||
listeners.push(listener);
|
||||
|
||||
listener.server.on('close', function(){
|
||||
listeners.splice(listeners.indexOf(listener), 1);
|
||||
|
||||
if (listeners.length === 0 && netserver){
|
||||
try {
|
||||
netserver.close();
|
||||
} catch(e){
|
||||
listener.options.log('flashsocket netserver close error - ' + e.stack)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (listener.options.flashPolicyServer && netserver === undefined){
|
||||
netserver = net.createServer(function(socket){
|
||||
socket.addListener('error', function(err){
|
||||
if (socket && socket.end){
|
||||
socket.end();
|
||||
socket.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
if(socket && socket.readyState == 'open')
|
||||
socket.end(policy(listeners));
|
||||
});
|
||||
|
||||
try {
|
||||
netserver.listen(843);
|
||||
} catch(e){
|
||||
if (e.errno == 13)
|
||||
listener.options.log('Your node instance does not have root privileges. '
|
||||
+ 'This means that the flash XML policy file will be '
|
||||
+ 'served inline instead of on port 843. This will slow '
|
||||
+ 'down initial connections slightly.');
|
||||
netserver = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Could not listen on port 843 so policy requests will be inline
|
||||
listener.server.addListener('connection', function(stream){
|
||||
var flashCheck = function (data) {
|
||||
// Only check the initial data
|
||||
stream.removeListener('data', flashCheck);
|
||||
if (data[0] === 60 && data.length == 23 && data == '<policy-file-request/>\0'){
|
||||
if (stream && stream.readyState == 'open'){
|
||||
var xml = policy([listener]);
|
||||
stream.write(xml);
|
||||
stream.end();
|
||||
stream.removeListener("data", flashCheck);
|
||||
if (data[0] === 60 && data.length == 23) {
|
||||
if (data == '<policy-file-request/>\0') {
|
||||
listener.options.log("Answering flash policy request inline");
|
||||
if (stream && stream.readyState == 'open'){
|
||||
var xml = policy([listener]);
|
||||
stream.write(xml);
|
||||
stream.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
stream.on("data", flashCheck);
|
||||
|
||||
stream.on('data', flashCheck);
|
||||
});
|
||||
};
|
||||
|
||||
function policy(listeners) {
|
||||
var xml = '<?xml version="1.0"?>\n<!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">\n<cross-domain-policy>\n';
|
||||
var xml = '<?xml version="1.0"?>\n<!DOCTYPE cross-domain-policy SYSTEM'
|
||||
+ ' "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">\n<cross-domain-policy>\n';
|
||||
|
||||
listeners.forEach(function(l){
|
||||
[].concat(l.options.origins).forEach(function(origin){
|
||||
@@ -41,4 +86,4 @@ function policy(listeners) {
|
||||
|
||||
xml += '</cross-domain-policy>\n';
|
||||
return xml;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
var Client = require('../client')
|
||||
, util = require(process.binding('natives').util ? 'util' : 'sys')
|
||||
, qs = require('querystring');
|
||||
|
||||
var HTMLFile = module.exports = function(){
|
||||
Client.apply(this, arguments);
|
||||
};
|
||||
|
||||
require('sys').inherits(HTMLFile, Client);
|
||||
util.inherits(HTMLFile, Client);
|
||||
|
||||
HTMLFile.prototype._onConnect = function(req, res){
|
||||
var self = this, body = '';
|
||||
@@ -31,7 +32,9 @@ HTMLFile.prototype._onConnect = function(req, res){
|
||||
try {
|
||||
var msg = qs.parse(body);
|
||||
self._onMessage(msg.data);
|
||||
} catch(e){}
|
||||
} catch(e){
|
||||
self.listener.options.log('htmlfile message handler error - ' + e.stack);
|
||||
}
|
||||
res.writeHead(200, {'Content-Type': 'text/plain'});
|
||||
res.write('ok');
|
||||
res.end();
|
||||
@@ -41,5 +44,6 @@ HTMLFile.prototype._onConnect = function(req, res){
|
||||
};
|
||||
|
||||
HTMLFile.prototype._write = function(message){
|
||||
this.response.write('<script>parent.s._('+ JSON.stringify(message) +', document);</script>'); //json for escaping
|
||||
};
|
||||
if (this._open)
|
||||
this.response.write('<script>parent.s._('+ JSON.stringify(message) +', document);</script>'); //json for escaping
|
||||
};
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
var XHRPolling = require('./xhr-polling');
|
||||
var XHRPolling = require('./xhr-polling')
|
||||
, util = require(process.binding('natives').util ? 'util' : 'sys');
|
||||
|
||||
JSONPPolling = module.exports = function(){
|
||||
XHRPolling.apply(this, arguments);
|
||||
};
|
||||
|
||||
require('sys').inherits(JSONPPolling, XHRPolling);
|
||||
util.inherits(JSONPPolling, XHRPolling);
|
||||
|
||||
JSONPPolling.prototype.getOptions = function(){
|
||||
return {
|
||||
@@ -20,13 +21,15 @@ JSONPPolling.prototype._onConnect = function(req, res){
|
||||
};
|
||||
|
||||
JSONPPolling.prototype._write = function(message){
|
||||
if (this.request.headers.origin && !this._verifyOrigin(this.request.headers.origin)){
|
||||
message = "alert('Cross domain security restrictions not met');";
|
||||
} else {
|
||||
message = "io.JSONP["+ this._index +"]._("+ JSON.stringify(message) +");";
|
||||
if (this._open){
|
||||
if (this.request.headers.origin && !this._verifyOrigin(this.request.headers.origin)){
|
||||
message = "alert('Cross domain security restrictions not met');";
|
||||
} else {
|
||||
message = "io.JSONP["+ this._index +"]._("+ JSON.stringify(message) +");";
|
||||
}
|
||||
this.response.writeHead(200, {'Content-Type': 'text/javascript; charset=UTF-8', 'Content-Length': Buffer.byteLength(message)});
|
||||
this.response.write(message);
|
||||
this.response.end();
|
||||
this._onClose();
|
||||
}
|
||||
this.response.writeHead(200, {'Content-Type': 'text/javascript; charset=UTF-8', 'Content-Length': Buffer.byteLength(message)});
|
||||
this.response.write(message);
|
||||
this.response.end();
|
||||
this._onClose();
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,33 +1,59 @@
|
||||
var Client = require('../client')
|
||||
, Stream = require('net').Stream
|
||||
, EventEmitter = require('events').EventEmitter
|
||||
, url = require('url')
|
||||
, util = require(process.binding('natives').util ? 'util' : 'sys')
|
||||
, crypto = require('crypto');
|
||||
|
||||
WebSocket = module.exports = function(){
|
||||
Client.apply(this, arguments);
|
||||
};
|
||||
|
||||
require('sys').inherits(WebSocket, Client);
|
||||
util.inherits(WebSocket, Client);
|
||||
|
||||
WebSocket.prototype._onConnect = function(req, socket){
|
||||
var self = this
|
||||
, headers = [];
|
||||
|
||||
if (!req.connection.setTimeout){
|
||||
req.connection.end();
|
||||
return false;
|
||||
}
|
||||
|
||||
this.parser = new Parser();
|
||||
this.parser.on('data', self._onMessage.bind(this));
|
||||
this.parser.on('error', self._onClose.bind(this));
|
||||
|
||||
Client.prototype._onConnect.call(this, req);
|
||||
|
||||
this.data = '';
|
||||
|
||||
if (this.request.headers.upgrade !== 'WebSocket' || !this._verifyOrigin(this.request.headers.origin)){
|
||||
this.listener.options.log('WebSocket connection invalid or Origin not verified');
|
||||
this.connection.destroy();
|
||||
this._onClose();
|
||||
return false;
|
||||
}
|
||||
|
||||
var origin = this.request.headers.origin,
|
||||
location = (origin && origin.substr(0, 5) == 'https' ? 'wss' : 'ws')
|
||||
location = (this.request.socket.encrypted ? 'wss' : 'ws')
|
||||
+ '://' + this.request.headers.host + this.request.url;
|
||||
|
||||
|
||||
this.waitingForNonce = false;
|
||||
if ('sec-websocket-key1' in this.request.headers){
|
||||
/* We need to send the 101 response immediately when using Draft 76 with
|
||||
a load balancing proxy, such as HAProxy. In order to protect an
|
||||
unsuspecting non-websocket HTTP server, HAProxy will not send the
|
||||
8-byte nonce through the connection until the Upgrade: WebSocket
|
||||
request has been confirmed by the WebSocket server by a 101 response
|
||||
indicating that the server can handle the upgraded protocol. We
|
||||
therefore must send the 101 response immediately, and then wait for
|
||||
the nonce to be forwarded to us afterward in order to finish the
|
||||
Draft 76 handshake.
|
||||
*/
|
||||
|
||||
// If we don't have the nonce yet, wait for it.
|
||||
if (!(this.upgradeHead && this.upgradeHead.length >= 8)) {
|
||||
this.waitingForNonce = true;
|
||||
}
|
||||
|
||||
headers = [
|
||||
'HTTP/1.1 101 WebSocket Protocol Handshake',
|
||||
'Upgrade: WebSocket',
|
||||
@@ -48,46 +74,55 @@ WebSocket.prototype._onConnect = function(req, socket){
|
||||
'WebSocket-Location: ' + location
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
try {
|
||||
this.connection.write(headers.concat('', '').join('\r\n'));
|
||||
this.connection.setTimeout(0);
|
||||
this.connection.setNoDelay(true);
|
||||
this.connection.setEncoding('utf-8');
|
||||
|
||||
try {
|
||||
this.connection.write(headers.concat('', '').join('\r\n'));
|
||||
} catch(e){
|
||||
this._onClose();
|
||||
}
|
||||
} catch(e){
|
||||
this._onClose();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.waitingForNonce) {
|
||||
// Since we will be receiving the binary nonce through the normal HTTP
|
||||
// data event, set the connection to 'binary' temporarily
|
||||
this.connection.setEncoding('binary');
|
||||
this._headers = headers;
|
||||
}
|
||||
else {
|
||||
if (this._proveReception(headers)) this._payload();
|
||||
}
|
||||
|
||||
this.buffer = "";
|
||||
|
||||
this.connection.addListener('data', function(data){
|
||||
self._handle(data);
|
||||
if (self.waitingForNonce) {
|
||||
self.buffer += data;
|
||||
|
||||
if (self.buffer.length < 8) { return; }
|
||||
// Restore the connection to utf8 encoding after receiving the nonce
|
||||
self.connection.setEncoding('utf8');
|
||||
self.waitingForNonce = false;
|
||||
// Stuff the nonce into the location where it's expected to be
|
||||
self.upgradeHead = self.buffer.substr(0,8);
|
||||
self.buffer = '';
|
||||
if (self._proveReception(self._headers)) { self._payload(); }
|
||||
return;
|
||||
}
|
||||
|
||||
self.parser.add(data);
|
||||
});
|
||||
|
||||
if (this._proveReception(headers)) this._payload();
|
||||
};
|
||||
|
||||
WebSocket.prototype._handle = function(data){
|
||||
var chunk, chunks, chunk_count;
|
||||
this.data += data;
|
||||
chunks = this.data.split('\ufffd');
|
||||
chunk_count = chunks.length - 1;
|
||||
for (var i = 0; i < chunk_count; i++){
|
||||
chunk = chunks[i];
|
||||
if (chunk[0] !== '\u0000'){
|
||||
this.listener.options.log('Data incorrectly framed by UA. Dropping connection');
|
||||
this.connection.end();
|
||||
this.connection.destroy();
|
||||
return false;
|
||||
}
|
||||
this._onMessage(chunk.slice(1));
|
||||
}
|
||||
this.data = chunks[chunks.length - 1];
|
||||
};
|
||||
|
||||
// http://www.whatwg.org/specs/web-apps/current-work/complete/network.html#opening-handshake
|
||||
WebSocket.prototype._proveReception = function(headers){
|
||||
var k1 = this.request.headers['sec-websocket-key1'],
|
||||
k2 = this.request.headers['sec-websocket-key2'];
|
||||
var self = this
|
||||
, k1 = this.request.headers['sec-websocket-key1']
|
||||
, k2 = this.request.headers['sec-websocket-key2'];
|
||||
|
||||
if (k1 && k2){
|
||||
var md5 = crypto.createHash('md5');
|
||||
@@ -97,11 +132,8 @@ WebSocket.prototype._proveReception = function(headers){
|
||||
spaces = k.replace(/[^ ]/g, '').length;
|
||||
|
||||
if (spaces === 0 || n % spaces !== 0){
|
||||
if (this.listener && this.listener.options){
|
||||
this.listener.options.log('Invalid WebSocket key: "' + k + '". Dropping connection');
|
||||
}
|
||||
this.connection.end();
|
||||
this.connection.destroy();
|
||||
self.listener.options.log('Invalid WebSocket key: "' + k + '". Dropping connection');
|
||||
self._onClose();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -117,7 +149,7 @@ WebSocket.prototype._proveReception = function(headers){
|
||||
md5.update(this.upgradeHead.toString('binary'));
|
||||
|
||||
try {
|
||||
this.connection.write(headers.concat('', '').join('\r\n') + md5.digest('binary'), 'binary');
|
||||
this.connection.write(md5.digest('binary'), 'binary');
|
||||
} catch(e){
|
||||
this._onClose();
|
||||
}
|
||||
@@ -136,4 +168,41 @@ WebSocket.prototype._write = function(message){
|
||||
}
|
||||
};
|
||||
|
||||
WebSocket.httpUpgrade = true;
|
||||
WebSocket.httpUpgrade = true;
|
||||
|
||||
function Parser(){
|
||||
this.buffer = '';
|
||||
this.i = 0;
|
||||
};
|
||||
|
||||
Parser.prototype.__proto__ = EventEmitter.prototype;
|
||||
|
||||
Parser.prototype.add = function(data){
|
||||
this.buffer += data;
|
||||
this.parse();
|
||||
};
|
||||
|
||||
Parser.prototype.parse = function(){
|
||||
for (var i = this.i, chr, l = this.buffer.length; i < l; i++){
|
||||
chr = this.buffer[i];
|
||||
if (i === 0){
|
||||
if (chr != '\u0000')
|
||||
this.error('Bad framing. Expected null byte as first frame');
|
||||
else
|
||||
continue;
|
||||
}
|
||||
if (chr == '\ufffd'){
|
||||
this.emit('data', this.buffer.substr(1, this.buffer.length - 2));
|
||||
this.buffer = this.buffer.substr(i + 1);
|
||||
this.i = 0;
|
||||
return this.parse();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Parser.prototype.error = function(reason){
|
||||
this.buffer = '';
|
||||
this.i = 0;
|
||||
this.emit('error', reason);
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
var Client = require('../client')
|
||||
, util = require(process.binding('natives').util ? 'util' : 'sys')
|
||||
, qs = require('querystring');
|
||||
|
||||
var Multipart = module.exports = function(){
|
||||
Client.apply(this, arguments);
|
||||
};
|
||||
|
||||
require('sys').inherits(Multipart, Client);
|
||||
util.inherits(Multipart, Client);
|
||||
|
||||
Multipart.prototype._onConnect = function(req, res){
|
||||
var self = this, body = '', headers = {};
|
||||
@@ -45,7 +46,9 @@ Multipart.prototype._onConnect = function(req, res){
|
||||
try {
|
||||
var msg = qs.parse(body);
|
||||
self._onMessage(msg.data);
|
||||
} catch(e){}
|
||||
} catch(e){
|
||||
self.listener.options.log('xhr-multipart message handler error - ' + e.stack);
|
||||
}
|
||||
res.writeHead(200, headers);
|
||||
res.write('ok');
|
||||
res.end();
|
||||
@@ -56,8 +59,9 @@ Multipart.prototype._onConnect = function(req, res){
|
||||
};
|
||||
|
||||
Multipart.prototype._write = function(message){
|
||||
this.response.write("Content-Type: text/plain" + (message.length === 1 && message.charCodeAt(0) === 6 ? "; charset=us-ascii" : "") + "\n\n");
|
||||
this.response.write(message + "\n");
|
||||
this.response.write("--socketio\n");
|
||||
if ('flush' in this.response) this.response.flush();
|
||||
};
|
||||
if (this._open){
|
||||
this.response.write("Content-Type: text/plain" + (message.length === 1 && message.charCodeAt(0) === 6 ? "; charset=us-ascii" : "") + "\n\n");
|
||||
this.response.write(message + "\n");
|
||||
this.response.write("--socketio\n");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
var Client = require('../client')
|
||||
, util = require(process.binding('natives').util ? 'util' : 'sys')
|
||||
, qs = require('querystring');
|
||||
|
||||
var Polling = module.exports = function(){
|
||||
Client.apply(this, arguments);
|
||||
};
|
||||
|
||||
require('sys').inherits(Polling, Client);
|
||||
util.inherits(Polling, Client);
|
||||
|
||||
Polling.prototype.getOptions = function(){
|
||||
return {
|
||||
@@ -47,7 +48,9 @@ Polling.prototype._onConnect = function(req, res){
|
||||
// optimization: just strip first 5 characters here?
|
||||
var msg = qs.parse(body);
|
||||
self._onMessage(msg.data);
|
||||
} catch(e){}
|
||||
} catch(e){
|
||||
self.listener.options.log('xhr-polling message handler error - ' + e.stack);
|
||||
}
|
||||
res.writeHead(200, headers);
|
||||
res.write('ok');
|
||||
res.end();
|
||||
@@ -62,14 +65,16 @@ Polling.prototype._onClose = function(){
|
||||
};
|
||||
|
||||
Polling.prototype._write = function(message){
|
||||
var headers = {'Content-Type': 'text/plain; charset=UTF-8', 'Content-Length': Buffer.byteLength(message)};
|
||||
// https://developer.mozilla.org/En/HTTP_Access_Control
|
||||
if (this.request.headers.origin && this._verifyOrigin(this.request.headers.origin)){
|
||||
headers['Access-Control-Allow-Origin'] = this.request.headers.origin;
|
||||
if (this.request.headers.cookie) headers['Access-Control-Allow-Credentials'] = 'true';
|
||||
if (this._open){
|
||||
var headers = {'Content-Type': 'text/plain; charset=UTF-8', 'Content-Length': Buffer.byteLength(message)};
|
||||
// https://developer.mozilla.org/En/HTTP_Access_Control
|
||||
if (this.request.headers.origin && this._verifyOrigin(this.request.headers.origin)){
|
||||
headers['Access-Control-Allow-Origin'] = (this.request.headers.origin === 'null' ? '*' : this.request.headers.origin);
|
||||
if (this.request.headers.cookie) headers['Access-Control-Allow-Credentials'] = 'true';
|
||||
}
|
||||
this.response.writeHead(200, headers);
|
||||
this.response.write(message);
|
||||
this.response.end();
|
||||
this._onClose();
|
||||
}
|
||||
this.response.writeHead(200, headers);
|
||||
this.response.write(message);
|
||||
this.response.end();
|
||||
this._onClose();
|
||||
};
|
||||
};
|
||||
|
||||
34
package.json
34
package.json
@@ -1,17 +1,21 @@
|
||||
{ "name" : "socket.io"
|
||||
, "description" : "The cross-browser WebSocket"
|
||||
, "version" : "0.6"
|
||||
, "author" : "LearnBoost"
|
||||
, "licenses" :
|
||||
[ { "type" : "MIT"
|
||||
, "url" : "http://github.com/learnboost/Socket.IO-node/raw/master/README.md"
|
||||
{
|
||||
"name": "socket.io"
|
||||
, "description": "The cross-browser WebSocket"
|
||||
, "version": "0.6.18"
|
||||
, "author": "Guillermo Rauch <guillermo@learnboost.com>"
|
||||
, "contributors": [
|
||||
{ "name": "Guillermo Rauch", "email": "rauchg@gmail.com" }
|
||||
, { "name": "Arnout Kazemier", "email": "info@3rd-eden.com" }
|
||||
]
|
||||
, "licenses": [{
|
||||
"type": "MIT"
|
||||
, "url": "http://github.com/learnboost/Socket.IO-node/raw/master/README.md"
|
||||
}]
|
||||
, "repository": {
|
||||
"type": "git"
|
||||
, "url": "http://github.com/learnboost/Socket.IO-node.git"
|
||||
}
|
||||
]
|
||||
, "repository" :
|
||||
{ "type" : "git"
|
||||
, "url" : "http://github.com/learnboost/Socket.IO-node.git"
|
||||
}
|
||||
, "engine" : [ "node >=0.1.102" ]
|
||||
, "main" : "./index"
|
||||
, "scripts" : { "test" : "make test" }
|
||||
, "engine": [ "node >=0.1.102" ]
|
||||
, "main": "./index"
|
||||
, "scripts": { "test" : "make test" }
|
||||
}
|
||||
|
||||
@@ -1,4 +1,35 @@
|
||||
|
||||
0.7.2 / 2010-12-29
|
||||
==================
|
||||
|
||||
* Fixed problem with `listen()` sometimes firing on the same tick [guillermo]
|
||||
|
||||
0.7.1 / 2010-12-28
|
||||
==================
|
||||
|
||||
* Fixed `assert.request()` client logic into an issue() function, fired upon the `listen()` callback if the server doesn't have an assigned fd. [guillermo]
|
||||
* Removed `--watch`
|
||||
|
||||
0.7.0 / 2010-11-19
|
||||
==================
|
||||
|
||||
* Removed `assert` from test function signature
|
||||
Just use `require('assert')` :) this will make integration
|
||||
with libraries like [should](http://github.com/visionmedia/should) cleaner.
|
||||
|
||||
0.6.4 / 2010-11-02
|
||||
==================
|
||||
|
||||
* Added regexp support to `assert.response()` headers
|
||||
* Removed `waitForExit` code, causing issues
|
||||
|
||||
0.6.3 / 2010-11-02
|
||||
==================
|
||||
|
||||
* Added `assert.response()` body RegExp support
|
||||
* Fixed issue with _--serial_ not executing files sequentially. Closes #42
|
||||
* Fixed hang when modules use `setInterval` - monitor running tests & force the process to quit after all have completed + timeout [Steve Mason]
|
||||
|
||||
0.6.2 / 2010-09-17
|
||||
==================
|
||||
|
||||
|
||||
@@ -35,5 +35,27 @@ Install via npm:
|
||||
|
||||
$ npm install expresso
|
||||
|
||||
## License
|
||||
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2010 TJ Holowaychuk <tj@vision-media.ca>
|
||||
|
||||
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.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/*!
|
||||
/*
|
||||
* Expresso
|
||||
* Copyright(c) TJ Holowaychuk <tj@vision-media.ca>
|
||||
* (MIT Licensed)
|
||||
@@ -23,7 +23,7 @@ var assert = require('assert'),
|
||||
* Expresso version.
|
||||
*/
|
||||
|
||||
var version = '0.6.2';
|
||||
var version = '0.7.2';
|
||||
|
||||
/**
|
||||
* Failure count.
|
||||
@@ -62,12 +62,6 @@ var growl = false;
|
||||
|
||||
var port = 5555;
|
||||
|
||||
/**
|
||||
* Watch mode.
|
||||
*/
|
||||
|
||||
var watch = false;
|
||||
|
||||
/**
|
||||
* Execute serially.
|
||||
*/
|
||||
@@ -80,6 +74,12 @@ var serial = false;
|
||||
|
||||
var timeout = 2000;
|
||||
|
||||
/**
|
||||
* Quiet output.
|
||||
*/
|
||||
|
||||
var quiet = false;
|
||||
|
||||
/**
|
||||
* Usage documentation.
|
||||
*/
|
||||
@@ -88,9 +88,9 @@ var usage = ''
|
||||
+ '[bold]{Usage}: expresso [options] <file ...>'
|
||||
+ '\n'
|
||||
+ '\n[bold]{Options}:'
|
||||
+ '\n -w, --watch Watch for modifications and re-execute tests'
|
||||
+ '\n -g, --growl Enable growl notifications'
|
||||
+ '\n -c, --coverage Generate and report test coverage'
|
||||
+ '\n -q, --quiet Suppress coverage report if 100%'
|
||||
+ '\n -t, --timeout MS Timeout in milliseconds, defaults to 2000'
|
||||
+ '\n -r, --require PATH Require the given module path'
|
||||
+ '\n -o, --only TESTS Execute only the comma sperated TESTS (can be set several times)'
|
||||
@@ -171,14 +171,14 @@ while (args.length) {
|
||||
run(files);
|
||||
})
|
||||
break;
|
||||
case '-q':
|
||||
case '--quiet':
|
||||
quiet = true;
|
||||
break;
|
||||
case '-b':
|
||||
case '--boring':
|
||||
boring = true;
|
||||
break;
|
||||
case '-w':
|
||||
case '--watch':
|
||||
watch = true;
|
||||
break;
|
||||
case '-g':
|
||||
case '--growl':
|
||||
growl = true;
|
||||
@@ -339,6 +339,27 @@ assert.length = function(val, n, msg) {
|
||||
*/
|
||||
|
||||
assert.response = function(server, req, res, msg){
|
||||
// Check that the server is ready or defer
|
||||
if (!server.fd) {
|
||||
if (!('__deferred' in server)) {
|
||||
server.__deferred = [];
|
||||
}
|
||||
server.__deferred.push(arguments);
|
||||
if (!server.__started) {
|
||||
server.listen(server.__port = port++, '127.0.0.1', function(){
|
||||
if (server.__deferred) {
|
||||
process.nextTick(function(){
|
||||
server.__deferred.forEach(function(args){
|
||||
assert.response.apply(assert, args);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
server.__started = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Callback as third or fourth arg
|
||||
var callback = typeof res === 'function'
|
||||
? res
|
||||
@@ -357,84 +378,92 @@ assert.response = function(server, req, res, msg){
|
||||
|
||||
// Create client
|
||||
if (!server.fd) {
|
||||
server.listen(server.__port = port++, '127.0.0.1');
|
||||
server.client = http.createClient(server.__port);
|
||||
server.listen(server.__port = port++, '127.0.0.1', issue);
|
||||
} else {
|
||||
issue();
|
||||
}
|
||||
|
||||
// Issue request
|
||||
var timer,
|
||||
client = server.client,
|
||||
method = req.method || 'GET',
|
||||
status = res.status || res.statusCode,
|
||||
data = req.data || req.body,
|
||||
timeout = req.timeout || 0;
|
||||
function issue(){
|
||||
if (!server.client)
|
||||
server.client = http.createClient(server.__port);
|
||||
|
||||
var request = client.request(method, req.url, req.headers);
|
||||
// Issue request
|
||||
var timer,
|
||||
client = server.client,
|
||||
method = req.method || 'GET',
|
||||
status = res.status || res.statusCode,
|
||||
data = req.data || req.body,
|
||||
requestTimeout = req.timeout || 0;
|
||||
|
||||
// Timeout
|
||||
if (timeout) {
|
||||
timer = setTimeout(function(){
|
||||
--server.__pending || server.close();
|
||||
delete req.timeout;
|
||||
assert.fail(msg + 'Request timed out after ' + timeout + 'ms.');
|
||||
}, timeout);
|
||||
}
|
||||
var request = client.request(method, req.url, req.headers);
|
||||
|
||||
if (data) request.write(data);
|
||||
request.addListener('response', function(response){
|
||||
response.body = '';
|
||||
response.setEncoding('utf8');
|
||||
response.addListener('data', function(chunk){ response.body += chunk; });
|
||||
response.addListener('end', function(){
|
||||
--server.__pending || server.close();
|
||||
if (timer) clearTimeout(timer);
|
||||
// Timeout
|
||||
if (requestTimeout) {
|
||||
timer = setTimeout(function(){
|
||||
--server.__pending || server.close();
|
||||
delete req.timeout;
|
||||
assert.fail(msg + 'Request timed out after ' + requestTimeout + 'ms.');
|
||||
}, requestTimeout);
|
||||
}
|
||||
|
||||
// Assert response body
|
||||
if (res.body !== undefined) {
|
||||
var eql = res.body instanceof RegExp
|
||||
? res.body.test(response.body)
|
||||
: res.body === response.body;
|
||||
assert.ok(
|
||||
eql,
|
||||
msg + 'Invalid response body.\n'
|
||||
+ ' Expected: ' + sys.inspect(res.body) + '\n'
|
||||
+ ' Got: ' + sys.inspect(response.body)
|
||||
);
|
||||
}
|
||||
if (data) request.write(data);
|
||||
request.on('response', function(response){
|
||||
response.body = '';
|
||||
response.setEncoding('utf8');
|
||||
response.on('data', function(chunk){ response.body += chunk; });
|
||||
response.on('end', function(){
|
||||
--server.__pending || server.close();
|
||||
if (timer) clearTimeout(timer);
|
||||
|
||||
// Assert response status
|
||||
if (typeof status === 'number') {
|
||||
assert.equal(
|
||||
response.statusCode,
|
||||
status,
|
||||
msg + colorize('Invalid response status code.\n'
|
||||
+ ' Expected: [green]{' + status + '}\n'
|
||||
+ ' Got: [red]{' + response.statusCode + '}')
|
||||
);
|
||||
}
|
||||
|
||||
// Assert response headers
|
||||
if (res.headers) {
|
||||
var keys = Object.keys(res.headers);
|
||||
for (var i = 0, len = keys.length; i < len; ++i) {
|
||||
var name = keys[i],
|
||||
actual = response.headers[name.toLowerCase()],
|
||||
expected = res.headers[name];
|
||||
assert.equal(
|
||||
actual,
|
||||
expected,
|
||||
msg + colorize('Invalid response header [bold]{' + name + '}.\n'
|
||||
+ ' Expected: [green]{' + expected + '}\n'
|
||||
+ ' Got: [red]{' + actual + '}')
|
||||
// Assert response body
|
||||
if (res.body !== undefined) {
|
||||
var eql = res.body instanceof RegExp
|
||||
? res.body.test(response.body)
|
||||
: res.body === response.body;
|
||||
assert.ok(
|
||||
eql,
|
||||
msg + 'Invalid response body.\n'
|
||||
+ ' Expected: ' + sys.inspect(res.body) + '\n'
|
||||
+ ' Got: ' + sys.inspect(response.body)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Callback
|
||||
callback(response);
|
||||
// Assert response status
|
||||
if (typeof status === 'number') {
|
||||
assert.equal(
|
||||
response.statusCode,
|
||||
status,
|
||||
msg + colorize('Invalid response status code.\n'
|
||||
+ ' Expected: [green]{' + status + '}\n'
|
||||
+ ' Got: [red]{' + response.statusCode + '}')
|
||||
);
|
||||
}
|
||||
|
||||
// Assert response headers
|
||||
if (res.headers) {
|
||||
var keys = Object.keys(res.headers);
|
||||
for (var i = 0, len = keys.length; i < len; ++i) {
|
||||
var name = keys[i],
|
||||
actual = response.headers[name.toLowerCase()],
|
||||
expected = res.headers[name],
|
||||
eql = expected instanceof RegExp
|
||||
? expected.test(actual)
|
||||
: expected == actual;
|
||||
assert.ok(
|
||||
eql,
|
||||
msg + colorize('Invalid response header [bold]{' + name + '}.\n'
|
||||
+ ' Expected: [green]{' + expected + '}\n'
|
||||
+ ' Got: [red]{' + actual + '}')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Callback
|
||||
callback(response);
|
||||
});
|
||||
});
|
||||
});
|
||||
request.end();
|
||||
request.end();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -476,7 +505,6 @@ function rpad(str, width) {
|
||||
*/
|
||||
|
||||
function reportCoverage(cov) {
|
||||
populateCoverage(cov);
|
||||
// Stats
|
||||
print('\n [bold]{Test Coverage}\n');
|
||||
var sep = ' +------------------------------------------+----------+------+------+--------+',
|
||||
@@ -507,9 +535,11 @@ function reportCoverage(cov) {
|
||||
for (var name in cov) {
|
||||
if (name.match(/\.js$/)) {
|
||||
var file = cov[name];
|
||||
print('\n [bold]{' + name + '}:');
|
||||
print(file.source);
|
||||
sys.print('\n');
|
||||
if ((file.coverage < 100) || !quiet) {
|
||||
print('\n [bold]{' + name + '}:');
|
||||
print(file.source);
|
||||
sys.print('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -574,6 +604,25 @@ function coverage(data, val) {
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if all files have 100% coverage
|
||||
*
|
||||
* @param {Object} cov
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
function hasFullCoverage(cov) {
|
||||
for (var name in cov) {
|
||||
var file = cov[name];
|
||||
if (file instanceof Array) {
|
||||
if (file.coverage !== 100) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the given test `files`, or try _test/*_.
|
||||
*
|
||||
@@ -581,6 +630,7 @@ function coverage(data, val) {
|
||||
*/
|
||||
|
||||
function run(files) {
|
||||
cursor(false);
|
||||
if (!files.length) {
|
||||
try {
|
||||
files = fs.readdirSync('test').map(function(file){
|
||||
@@ -592,7 +642,6 @@ function run(files) {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
if (watch) watchFiles(files);
|
||||
runFiles(files);
|
||||
}
|
||||
|
||||
@@ -617,16 +666,25 @@ function cursor(show) {
|
||||
*/
|
||||
|
||||
function runFiles(files) {
|
||||
files.forEach(runFile);
|
||||
if (serial) {
|
||||
(function next(){
|
||||
if (files.length) {
|
||||
runFile(files.shift(), next);
|
||||
}
|
||||
})();
|
||||
} else {
|
||||
files.forEach(runFile);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run tests for the given `file`.
|
||||
* Run tests for the given `file`, callback `fn()` when finished.
|
||||
*
|
||||
* @param {String} file
|
||||
* @param {Function} fn
|
||||
*/
|
||||
|
||||
function runFile(file) {
|
||||
function runFile(file, fn) {
|
||||
if (file.match(/\.js$/)) {
|
||||
var title = path.basename(file),
|
||||
file = path.join(cwd, file),
|
||||
@@ -634,7 +692,7 @@ function runFile(file) {
|
||||
(function check(){
|
||||
var len = Object.keys(mod).length;
|
||||
if (len) {
|
||||
runSuite(title, mod);
|
||||
runSuite(title, mod, fn);
|
||||
} else {
|
||||
setTimeout(check, 20);
|
||||
}
|
||||
@@ -642,49 +700,6 @@ function runFile(file) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the module cache for the given `file`.
|
||||
*
|
||||
* @param {String} file
|
||||
*/
|
||||
|
||||
function clearCache(file) {
|
||||
var keys = Object.keys(module.moduleCache);
|
||||
for (var i = 0, len = keys.length; i < len; ++i) {
|
||||
var key = keys[i];
|
||||
if (key.indexOf(file) === key.length - file.length) {
|
||||
delete module.moduleCache[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Watch the given `files` for changes.
|
||||
*
|
||||
* @param {Array} files
|
||||
*/
|
||||
|
||||
function watchFiles(files) {
|
||||
var p = 0,
|
||||
c = ['▫ ', '▫▫ ', '▫▫▫ ', ' ▫▫▫',
|
||||
' ▫▫', ' ▫', ' ▫', ' ▫▫',
|
||||
'▫▫▫ ', '▫▫ ', '▫ '],
|
||||
l = c.length;
|
||||
cursor(false);
|
||||
setInterval(function(){
|
||||
sys.print(colorize(' [green]{' + c[p++ % l] + '} watching\r'));
|
||||
}, 100);
|
||||
files.forEach(function(file){
|
||||
fs.watchFile(file, { interval: 100 }, function(curr, prev){
|
||||
if (curr.mtime > prev.mtime) {
|
||||
print(' [yellow]{◦} ' + file);
|
||||
clearCache(file);
|
||||
runFile(file);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Report `err` for the given `test` and `suite`.
|
||||
*
|
||||
@@ -696,23 +711,23 @@ function watchFiles(files) {
|
||||
function error(suite, test, err) {
|
||||
++failures;
|
||||
var name = err.name,
|
||||
stack = err.stack.replace(err.name, ''),
|
||||
stack = err.stack ? err.stack.replace(err.name, '') : '',
|
||||
label = test === 'uncaught'
|
||||
? test
|
||||
: suite + ' ' + test;
|
||||
print('\n [bold]{' + label + '}: [red]{' + name + '}' + stack + '\n');
|
||||
if (watch) notify(label + ' failed');
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the given tests.
|
||||
* Run the given tests, callback `fn()` when finished.
|
||||
*
|
||||
* @param {String} title
|
||||
* @param {Object} tests
|
||||
* @param {Function} fn
|
||||
*/
|
||||
|
||||
var dots = 0;
|
||||
function runSuite(title, tests) {
|
||||
function runSuite(title, tests, fn) {
|
||||
// Keys
|
||||
var keys = only.length
|
||||
? only.slice(0)
|
||||
@@ -720,7 +735,7 @@ function runSuite(title, tests) {
|
||||
|
||||
// Setup
|
||||
var setup = tests.setup || function(fn){ fn(); };
|
||||
|
||||
|
||||
// Iterate tests
|
||||
(function next(){
|
||||
if (keys.length) {
|
||||
@@ -735,27 +750,25 @@ function runSuite(title, tests) {
|
||||
++testcount;
|
||||
assert.testTitle = key;
|
||||
if (serial) {
|
||||
if (!watch) {
|
||||
sys.print('.');
|
||||
if (++dots % 25 === 0) sys.print('\n');
|
||||
}
|
||||
sys.print('.');
|
||||
if (++dots % 25 === 0) sys.print('\n');
|
||||
setup(function(){
|
||||
if (test.length < 2) {
|
||||
test(assert);
|
||||
if (test.length < 1) {
|
||||
test();
|
||||
next();
|
||||
} else {
|
||||
var id = setTimeout(function(){
|
||||
throw new Error("'" + key + "' timed out");
|
||||
}, timeout);
|
||||
test(assert, function(){
|
||||
test(function(){
|
||||
clearTimeout(id);
|
||||
next();
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
test(assert, function(fn){
|
||||
process.addListener('beforeExit', function(){
|
||||
test(function(fn){
|
||||
process.on('beforeExit', function(){
|
||||
try {
|
||||
fn();
|
||||
} catch (err) {
|
||||
@@ -769,6 +782,8 @@ function runSuite(title, tests) {
|
||||
}
|
||||
}
|
||||
if (!serial) next();
|
||||
} else if (serial) {
|
||||
fn();
|
||||
}
|
||||
})();
|
||||
}
|
||||
@@ -778,6 +793,7 @@ function runSuite(title, tests) {
|
||||
*/
|
||||
|
||||
function report() {
|
||||
cursor(true);
|
||||
process.emit('beforeExit');
|
||||
if (failures) {
|
||||
print('\n [bold]{Failures}: [red]{' + failures + '}\n\n');
|
||||
@@ -788,7 +804,10 @@ function report() {
|
||||
notify('100% ok');
|
||||
}
|
||||
if (typeof _$jscoverage === 'object') {
|
||||
reportCoverage(_$jscoverage);
|
||||
populateCoverage(_$jscoverage);
|
||||
if (!hasFullCoverage(_$jscoverage) || !quiet) {
|
||||
reportCoverage(_$jscoverage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -806,14 +825,14 @@ function notify(msg) {
|
||||
|
||||
// Report uncaught exceptions
|
||||
|
||||
process.addListener('uncaughtException', function(err){
|
||||
process.on('uncaughtException', function(err){
|
||||
error('uncaught', 'uncaught', err);
|
||||
});
|
||||
|
||||
// Show cursor
|
||||
|
||||
['INT', 'TERM', 'QUIT'].forEach(function(sig){
|
||||
process.addListener('SIG' + sig, function(){
|
||||
process.on('SIG' + sig, function(){
|
||||
cursor(true);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
@@ -113,7 +113,7 @@ code .this { color: #19469D; }</style>
|
||||
<h1>!/usr/bin/env node</h1>
|
||||
</td>
|
||||
<td class="code">
|
||||
<pre><code>!
|
||||
<pre><code>
|
||||
* <span class="class">Expresso</span>
|
||||
* <span class="class">Copyright</span>(<span class="variable">c</span>) <span class="class">TJ</span> <span class="class">Holowaychuk</span> &<span class="variable">lt</span>;<span class="variable">tj</span>@<span class="variable">vision</span>-<span class="variable">media</span>.<span class="variable">ca</span>&<span class="variable">gt</span>;
|
||||
* (<span class="class">MIT</span> <span class="class">Licensed</span>)
|
||||
@@ -142,7 +142,7 @@ code .this { color: #19469D; }</style>
|
||||
</p>
|
||||
</td>
|
||||
<td class="code">
|
||||
<pre><code><span class="keyword">var</span> <span class="variable">version</span> = <span class="string">'0.6.1'</span>;</code></pre>
|
||||
<pre><code><span class="keyword">var</span> <span class="variable">version</span> = <span class="string">'0.6.4'</span>;</code></pre>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="code">
|
||||
@@ -219,6 +219,15 @@ code .this { color: #19469D; }</style>
|
||||
</tr>
|
||||
<tr class="code">
|
||||
<td class="docs">
|
||||
<p>Default timeout.
|
||||
</p>
|
||||
</td>
|
||||
<td class="code">
|
||||
<pre><code><span class="keyword">var</span> <span class="variable">timeout</span> = <span class="number integer">2000</span>;</code></pre>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="code">
|
||||
<td class="docs">
|
||||
<p>Usage documentation.
|
||||
</p>
|
||||
</td>
|
||||
@@ -230,6 +239,7 @@ code .this { color: #19469D; }</style>
|
||||
+ <span class="string">'\n -w, --watch Watch for modifications and re-execute tests'</span>
|
||||
+ <span class="string">'\n -g, --growl Enable growl notifications'</span>
|
||||
+ <span class="string">'\n -c, --coverage Generate and report test coverage'</span>
|
||||
+ <span class="string">'\n -t, --timeout MS Timeout in milliseconds, defaults to 2000'</span>
|
||||
+ <span class="string">'\n -r, --require PATH Require the given module path'</span>
|
||||
+ <span class="string">'\n -o, --only TESTS Execute only the comma sperated TESTS (can be set several times)'</span>
|
||||
+ <span class="string">'\n -I, --include PATH Unshift the given path to require.paths'</span>
|
||||
@@ -291,6 +301,14 @@ code .this { color: #19469D; }</style>
|
||||
<span class="keyword">throw</span> <span class="keyword">new</span> <span class="class">Error</span>(<span class="string">'--require requires a path'</span>);
|
||||
}
|
||||
<span class="keyword">break</span>;
|
||||
<span class="keyword">case</span> <span class="string">'-t'</span>:
|
||||
<span class="keyword">case</span> <span class="string">'--timeout'</span>:
|
||||
<span class="keyword">if</span> (<span class="variable">arg</span> = <span class="variable">args</span>.<span class="variable">shift</span>()) {
|
||||
<span class="variable">timeout</span> = <span class="variable">parseInt</span>(<span class="variable">arg</span>, <span class="number integer">10</span>);
|
||||
} <span class="keyword">else</span> {
|
||||
<span class="keyword">throw</span> <span class="keyword">new</span> <span class="class">Error</span>(<span class="string">'--timeout requires an argument'</span>);
|
||||
}
|
||||
<span class="keyword">break</span>;
|
||||
<span class="keyword">case</span> <span class="string">'-c'</span>:
|
||||
<span class="keyword">case</span> <span class="string">'--cov'</span>:
|
||||
<span class="keyword">case</span> <span class="string">'--coverage'</span>:
|
||||
@@ -525,17 +543,17 @@ the given <code>req</code> object and <code>res</code> assertions object.</p>
|
||||
<span class="variable">method</span> = <span class="variable">req</span>.<span class="variable">method</span> || <span class="string">'GET'</span>,
|
||||
<span class="variable">status</span> = <span class="variable">res</span>.<span class="variable">status</span> || <span class="variable">res</span>.<span class="variable">statusCode</span>,
|
||||
<span class="variable">data</span> = <span class="variable">req</span>.<span class="variable">data</span> || <span class="variable">req</span>.<span class="variable">body</span>,
|
||||
<span class="variable">timeout</span> = <span class="variable">req</span>.<span class="variable">timeout</span> || <span class="number integer">0</span>;
|
||||
<span class="variable">requestTimeout</span> = <span class="variable">req</span>.<span class="variable">timeout</span> || <span class="number integer">0</span>;
|
||||
|
||||
<span class="keyword">var</span> <span class="variable">request</span> = <span class="variable">client</span>.<span class="variable">request</span>(<span class="variable">method</span>, <span class="variable">req</span>.<span class="variable">url</span>, <span class="variable">req</span>.<span class="variable">headers</span>);
|
||||
|
||||
<span class="comment">// Timeout</span>
|
||||
<span class="keyword">if</span> (<span class="variable">timeout</span>) {
|
||||
<span class="keyword">if</span> (<span class="variable">requestTimeout</span>) {
|
||||
<span class="variable">timer</span> = <span class="variable">setTimeout</span>(<span class="keyword">function</span>(){
|
||||
--<span class="variable">server</span>.<span class="variable">__pending</span> || <span class="variable">server</span>.<span class="variable">close</span>();
|
||||
<span class="keyword">delete</span> <span class="variable">req</span>.<span class="variable">timeout</span>;
|
||||
<span class="variable">assert</span>.<span class="variable">fail</span>(<span class="variable">msg</span> + <span class="string">'Request timed out after '</span> + <span class="variable">timeout</span> + <span class="string">'ms.'</span>);
|
||||
}, <span class="variable">timeout</span>);
|
||||
<span class="variable">assert</span>.<span class="variable">fail</span>(<span class="variable">msg</span> + <span class="string">'Request timed out after '</span> + <span class="variable">requestTimeout</span> + <span class="string">'ms.'</span>);
|
||||
}, <span class="variable">requestTimeout</span>);
|
||||
}
|
||||
|
||||
<span class="keyword">if</span> (<span class="variable">data</span>) <span class="variable">request</span>.<span class="variable">write</span>(<span class="variable">data</span>);
|
||||
@@ -549,9 +567,11 @@ the given <code>req</code> object and <code>res</code> assertions object.</p>
|
||||
|
||||
<span class="comment">// Assert response body</span>
|
||||
<span class="keyword">if</span> (<span class="variable">res</span>.<span class="variable">body</span> !== <span class="variable">undefined</span>) {
|
||||
<span class="variable">assert</span>.<span class="variable">equal</span>(
|
||||
<span class="variable">response</span>.<span class="variable">body</span>,
|
||||
<span class="variable">res</span>.<span class="variable">body</span>,
|
||||
<span class="keyword">var</span> <span class="variable">eql</span> = <span class="variable">res</span>.<span class="variable">body</span> <span class="variable">instanceof</span> <span class="class">RegExp</span>
|
||||
? <span class="variable">res</span>.<span class="variable">body</span>.<span class="variable">test</span>(<span class="variable">response</span>.<span class="variable">body</span>)
|
||||
: <span class="variable">res</span>.<span class="variable">body</span> === <span class="variable">response</span>.<span class="variable">body</span>;
|
||||
<span class="variable">assert</span>.<span class="variable">ok</span>(
|
||||
<span class="variable">eql</span>,
|
||||
<span class="variable">msg</span> + <span class="string">'Invalid response body.\n'</span>
|
||||
+ <span class="string">' Expected: '</span> + <span class="variable">sys</span>.<span class="variable">inspect</span>(<span class="variable">res</span>.<span class="variable">body</span>) + <span class="string">'\n'</span>
|
||||
+ <span class="string">' Got: '</span> + <span class="variable">sys</span>.<span class="variable">inspect</span>(<span class="variable">response</span>.<span class="variable">body</span>)
|
||||
@@ -575,10 +595,12 @@ the given <code>req</code> object and <code>res</code> assertions object.</p>
|
||||
<span class="keyword">for</span> (<span class="keyword">var</span> <span class="variable">i</span> = <span class="number integer">0</span>, <span class="variable">len</span> = <span class="variable">keys</span>.<span class="variable">length</span>; <span class="variable">i</span> &<span class="variable">lt</span>; <span class="variable">len</span>; ++<span class="variable">i</span>) {
|
||||
<span class="keyword">var</span> <span class="variable">name</span> = <span class="variable">keys</span>[<span class="variable">i</span>],
|
||||
<span class="variable">actual</span> = <span class="variable">response</span>.<span class="variable">headers</span>[<span class="variable">name</span>.<span class="variable">toLowerCase</span>()],
|
||||
<span class="variable">expected</span> = <span class="variable">res</span>.<span class="variable">headers</span>[<span class="variable">name</span>];
|
||||
<span class="variable">assert</span>.<span class="variable">equal</span>(
|
||||
<span class="variable">actual</span>,
|
||||
<span class="variable">expected</span>,
|
||||
<span class="variable">expected</span> = <span class="variable">res</span>.<span class="variable">headers</span>[<span class="variable">name</span>],
|
||||
<span class="variable">eql</span> = <span class="variable">expected</span> <span class="variable">instanceof</span> <span class="class">RegExp</span>
|
||||
? <span class="variable">expected</span>.<span class="variable">test</span>(<span class="variable">actual</span>)
|
||||
: <span class="variable">expected</span> == <span class="variable">actual</span>;
|
||||
<span class="variable">assert</span>.<span class="variable">ok</span>(
|
||||
<span class="variable">eql</span>,
|
||||
<span class="variable">msg</span> + <span class="variable">colorize</span>(<span class="string">'Invalid response header [bold]{'</span> + <span class="variable">name</span> + <span class="string">'}.\n'</span>
|
||||
+ <span class="string">' Expected: [green]{'</span> + <span class="variable">expected</span> + <span class="string">'}\n'</span>
|
||||
+ <span class="string">' Got: [red]{'</span> + <span class="variable">actual</span> + <span class="string">'}'</span>)
|
||||
@@ -800,20 +822,28 @@ the given <code>req</code> object and <code>res</code> assertions object.</p>
|
||||
</td>
|
||||
<td class="code">
|
||||
<pre><code><span class="keyword">function</span> <span class="variable">runFiles</span>(<span class="variable">files</span>) {
|
||||
<span class="variable">files</span>.<span class="variable">forEach</span>(<span class="variable">runFile</span>);
|
||||
<span class="keyword">if</span> (<span class="variable">serial</span>) {
|
||||
(<span class="keyword">function</span> <span class="variable">next</span>(){
|
||||
<span class="keyword">if</span> (<span class="variable">files</span>.<span class="variable">length</span>) {
|
||||
<span class="variable">runFile</span>(<span class="variable">files</span>.<span class="variable">shift</span>(), <span class="variable">next</span>);
|
||||
}
|
||||
})();
|
||||
} <span class="keyword">else</span> {
|
||||
<span class="variable">files</span>.<span class="variable">forEach</span>(<span class="variable">runFile</span>);
|
||||
}
|
||||
}</code></pre>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="code">
|
||||
<td class="docs">
|
||||
<p>Run tests for the given <code>file</code>.</p>
|
||||
<p>Run tests for the given <code>file</code>, callback <code>fn()</code> when finished.</p>
|
||||
|
||||
<h2></h2>
|
||||
|
||||
<ul><li><p><strong>param</strong>: <em>String</em> file</p></li></ul>
|
||||
<ul><li><p><strong>param</strong>: <em>String</em> file</p></li><li><p><strong>param</strong>: <em>Function</em> fn</p></li></ul>
|
||||
</td>
|
||||
<td class="code">
|
||||
<pre><code><span class="keyword">function</span> <span class="variable">runFile</span>(<span class="variable">file</span>) {
|
||||
<pre><code><span class="keyword">function</span> <span class="variable">runFile</span>(<span class="variable">file</span>, <span class="variable">fn</span>) {
|
||||
<span class="keyword">if</span> (<span class="variable">file</span>.<span class="variable">match</span>(<span class="regexp">/\.js$/</span>)) {
|
||||
<span class="keyword">var</span> <span class="variable">title</span> = <span class="variable">path</span>.<span class="variable">basename</span>(<span class="variable">file</span>),
|
||||
<span class="variable">file</span> = <span class="variable">path</span>.<span class="variable">join</span>(<span class="variable">cwd</span>, <span class="variable">file</span>),
|
||||
@@ -821,7 +851,7 @@ the given <code>req</code> object and <code>res</code> assertions object.</p>
|
||||
(<span class="keyword">function</span> <span class="variable">check</span>(){
|
||||
<span class="keyword">var</span> <span class="variable">len</span> = <span class="class">Object</span>.<span class="variable">keys</span>(<span class="variable">mod</span>).<span class="variable">length</span>;
|
||||
<span class="keyword">if</span> (<span class="variable">len</span>) {
|
||||
<span class="variable">runSuite</span>(<span class="variable">title</span>, <span class="variable">mod</span>);
|
||||
<span class="variable">runSuite</span>(<span class="variable">title</span>, <span class="variable">mod</span>, <span class="variable">fn</span>);
|
||||
} <span class="keyword">else</span> {
|
||||
<span class="variable">setTimeout</span>(<span class="variable">check</span>, <span class="number integer">20</span>);
|
||||
}
|
||||
@@ -904,15 +934,15 @@ the given <code>req</code> object and <code>res</code> assertions object.</p>
|
||||
</tr>
|
||||
<tr class="code">
|
||||
<td class="docs">
|
||||
<p>Run the given tests.</p>
|
||||
<p>Run the given tests, callback <code>fn()</code> when finished.</p>
|
||||
|
||||
<h2></h2>
|
||||
|
||||
<ul><li><p><strong>param</strong>: <em>String</em> title</p></li><li><p><strong>param</strong>: <em>Object</em> tests</p></li></ul>
|
||||
<ul><li><p><strong>param</strong>: <em>String</em> title</p></li><li><p><strong>param</strong>: <em>Object</em> tests</p></li><li><p><strong>param</strong>: <em>Function</em> fn</p></li></ul>
|
||||
</td>
|
||||
<td class="code">
|
||||
<pre><code><span class="keyword">var</span> <span class="variable">dots</span> = <span class="number integer">0</span>;
|
||||
<span class="keyword">function</span> <span class="variable">runSuite</span>(<span class="variable">title</span>, <span class="variable">tests</span>) {
|
||||
<span class="keyword">function</span> <span class="variable">runSuite</span>(<span class="variable">title</span>, <span class="variable">tests</span>, <span class="variable">fn</span>) {
|
||||
<span class="comment">// Keys</span>
|
||||
<span class="keyword">var</span> <span class="variable">keys</span> = <span class="variable">only</span>.<span class="variable">length</span>
|
||||
? <span class="variable">only</span>.<span class="variable">slice</span>(<span class="number integer">0</span>)
|
||||
@@ -920,7 +950,7 @@ the given <code>req</code> object and <code>res</code> assertions object.</p>
|
||||
|
||||
<span class="comment">// Setup</span>
|
||||
<span class="keyword">var</span> <span class="variable">setup</span> = <span class="variable">tests</span>.<span class="variable">setup</span> || <span class="keyword">function</span>(<span class="variable">fn</span>){ <span class="variable">fn</span>(); };
|
||||
|
||||
|
||||
<span class="comment">// Iterate tests</span>
|
||||
(<span class="keyword">function</span> <span class="variable">next</span>(){
|
||||
<span class="keyword">if</span> (<span class="variable">keys</span>.<span class="variable">length</span>) {
|
||||
@@ -940,21 +970,21 @@ the given <code>req</code> object and <code>res</code> assertions object.</p>
|
||||
<span class="keyword">if</span> (++<span class="variable">dots</span> % <span class="number integer">25</span> === <span class="number integer">0</span>) <span class="variable">sys</span>.<span class="variable">print</span>(<span class="string">'\n'</span>);
|
||||
}
|
||||
<span class="variable">setup</span>(<span class="keyword">function</span>(){
|
||||
<span class="keyword">if</span> (<span class="variable">test</span>.<span class="variable">length</span> &<span class="variable">lt</span>; <span class="number integer">2</span>) {
|
||||
<span class="variable">test</span>(<span class="variable">assert</span>);
|
||||
<span class="keyword">if</span> (<span class="variable">test</span>.<span class="variable">length</span> &<span class="variable">lt</span>; <span class="number integer">1</span>) {
|
||||
<span class="variable">test</span>();
|
||||
<span class="variable">next</span>();
|
||||
} <span class="keyword">else</span> {
|
||||
<span class="keyword">var</span> <span class="variable">id</span> = <span class="variable">setTimeout</span>(<span class="keyword">function</span>(){
|
||||
<span class="keyword">throw</span> <span class="keyword">new</span> <span class="class">Error</span>(&<span class="variable">quot</span>;<span class="string">'" + key + "'</span> <span class="variable">timed</span> <span class="variable">out</span>&<span class="variable">quot</span>;);
|
||||
}, <span class="number integer">2000</span>);
|
||||
<span class="variable">test</span>(<span class="variable">assert</span>, <span class="keyword">function</span>(){
|
||||
}, <span class="variable">timeout</span>);
|
||||
<span class="variable">test</span>(<span class="keyword">function</span>(){
|
||||
<span class="variable">clearTimeout</span>(<span class="variable">id</span>);
|
||||
<span class="variable">next</span>();
|
||||
});
|
||||
}
|
||||
});
|
||||
} <span class="keyword">else</span> {
|
||||
<span class="variable">test</span>(<span class="variable">assert</span>, <span class="keyword">function</span>(<span class="variable">fn</span>){
|
||||
<span class="variable">test</span>(<span class="keyword">function</span>(<span class="variable">fn</span>){
|
||||
<span class="variable">process</span>.<span class="variable">addListener</span>(<span class="string">'beforeExit'</span>, <span class="keyword">function</span>(){
|
||||
<span class="keyword">try</span> {
|
||||
<span class="variable">fn</span>();
|
||||
@@ -969,6 +999,8 @@ the given <code>req</code> object and <code>res</code> assertions object.</p>
|
||||
}
|
||||
}
|
||||
<span class="keyword">if</span> (!<span class="variable">serial</span>) <span class="variable">next</span>();
|
||||
} <span class="keyword">else</span> <span class="keyword">if</span> (<span class="variable">serial</span>) {
|
||||
<span class="variable">fn</span>();
|
||||
}
|
||||
})();
|
||||
}</code></pre>
|
||||
|
||||
@@ -41,6 +41,10 @@
|
||||
<div id="wrapper">
|
||||
<h1>Expresso</h1>
|
||||
<div class='mp'>
|
||||
<h2 id="NAME">NAME</h2>
|
||||
<p class="man-name">
|
||||
<code>index</code>
|
||||
</p>
|
||||
<p><a href="http://github.com/visionmedia/expresso">Expresso</a> is a JavaScript <a href="http://en.wikipedia.org/wiki/Test-driven_development">TDD</a> framework written for <a href="http://nodejs.org">nodejs</a>. Expresso is extremely fast, and is packed with features such as additional assertion methods, code coverage reporting, CI support, and more.</p>
|
||||
|
||||
<h2 id="Features">Features</h2>
|
||||
@@ -85,7 +89,7 @@ the command below, which will first compile node-jscoverage:</p>
|
||||
|
||||
<p>To define tests we simply export several functions:</p>
|
||||
|
||||
<pre><code>exports['test String#length'] = function(assert){
|
||||
<pre><code>exports['test String#length'] = function(){
|
||||
assert.equal(6, 'foobar'.length);
|
||||
};
|
||||
</code></pre>
|
||||
@@ -95,7 +99,7 @@ export your own object containing the tests, however this
|
||||
is essentially the as above:</p>
|
||||
|
||||
<pre><code>module.exports = {
|
||||
'test String#length': function(assert){
|
||||
'test String#length': function(){
|
||||
assert.equal(6, 'foobar'.length);
|
||||
}
|
||||
};
|
||||
@@ -103,16 +107,16 @@ is essentially the as above:</p>
|
||||
|
||||
<p>If you prefer not to use quoted keys:</p>
|
||||
|
||||
<pre><code>exports.testsStringLength = function(assert){
|
||||
<pre><code>exports.testsStringLength = function(){
|
||||
assert.equal(6, 'foobar'.length);
|
||||
};
|
||||
</code></pre>
|
||||
|
||||
<p>The second argument passed to each callback is <em>beforeExit</em>,
|
||||
<p>The argument passed to each callback is <em>beforeExit</em>,
|
||||
which is typically used to assert that callbacks have been
|
||||
invoked.</p>
|
||||
|
||||
<pre><code>exports.testAsync = function(assert, beforeExit){
|
||||
<pre><code>exports.testAsync = function(beforeExit){
|
||||
var n = 0;
|
||||
setTimeout(function(){
|
||||
++n;
|
||||
@@ -229,9 +233,9 @@ which is then used to perform several assertions
|
||||
on the response with the following properties:</p>
|
||||
|
||||
<ul>
|
||||
<li><em>body</em> assert response body</li>
|
||||
<li><em>body</em> assert response body (regexp or string)</li>
|
||||
<li><em>status</em> assert response status code</li>
|
||||
<li><em>header</em> assert that all given headers match (unspecified are ignored)</li>
|
||||
<li><em>header</em> assert that all given headers match (unspecified are ignored, use a regexp or string)</li>
|
||||
</ul>
|
||||
|
||||
|
||||
@@ -361,7 +365,7 @@ future <code>--cov</code> will most likely accept a path.</p>
|
||||
<p>Sometimes it is useful to postpone running of tests until a callback or event has fired, currently the <em>exports.foo = function(){};</em> syntax is supported for this:</p>
|
||||
|
||||
<pre><code>setTimeout(function(){
|
||||
exports['test async exports'] = function(assert){
|
||||
exports['test async exports'] = function(){
|
||||
assert.ok('wahoo');
|
||||
};
|
||||
}, 100);
|
||||
|
||||
@@ -37,7 +37,7 @@ Install via npm:
|
||||
|
||||
To define tests we simply export several functions:
|
||||
|
||||
exports['test String#length'] = function(assert){
|
||||
exports['test String#length'] = function(){
|
||||
assert.equal(6, 'foobar'.length);
|
||||
};
|
||||
|
||||
@@ -46,22 +46,22 @@ export your own object containing the tests, however this
|
||||
is essentially the as above:
|
||||
|
||||
module.exports = {
|
||||
'test String#length': function(assert){
|
||||
'test String#length': function(){
|
||||
assert.equal(6, 'foobar'.length);
|
||||
}
|
||||
};
|
||||
|
||||
If you prefer not to use quoted keys:
|
||||
|
||||
exports.testsStringLength = function(assert){
|
||||
exports.testsStringLength = function(){
|
||||
assert.equal(6, 'foobar'.length);
|
||||
};
|
||||
|
||||
The second argument passed to each callback is _beforeExit_,
|
||||
The argument passed to each callback is _beforeExit_,
|
||||
which is typically used to assert that callbacks have been
|
||||
invoked.
|
||||
|
||||
exports.testAsync = function(assert, beforeExit){
|
||||
exports.testAsync = function(beforeExit){
|
||||
var n = 0;
|
||||
setTimeout(function(){
|
||||
++n;
|
||||
@@ -164,9 +164,9 @@ receives the response for assertions, or an object
|
||||
which is then used to perform several assertions
|
||||
on the response with the following properties:
|
||||
|
||||
- _body_ assert response body
|
||||
- _body_ assert response body (regexp or string)
|
||||
- _status_ assert response status code
|
||||
- _header_ assert that all given headers match (unspecified are ignored)
|
||||
- _header_ assert that all given headers match (unspecified are ignored, use a regexp or string)
|
||||
|
||||
When providing _res_ you may then also pass a callback function
|
||||
as the fourth argument for additional assertions.
|
||||
@@ -284,7 +284,7 @@ future `--cov` will most likely accept a path.
|
||||
Sometimes it is useful to postpone running of tests until a callback or event has fired, currently the _exports.foo = function(){};_ syntax is supported for this:
|
||||
|
||||
setTimeout(function(){
|
||||
exports['test async exports'] = function(assert){
|
||||
exports['test async exports'] = function(){
|
||||
assert.ok('wahoo');
|
||||
};
|
||||
}, 100);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{ "name": "expresso",
|
||||
"version": "0.6.2",
|
||||
"version": "0.7.2",
|
||||
"description": "TDD framework, light-weight, fast, CI-friendly",
|
||||
"author": "TJ Holowaychuk <tj@vision-media.ca>",
|
||||
"bin": {
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var assert = require('assert');
|
||||
|
||||
module.exports = {
|
||||
'assert.eql()': function(assert){
|
||||
'assert.eql()': function(){
|
||||
assert.equal(assert.deepEqual, assert.eql);
|
||||
},
|
||||
|
||||
'assert.type()': function(assert){
|
||||
'assert.type()': function(){
|
||||
assert.type('foobar', 'string');
|
||||
assert.type(2, 'number');
|
||||
assert.throws(function(){
|
||||
@@ -11,7 +18,7 @@ module.exports = {
|
||||
});
|
||||
},
|
||||
|
||||
'assert.includes()': function(assert){
|
||||
'assert.includes()': function(){
|
||||
assert.includes('some random string', 'dom');
|
||||
assert.throws(function(){
|
||||
assert.include('some random string', 'foobar');
|
||||
@@ -31,7 +38,7 @@ module.exports = {
|
||||
});
|
||||
},
|
||||
|
||||
'assert.isNull()': function(assert){
|
||||
'assert.isNull()': function(){
|
||||
assert.isNull(null);
|
||||
assert.throws(function(){
|
||||
assert.isNull(undefined);
|
||||
@@ -41,7 +48,7 @@ module.exports = {
|
||||
});
|
||||
},
|
||||
|
||||
'assert.isUndefined()': function(assert){
|
||||
'assert.isUndefined()': function(){
|
||||
assert.isUndefined(undefined);
|
||||
assert.throws(function(){
|
||||
assert.isUndefined(null);
|
||||
@@ -51,7 +58,7 @@ module.exports = {
|
||||
});
|
||||
},
|
||||
|
||||
'assert.isNotNull()': function(assert){
|
||||
'assert.isNotNull()': function(){
|
||||
assert.isNotNull(false);
|
||||
assert.isNotNull(undefined);
|
||||
assert.throws(function(){
|
||||
@@ -59,7 +66,7 @@ module.exports = {
|
||||
});
|
||||
},
|
||||
|
||||
'assert.isDefined()': function(assert){
|
||||
'assert.isDefined()': function(){
|
||||
assert.isDefined(false);
|
||||
assert.isDefined(null);
|
||||
assert.throws(function(){
|
||||
@@ -67,14 +74,14 @@ module.exports = {
|
||||
});
|
||||
},
|
||||
|
||||
'assert.match()': function(assert){
|
||||
'assert.match()': function(){
|
||||
assert.match('foobar', /foo(bar)?/);
|
||||
assert.throws(function(){
|
||||
assert.match('something', /rawr/);
|
||||
});
|
||||
},
|
||||
|
||||
'assert.length()': function(assert){
|
||||
'assert.length()': function(){
|
||||
assert.length('test', 4);
|
||||
assert.length([1,2,3,4], 4);
|
||||
assert.throws(function(){
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var assert = require('assert');
|
||||
|
||||
setTimeout(function(){
|
||||
exports['test async exports'] = function(assert){
|
||||
exports['test async exports'] = function(){
|
||||
assert.ok('wahoo');
|
||||
};
|
||||
}, 100);
|
||||
@@ -3,10 +3,11 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var bar = require('bar');
|
||||
var assert = require('assert')
|
||||
, bar = require('bar');
|
||||
|
||||
module.exports = {
|
||||
'bar()': function(assert){
|
||||
'bar()': function(){
|
||||
assert.equal('bar', bar.bar());
|
||||
}
|
||||
};
|
||||
@@ -3,10 +3,11 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var foo = require('foo');
|
||||
var assert = require('assert')
|
||||
, foo = require('foo');
|
||||
|
||||
module.exports = {
|
||||
'foo()': function(assert){
|
||||
'foo()': function(){
|
||||
assert.equal('foo', foo.foo());
|
||||
assert.equal('foo', foo.foo());
|
||||
}
|
||||
|
||||
@@ -3,80 +3,144 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var http = require('http');
|
||||
var assert = require('assert')
|
||||
, http = require('http');
|
||||
|
||||
var server = http.createServer(function(req, res){
|
||||
if (req.method === 'GET') {
|
||||
if (req.url === '/delay') {
|
||||
setTimeout(function(){
|
||||
res.writeHead(200, {});
|
||||
res.end('delayed');
|
||||
}, 200);
|
||||
} else {
|
||||
var body = JSON.stringify({ name: 'tj' });
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'application/json; charset=utf8',
|
||||
'Content-Length': body.length
|
||||
});
|
||||
res.end(body);
|
||||
}
|
||||
if (req.method === 'GET') {
|
||||
if (req.url === '/delay') {
|
||||
setTimeout(function(){
|
||||
res.writeHead(200, {});
|
||||
res.end('delayed');
|
||||
}, 200);
|
||||
} else {
|
||||
var body = '';
|
||||
req.setEncoding('utf8');
|
||||
req.addListener('data', function(chunk){ body += chunk });
|
||||
req.addListener('end', function(){
|
||||
res.writeHead(200, {});
|
||||
res.end(req.url + ' ' + body);
|
||||
});
|
||||
var body = JSON.stringify({ name: 'tj' });
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'application/json; charset=utf8',
|
||||
'Content-Length': body.length
|
||||
});
|
||||
res.end(body);
|
||||
}
|
||||
} else {
|
||||
var body = '';
|
||||
req.setEncoding('utf8');
|
||||
req.on('data', function(chunk){ body += chunk });
|
||||
req.on('end', function(){
|
||||
res.writeHead(200, {});
|
||||
res.end(req.url + ' ' + body);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
'test assert.response()': function(assert, beforeExit){
|
||||
var called = 0;
|
||||
var delayedServer = http.createServer(function(req, res){
|
||||
res.writeHead(200);
|
||||
res.end('it worked');
|
||||
});
|
||||
|
||||
assert.response(server, {
|
||||
url: '/',
|
||||
method: 'GET'
|
||||
},{
|
||||
body: '{"name":"tj"}',
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf8'
|
||||
}
|
||||
});
|
||||
|
||||
assert.response(server, {
|
||||
url: '/foo',
|
||||
method: 'POST',
|
||||
data: 'bar baz'
|
||||
},{
|
||||
body: '/foo bar baz',
|
||||
status: 200
|
||||
}, function(res){
|
||||
++called;
|
||||
assert.ok(res);
|
||||
});
|
||||
|
||||
assert.response(server, {
|
||||
url: '/foo'
|
||||
}, function(res){
|
||||
++called;
|
||||
assert.ok(res.body.indexOf('tj') >= 0, 'Test assert.response() callback');
|
||||
});
|
||||
|
||||
assert.response(server,
|
||||
{ url: '/delay', timeout: 300 },
|
||||
{ body: 'delayed' });
|
||||
|
||||
beforeExit(function(){
|
||||
assert.equal(2, called);
|
||||
});
|
||||
},
|
||||
var oldListen = delayedServer.listen;
|
||||
delayedServer.listen = function(){
|
||||
var args = arguments;
|
||||
setTimeout(function(){
|
||||
oldListen.apply(delayedServer, args);
|
||||
}, 100);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
'test assert.response(req, res, fn)': function(beforeExit){
|
||||
var calls = 0;
|
||||
|
||||
assert.response(server, {
|
||||
url: '/',
|
||||
method: 'GET'
|
||||
},{
|
||||
body: '{"name":"tj"}',
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf8'
|
||||
}
|
||||
}, function(res){
|
||||
++calls;
|
||||
assert.ok(res);
|
||||
});
|
||||
|
||||
'test assert.response() regexp': function(assert){
|
||||
assert.response(server,
|
||||
{ url: '/foo', method: 'POST', data: 'foobar' },
|
||||
{ body: /^\/foo foo(bar)?/ });
|
||||
}
|
||||
};
|
||||
beforeExit(function(){
|
||||
assert.equal(1, calls);
|
||||
})
|
||||
},
|
||||
|
||||
'test assert.response(req, fn)': function(beforeExit){
|
||||
var calls = 0;
|
||||
|
||||
assert.response(server, {
|
||||
url: '/foo'
|
||||
}, function(res){
|
||||
++calls;
|
||||
assert.ok(res.body.indexOf('tj') >= 0, 'Test assert.response() callback');
|
||||
});
|
||||
|
||||
beforeExit(function(){
|
||||
assert.equal(1, calls);
|
||||
});
|
||||
},
|
||||
|
||||
'test assert.response() delay': function(beforeExit){
|
||||
var calls = 0;
|
||||
|
||||
assert.response(server,
|
||||
{ url: '/delay', timeout: 1500 },
|
||||
{ body: 'delayed' },
|
||||
function(){
|
||||
++calls;
|
||||
});
|
||||
|
||||
beforeExit(function(){
|
||||
assert.equal(1, calls);
|
||||
});
|
||||
},
|
||||
|
||||
'test assert.response() regexp': function(beforeExit){
|
||||
var calls = 0;
|
||||
|
||||
assert.response(server,
|
||||
{ url: '/foo', method: 'POST', data: 'foobar' },
|
||||
{ body: /^\/foo foo(bar)?/ },
|
||||
function(){
|
||||
++calls;
|
||||
});
|
||||
|
||||
beforeExit(function(){
|
||||
assert.equal(1, calls);
|
||||
});
|
||||
},
|
||||
|
||||
'test assert.response() regexp headers': function(beforeExit){
|
||||
var calls = 0;
|
||||
|
||||
assert.response(server,
|
||||
{ url: '/' },
|
||||
{ body: '{"name":"tj"}', headers: { 'Content-Type': /^application\/json/ } },
|
||||
function(){
|
||||
++calls;
|
||||
});
|
||||
|
||||
beforeExit(function(){
|
||||
assert.equal(1, calls);
|
||||
});
|
||||
},
|
||||
|
||||
// [!] if this test doesn't pass, an uncaught ECONNREFUSED will display
|
||||
'test assert.response() with deferred listen()': function(beforeExit){
|
||||
var calls = 0;
|
||||
|
||||
assert.response(delayedServer,
|
||||
{ url: '/' },
|
||||
{ body: 'it worked' },
|
||||
function(){
|
||||
++calls;
|
||||
});
|
||||
|
||||
beforeExit(function(){
|
||||
assert.equal(1, calls);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
var setup = 0,
|
||||
order = [];
|
||||
var assert = require('assert')
|
||||
, setup = 0
|
||||
, order = [];
|
||||
|
||||
module.exports = {
|
||||
setup: function(done){
|
||||
@@ -8,7 +9,7 @@ module.exports = {
|
||||
done();
|
||||
},
|
||||
|
||||
a: function(assert, done){
|
||||
a: function(done){
|
||||
assert.equal(1, setup);
|
||||
order.push('a');
|
||||
setTimeout(function(){
|
||||
@@ -16,7 +17,7 @@ module.exports = {
|
||||
}, 500);
|
||||
},
|
||||
|
||||
b: function(assert, done){
|
||||
b: function(done){
|
||||
assert.equal(2, setup);
|
||||
order.push('b');
|
||||
setTimeout(function(){
|
||||
@@ -24,7 +25,7 @@ module.exports = {
|
||||
}, 200);
|
||||
},
|
||||
|
||||
c: function(assert, done){
|
||||
c: function(done){
|
||||
assert.equal(3, setup);
|
||||
order.push('c');
|
||||
setTimeout(function(){
|
||||
@@ -32,7 +33,7 @@ module.exports = {
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
d: function(assert){
|
||||
d: function(){
|
||||
assert.eql(order, ['a', 'b', 'c']);
|
||||
}
|
||||
};
|
||||
@@ -3,7 +3,8 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var http = require('http');
|
||||
var assert = require('assert')
|
||||
, http = require('http');
|
||||
|
||||
var server = http.createServer(function(req, res){
|
||||
if (req.method === 'GET') {
|
||||
@@ -32,7 +33,7 @@ var server = http.createServer(function(req, res){
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
'test assert.response()': function(assert, done){
|
||||
'test assert.response()': function(done){
|
||||
assert.response(server, {
|
||||
url: '/',
|
||||
method: 'GET'
|
||||
|
||||
3
support/socket.io-client/.gitmodules
vendored
3
support/socket.io-client/.gitmodules
vendored
@@ -1,3 +0,0 @@
|
||||
[submodule "lib/vendor/web-socket-js"]
|
||||
path = lib/vendor/web-socket-js
|
||||
url = git://github.com/gimite/web-socket-js.git
|
||||
21
support/socket.io-client/History.md
Normal file
21
support/socket.io-client/History.md
Normal file
@@ -0,0 +1,21 @@
|
||||
|
||||
0.7.0 / 2011-??-??
|
||||
==================
|
||||
|
||||
* Fixed JSONP interaction with jQuery. [saschagehlich]
|
||||
* Fixed; different port now considered cross-domain.
|
||||
* Added compatibility for inclusion in non-browser environments.
|
||||
* Added package.json.
|
||||
* Added noConflict support. [kreichgauer]
|
||||
* Added reconnection support with exponential backoff. [3rd-Eden]
|
||||
|
||||
0.6.2 / 2011-02-05
|
||||
==================
|
||||
|
||||
* Fixed problem with xhr-multipart buffering
|
||||
* Updated Flash websocket transport
|
||||
* Fixed tryTransportsOnConnectTimeout option
|
||||
* Added 'connect_failed' event after the last available transport fails to connect
|
||||
within the timeout
|
||||
* Add 'connecting' event emit on each connection attempt.
|
||||
|
||||
@@ -46,14 +46,6 @@ The `socket.io` client is basically a simple HTTP Socket interface implementatio
|
||||
- Easy to use! See [socket.io-node](http://github.com/LearnBoost/Socket.IO-node) for the server to connect to.
|
||||
|
||||
### How to use
|
||||
|
||||
The recommended way of including the Socket.IO client is through the Socket.IO CDN:
|
||||
|
||||
In your <head>
|
||||
|
||||
<script src="http://cdn.socket.io/stable/socket.io.js"></script>
|
||||
|
||||
Then, in your code
|
||||
|
||||
socket = new io.Socket('localhost');
|
||||
socket.connect();
|
||||
@@ -72,27 +64,25 @@ If you are serving you .swf from a other domain than socket.io.js you will need
|
||||
|
||||
The insecure version can be found [here](http://github.com/gimite/web-socket-js/blob/master/WebSocketMainInsecure.zip).
|
||||
|
||||
IMPORTANT! When checking out the git repo, make sure to include the submodules. One way to do it is:
|
||||
|
||||
git clone [repo] --recursive
|
||||
|
||||
Another, once cloned
|
||||
|
||||
git submodule update --init --recursive
|
||||
|
||||
### Documentation
|
||||
|
||||
#### io.Socket
|
||||
|
||||
new io.Socket(host, [options]);
|
||||
|
||||
Options:
|
||||
##### Options:
|
||||
|
||||
- *secure*
|
||||
|
||||
false
|
||||
|
||||
Use secure connections
|
||||
|
||||
- *port*
|
||||
|
||||
Current port or 80
|
||||
|
||||
The port `socket.io` server is attached to (defaults to the document.location port)
|
||||
The port `socket.io` server is attached to (defaults to the document.location port).
|
||||
|
||||
- *resource*
|
||||
|
||||
@@ -102,9 +92,9 @@ Options:
|
||||
|
||||
- *transports*
|
||||
|
||||
['websocket', 'flashsocket', 'htmlfile', 'xhr-multipart', 'xhr-polling']
|
||||
['websocket', 'flashsocket', 'htmlfile', 'xhr-multipart', 'xhr-polling', 'jsonp-polling']
|
||||
|
||||
A list of the transports to attempt to utilize (in order of preference)
|
||||
A list of the transports to attempt to utilize (in order of preference).
|
||||
|
||||
- *transportOptions*
|
||||
|
||||
@@ -117,11 +107,48 @@ Options:
|
||||
|
||||
An object containing (optional) options to pass to each transport.
|
||||
|
||||
Properties:
|
||||
- *rememberTransport*
|
||||
|
||||
true
|
||||
|
||||
A boolean indicating if the utilized transport should be remembered in a cookie.
|
||||
|
||||
- *connectTimeout*
|
||||
|
||||
5000
|
||||
|
||||
The amount of miliseconds a transport has to create a connection before we consider it timed out.
|
||||
|
||||
- *tryTransportsOnConnectTimeout*
|
||||
|
||||
true
|
||||
|
||||
A boolean indicating if we should try other transports when the connectTimeout occurs.
|
||||
|
||||
- *reconnect*
|
||||
|
||||
true
|
||||
|
||||
A boolean indicating if we should automatically reconnect if a connection is disconnected.
|
||||
|
||||
- *reconnectionDelay*
|
||||
|
||||
500
|
||||
|
||||
The amount of milliseconds before we try to connect to the server again. We are using a exponential back off algorithm for the following reconnections, on each reconnect attempt this value will get multiplied (500 > 1000 > 2000 > 4000 > 8000).
|
||||
|
||||
|
||||
- *maxReconnectionAttempts*
|
||||
|
||||
10
|
||||
|
||||
The amount of attempts should we make using the current transport to connect to the server? After this we will do one final attempt, and re-try with all enabled transport methods before we give up.
|
||||
|
||||
##### Properties:
|
||||
|
||||
- *options*
|
||||
|
||||
The passed in options combined with the defaults
|
||||
The passed in options combined with the defaults.
|
||||
|
||||
- *connected*
|
||||
|
||||
@@ -130,16 +157,20 @@ Properties:
|
||||
- *connecting*
|
||||
|
||||
Whether the socket is connecting or not.
|
||||
|
||||
- *reconnecting*
|
||||
|
||||
Whether we are reconnecting or not.
|
||||
|
||||
- *transport*
|
||||
|
||||
The transport instance.
|
||||
|
||||
Methods:
|
||||
##### Methods:
|
||||
|
||||
- *connect*
|
||||
- *connect(λ)*
|
||||
|
||||
Establishes a connection
|
||||
Establishes a connection. If λ is supplied as argument, it will be called once the connection is established.
|
||||
|
||||
- *send(message)*
|
||||
|
||||
@@ -147,21 +178,36 @@ Methods:
|
||||
|
||||
- *disconnect*
|
||||
|
||||
Closes the connection
|
||||
Closes the connection.
|
||||
|
||||
- *on(event, λ)*
|
||||
|
||||
Adds a listener for the event *event*
|
||||
Adds a listener for the event *event*.
|
||||
|
||||
- *once(event, λ)*
|
||||
|
||||
Adds a one time listener for the event *event*. The listener is removed after the first time the event is fired.
|
||||
|
||||
- *removeEvent(event, λ)*
|
||||
|
||||
Removes the listener λ for the event *event*
|
||||
Removes the listener λ for the event *event*.
|
||||
|
||||
Events:
|
||||
##### Events:
|
||||
|
||||
- *connect*
|
||||
|
||||
Fired when the connection is established and the handshake successful
|
||||
Fired when the connection is established and the handshake successful.
|
||||
|
||||
- *connecting(transport_type)*
|
||||
|
||||
Fired when a connection is attempted, passing the transport name.
|
||||
|
||||
- *connect_failed*
|
||||
|
||||
Fired when the connection timeout occurs after the last connection attempt.
|
||||
This only fires if the `connectTimeout` option is set.
|
||||
If the `tryTransportsOnConnectTimeout` option is set, this only fires once all
|
||||
possible transports have been tried.
|
||||
|
||||
- *message(message)*
|
||||
|
||||
@@ -174,19 +220,25 @@ Events:
|
||||
- *disconnect*
|
||||
|
||||
Fired when the connection is considered disconnected.
|
||||
|
||||
- *reconnect(transport_type,reconnectionAttempts)*
|
||||
|
||||
### Changelog
|
||||
Fired when the connection has been re-established. This only fires if the `reconnect` option is set.
|
||||
|
||||
2010 08 02 - **0.5.4** (9.95KB)
|
||||
- *reconnecting(reconnectionDelay,reconnectionAttempts)*
|
||||
|
||||
* Added io.util.load as a reusable onload handler
|
||||
* Added io.util.ios which reports if the UA is running on iPhone or iPad
|
||||
* No more loading bar on iPhone: XHR-Polling now connects `onload` for the iOS WebKit, and waits 10 ms to launch the initial connection.
|
||||
Fired when a reconnection is attempted, passing the next delay for the next reconnection.
|
||||
|
||||
### Credits
|
||||
- *reconnect_failed*
|
||||
|
||||
Fired when all reconnection attempts have failed and we where unsuccessful in reconnecting to the server.
|
||||
|
||||
### Contributors
|
||||
|
||||
Guillermo Rauch <guillermo@learnboost.com>
|
||||
|
||||
Arnout Kazemier <info@3rd-eden.com>
|
||||
|
||||
### License
|
||||
|
||||
(The MIT License)
|
||||
@@ -210,4 +262,4 @@ 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.
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
@@ -14,8 +14,10 @@
|
||||
*/
|
||||
|
||||
var fs = require('fs'),
|
||||
sys = require('sys'),
|
||||
socket = require('../lib/io'),
|
||||
jsp = require('../lib/vendor/uglifyjs/lib/parse-js'),
|
||||
pro = require("../lib/vendor/uglifyjs/lib/process"),
|
||||
ast,
|
||||
files = [
|
||||
'io.js',
|
||||
'util.js',
|
||||
@@ -29,22 +31,29 @@ var fs = require('fs'),
|
||||
'transports/jsonp-polling.js',
|
||||
'socket.js',
|
||||
'vendor/web-socket-js/swfobject.js',
|
||||
'vendor/web-socket-js/FABridge.js',
|
||||
'vendor/web-socket-js/web_socket.js'
|
||||
],
|
||||
content = "/** Socket.IO "+ socket.io.version +" - Built with build.js */\n";
|
||||
content = "/** Socket.IO "+ socket.io.version +" - Built with build.js */\n",
|
||||
license = "/* Socket.IO.min "+ socket.io.version +" @author Guillermo Rauch <guillermo@learnboost.com>, @license The MIT license., @copyright Copyright (c) 2010 LearnBoost <dev@learnboost.com> */\n";
|
||||
|
||||
sys.log('Reading files…');
|
||||
console.log('Reading files…');
|
||||
|
||||
files.forEach(function(file){
|
||||
var path = __dirname + '/../lib/' + file;
|
||||
sys.log (' + ' + path);
|
||||
console.log(' + ' + path);
|
||||
content += fs.readFileSync(path) + "\n";
|
||||
});
|
||||
|
||||
sys.log('Generating…');
|
||||
console.log('Generating…');
|
||||
|
||||
fs.write(fs.openSync(__dirname + '/../socket.io.js', 'w'), content, 0, 'utf8');
|
||||
sys.log(' + ' + __dirname + '/../socket.io.js');
|
||||
console.log(' + ' + __dirname + '/../socket.io.js');
|
||||
|
||||
sys.log('All done!');
|
||||
console.log('Uglyfying…');
|
||||
ast = jsp.parse(content);
|
||||
ast = pro.ast_mangle(ast); // get a new AST with mangled names
|
||||
ast = pro.ast_squeeze(ast);
|
||||
fs.write(fs.openSync(__dirname + '/../socket.io.min.js', 'w'), license + pro.gen_code(ast), 0, 'utf8');
|
||||
console.log(' + ' + __dirname + '/../socket.io.min.js');
|
||||
|
||||
console.log('All done!');
|
||||
@@ -1,24 +1,46 @@
|
||||
/**
|
||||
* Socket.IO client
|
||||
*
|
||||
* @author Guillermo Rauch <guillermo@learnboost.com>
|
||||
* @license The MIT license.
|
||||
* @copyright Copyright (c) 2010 LearnBoost <dev@learnboost.com>
|
||||
* socket.io-node-client
|
||||
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
this.io = {
|
||||
version: '0.6',
|
||||
|
||||
setPath: function(path){
|
||||
if (window.console && console.error) console.error('io.setPath will be removed. Please set the variable WEB_SOCKET_SWF_LOCATION pointing to WebSocketMain.swf');
|
||||
this.path = /\/$/.test(path) ? path : path + '/';
|
||||
WEB_SOCKET_SWF_LOCATION = path + 'lib/vendor/web-socket-js/WebSocketMain.swf';
|
||||
}
|
||||
/**
|
||||
* @namespace
|
||||
*/
|
||||
var io = this.io = {
|
||||
|
||||
/**
|
||||
* Library version.
|
||||
*/
|
||||
version: '0.6.3',
|
||||
|
||||
/**
|
||||
* Updates the location of the WebSocketMain.swf file that is required for the Flashsocket transport.
|
||||
* This should only be needed if you want to load in the WebSocketMainInsecure.swf or if you want to
|
||||
* host the .swf file on a other server.
|
||||
*
|
||||
* @static
|
||||
* @deprecated Set the variable `WEB_SOCKET_SWF_LOCATION` pointing to WebSocketMain.swf
|
||||
* @param {String} path The path of the .swf file
|
||||
* @api public
|
||||
*/
|
||||
setPath: function(path){
|
||||
if (window.console && console.error) console.error('io.setPath will be removed. Please set the variable WEB_SOCKET_SWF_LOCATION pointing to WebSocketMain.swf');
|
||||
this.path = /\/$/.test(path) ? path : path + '/';
|
||||
WEB_SOCKET_SWF_LOCATION = path + 'lib/vendor/web-socket-js/WebSocketMain.swf';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Expose Socket.IO in jQuery
|
||||
*/
|
||||
if ('jQuery' in this) jQuery.io = this.io;
|
||||
|
||||
/**
|
||||
* Default path to the .swf file.
|
||||
*/
|
||||
if (typeof window != 'undefined'){
|
||||
// WEB_SOCKET_SWF_LOCATION = (document.location.protocol == 'https:' ? 'https:' : 'http:') + '//cdn.socket.io/' + this.io.version + '/WebSocketMain.swf';
|
||||
WEB_SOCKET_SWF_LOCATION = '/socket.io/lib/vendor/web-socket-js/WebSocketMain.swf';
|
||||
}
|
||||
if (typeof WEB_SOCKET_SWF_LOCATION === 'undefined')
|
||||
WEB_SOCKET_SWF_LOCATION = '/socket.io/lib/vendor/web-socket-js/WebSocketMain.swf';
|
||||
}
|
||||
|
||||
@@ -1,157 +1,462 @@
|
||||
/**
|
||||
* Socket.IO client
|
||||
*
|
||||
* @author Guillermo Rauch <guillermo@learnboost.com>
|
||||
* @license The MIT license.
|
||||
* @copyright Copyright (c) 2010 LearnBoost <dev@learnboost.com>
|
||||
* socket.io-node-client
|
||||
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
(function(){
|
||||
|
||||
var Socket = io.Socket = function(host, options){
|
||||
this.host = host || document.domain;
|
||||
this.options = {
|
||||
secure: false,
|
||||
document: document,
|
||||
port: document.location.port || 80,
|
||||
resource: 'socket.io',
|
||||
transports: ['websocket', 'flashsocket', 'htmlfile', 'xhr-multipart', 'xhr-polling', 'jsonp-polling'],
|
||||
transportOptions: {
|
||||
'xhr-polling': {
|
||||
timeout: 25000 // based on polling duration default
|
||||
},
|
||||
'jsonp-polling': {
|
||||
timeout: 25000
|
||||
}
|
||||
},
|
||||
connectTimeout: 5000,
|
||||
tryTransportsOnConnectTimeout: true,
|
||||
rememberTransport: true
|
||||
};
|
||||
io.util.merge(this.options, options);
|
||||
this.connected = false;
|
||||
this.connecting = false;
|
||||
this._events = {};
|
||||
this.transport = this.getTransport();
|
||||
if (!this.transport && 'console' in window) console.error('No transport available');
|
||||
};
|
||||
|
||||
Socket.prototype.getTransport = function(override){
|
||||
var transports = override || this.options.transports, match;
|
||||
if (this.options.rememberTransport && !override){
|
||||
match = this.options.document.cookie.match('(?:^|;)\\s*socketio=([^;]*)');
|
||||
if (match){
|
||||
this._rememberedTransport = true;
|
||||
transports = [decodeURIComponent(match[1])];
|
||||
}
|
||||
}
|
||||
for (var i = 0, transport; transport = transports[i]; i++){
|
||||
if (io.Transport[transport]
|
||||
&& io.Transport[transport].check()
|
||||
&& (!this._isXDomain() || io.Transport[transport].xdomainCheck())){
|
||||
return new io.Transport[transport](this, this.options.transportOptions[transport] || {});
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
Socket.prototype.connect = function(){
|
||||
if (this.transport && !this.connected){
|
||||
if (this.connecting) this.disconnect();
|
||||
this.connecting = true;
|
||||
this.transport.connect();
|
||||
if (this.options.connectTimeout){
|
||||
var self = this;
|
||||
setTimeout(function(){
|
||||
if (!self.connected){
|
||||
self.disconnect();
|
||||
if (self.options.tryTransportsOnConnectTimeout && !self._rememberedTransport){
|
||||
var remainingTransports = [], transports = self.options.transports;
|
||||
for (var i = 0, transport; transport = transports[i]; i++){
|
||||
if (transport != self.transport.type) remainingTransports.push(transport);
|
||||
}
|
||||
if (remainingTransports.length){
|
||||
self.transport = self.getTransport(remainingTransports);
|
||||
self.connect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}, this.options.connectTimeout);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
Socket.prototype.send = function(data){
|
||||
if (!this.transport || !this.transport.connected) return this._queue(data);
|
||||
this.transport.send(data);
|
||||
return this;
|
||||
};
|
||||
|
||||
Socket.prototype.disconnect = function(){
|
||||
this.transport.disconnect();
|
||||
return this;
|
||||
};
|
||||
|
||||
Socket.prototype.on = function(name, fn){
|
||||
if (!(name in this._events)) this._events[name] = [];
|
||||
this._events[name].push(fn);
|
||||
return this;
|
||||
};
|
||||
|
||||
Socket.prototype.fire = function(name, args){
|
||||
if (name in this._events){
|
||||
for (var i = 0, ii = this._events[name].length; i < ii; i++)
|
||||
this._events[name][i].apply(this, args === undefined ? [] : args);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
Socket.prototype.removeEvent = function(name, fn){
|
||||
if (name in this._events){
|
||||
for (var a = 0, l = this._events[name].length; a < l; a++)
|
||||
if (this._events[name][a] == fn) this._events[name].splice(a, 1);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
Socket.prototype._queue = function(message){
|
||||
if (!('_queueStack' in this)) this._queueStack = [];
|
||||
this._queueStack.push(message);
|
||||
return this;
|
||||
};
|
||||
|
||||
Socket.prototype._doQueue = function(){
|
||||
if (!('_queueStack' in this) || !this._queueStack.length) return this;
|
||||
this.transport.send(this._queueStack);
|
||||
this._queueStack = [];
|
||||
return this;
|
||||
};
|
||||
|
||||
Socket.prototype._isXDomain = function(){
|
||||
return this.host !== document.domain;
|
||||
};
|
||||
|
||||
Socket.prototype._onConnect = function(){
|
||||
this.connected = true;
|
||||
this.connecting = false;
|
||||
this._doQueue();
|
||||
if (this.options.rememberTransport) this.options.document.cookie = 'socketio=' + encodeURIComponent(this.transport.type);
|
||||
this.fire('connect');
|
||||
};
|
||||
|
||||
Socket.prototype._onMessage = function(data){
|
||||
this.fire('message', [data]);
|
||||
};
|
||||
|
||||
Socket.prototype._onDisconnect = function(){
|
||||
var wasConnected = this.connected;
|
||||
this.connected = false;
|
||||
this.connecting = false;
|
||||
this._queueStack = [];
|
||||
if (wasConnected) this.fire('disconnect');
|
||||
};
|
||||
|
||||
Socket.prototype.addListener = Socket.prototype.addEvent = Socket.prototype.addEventListener = Socket.prototype.on;
|
||||
|
||||
var io = this.io;
|
||||
|
||||
/**
|
||||
* Create a new `Socket.IO client` which can establish a persisted
|
||||
* connection with a Socket.IO enabled server.
|
||||
*
|
||||
* Options:
|
||||
* - `secure` Use secure connections, defaulting to false.
|
||||
* - `document` Reference to the document object to retrieve and set cookies, defaulting to document.
|
||||
* - `port` The port where the Socket.IO server listening on, defaulting to location.port.
|
||||
* - `resource` The path or namespace on the server where the Socket.IO requests are intercepted, defaulting to 'socket.io'.
|
||||
* - `transports` A ordered list with the available transports, defaulting to all transports.
|
||||
* - `transportOption` A {Object} containing the options for each transport. The key of the object should reflect
|
||||
* name of the transport and the value a {Object} with the options.
|
||||
* - `connectTimeout` The duration in milliseconds that a transport has to establish a working connection, defaulting to 5000.
|
||||
* - `tryTransportsOnConnectTimeout` Should we attempt other transport methods when the connectTimeout occurs, defaulting to true.
|
||||
* - `reconnect` Should reconnection happen automatically, defaulting to true.
|
||||
* - `reconnectionDelay` The delay in milliseconds before we attempt to establish a working connection. This value will
|
||||
* increase automatically using a exponential back off algorithm. Defaulting to 500.
|
||||
* - `maxReconnectionAttempts` Number of attempts we should make before seizing the reconnect operation, defaulting to 10.
|
||||
* - `rememberTransport` Should the successfully connected transport be remembered in a cookie, defaulting to true.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* Create client with the default settings.
|
||||
*
|
||||
* var socket = new io.Socket();
|
||||
* socket.connect();
|
||||
* socket.on('message', function(msg){
|
||||
* console.log('Received message: ' + msg );
|
||||
* });
|
||||
* socket.on('connect', function(){
|
||||
* socket.send('Hello from client');
|
||||
* });
|
||||
*
|
||||
* Create a connection with server on a different port and host.
|
||||
*
|
||||
* var socket = new io.Socket('http://example.com',{port:1337});
|
||||
*
|
||||
* @constructor
|
||||
* @exports Socket as io.Socket
|
||||
* @param {String} [host] The host where the Socket.IO server is located, it defaults to the host that runs the page.
|
||||
* @param {Objects} [options] The options that will configure the Socket.IO client.
|
||||
* @property {String} host The supplied host arguments or the host that page runs.
|
||||
* @property {Object} options The passed options combined with the defaults.
|
||||
* @property {Boolean} connected Whether the socket is connected or not.
|
||||
* @property {Boolean} connecting Whether the socket is connecting or not.
|
||||
* @property {Boolean} reconnecting Whether the socket is reconnecting or not.
|
||||
* @property {Object} transport The selected transport instance.
|
||||
* @api public
|
||||
*/
|
||||
var Socket = io.Socket = function(host, options){
|
||||
this.host = host || document.domain;
|
||||
this.options = {
|
||||
secure: false,
|
||||
document: document,
|
||||
port: document.location.port || 80,
|
||||
resource: 'socket.io',
|
||||
transports: ['websocket', 'flashsocket', 'htmlfile', 'xhr-multipart', 'xhr-polling', 'jsonp-polling'],
|
||||
transportOptions: {
|
||||
'xhr-polling': {
|
||||
timeout: 25000 // based on polling duration default
|
||||
},
|
||||
'jsonp-polling': {
|
||||
timeout: 25000
|
||||
}
|
||||
},
|
||||
connectTimeout: 5000,
|
||||
tryTransportsOnConnectTimeout: true,
|
||||
reconnect: true,
|
||||
reconnectionDelay: 500,
|
||||
maxReconnectionAttempts: 10,
|
||||
rememberTransport: true
|
||||
};
|
||||
io.util.merge(this.options, options);
|
||||
this.connected = false;
|
||||
this.connecting = false;
|
||||
this.reconnecting = false;
|
||||
this.events = {};
|
||||
this.transport = this.getTransport();
|
||||
if (!this.transport && 'console' in window) console.error('No transport available');
|
||||
};
|
||||
|
||||
/**
|
||||
* Find an available transport based on the options supplied in the constructor. For example if the
|
||||
* `rememberTransport` option was set we will only connect with the previous successfully connected transport.
|
||||
* The supplied transports can be overruled if the `override` argument is supplied.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* Override the existing transports.
|
||||
*
|
||||
* var socket = new io.Socket();
|
||||
* socket.getTransport(['jsonp-polling','websocket']);
|
||||
* // returns the json-polling transport because it's availabe in all browsers.
|
||||
*
|
||||
* @param {Array} [override] A ordered list with transports that should be used instead of the options.transports.
|
||||
* @returns {Null|Transport} The available transport.
|
||||
* @api private
|
||||
*/
|
||||
Socket.prototype.getTransport = function(override){
|
||||
var transports = override || this.options.transports, match;
|
||||
if (this.options.rememberTransport && !override){
|
||||
match = this.options.document.cookie.match('(?:^|;)\\s*socketio=([^;]*)');
|
||||
if (match){
|
||||
this.rememberedTransport = true;
|
||||
transports = [decodeURIComponent(match[1])];
|
||||
}
|
||||
}
|
||||
for (var i = 0, transport; transport = transports[i]; i++){
|
||||
if (io.Transport[transport]
|
||||
&& io.Transport[transport].check()
|
||||
&& (!this.isXDomain() || io.Transport[transport].xdomainCheck())){
|
||||
return new io.Transport[transport](this, this.options.transportOptions[transport] || {});
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Establish a new connection with the Socket.IO server. This is done using the selected transport by the
|
||||
* getTransport method. If the `connectTimeout` and the `tryTransportsOnConnectTimeout` options are set
|
||||
* the client will keep trying to connect to the server using a different transports when the timeout occurs.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* Create a Socket.IO client with a connect callback (We assume we have the WebSocket transport avaliable).
|
||||
*
|
||||
* var socket = new io.Socket();
|
||||
* socket.connect(function(transport){
|
||||
* console.log("Connected to server using the " + socket.transport.type + " transport.");
|
||||
* });
|
||||
* // => "Connected to server using the WebSocket transport."
|
||||
*
|
||||
* @param {Function} [fn] Callback.
|
||||
* @returns {io.Socket}
|
||||
* @api public
|
||||
*/
|
||||
Socket.prototype.connect = function(fn){
|
||||
if (this.transport && !this.connected){
|
||||
if (this.connecting) this.disconnect(true);
|
||||
this.connecting = true;
|
||||
this.emit('connecting', [this.transport.type]);
|
||||
this.transport.connect();
|
||||
if (this.options.connectTimeout){
|
||||
var self = this;
|
||||
this.connectTimeoutTimer = setTimeout(function(){
|
||||
if (!self.connected){
|
||||
self.disconnect(true);
|
||||
if (self.options.tryTransportsOnConnectTimeout && !self.rememberedTransport){
|
||||
if(!self.remainingTransports) self.remainingTransports = self.options.transports.slice(0);
|
||||
var transports = self.remainingTransports;
|
||||
while(transports.length > 0 && transports.splice(0,1)[0] != self.transport.type){}
|
||||
if(transports.length){
|
||||
self.transport = self.getTransport(transports);
|
||||
self.connect();
|
||||
}
|
||||
}
|
||||
if(!self.remainingTransports || self.remainingTransports.length == 0) self.emit('connect_failed');
|
||||
}
|
||||
if(self.remainingTransports && self.remainingTransports.length == 0) delete self.remainingTransports;
|
||||
}, this.options.connectTimeout);
|
||||
}
|
||||
}
|
||||
if (fn && typeof fn == 'function') this.once('connect',fn);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends the data to the Socket.IO server. If there isn't a connection to the server
|
||||
* the data will be forwarded to the queue.
|
||||
*
|
||||
* @param {Mixed} data The data that needs to be send to the Socket.IO server.
|
||||
* @returns {io.Socket}
|
||||
* @api public
|
||||
*/
|
||||
Socket.prototype.send = function(data){
|
||||
if (!this.transport || !this.transport.connected) return this.queue(data);
|
||||
this.transport.send(data);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Disconnect the established connect.
|
||||
*
|
||||
* @param {Boolean} [soft] A soft disconnect will keep the reconnect settings enabled.
|
||||
* @returns {io.Socket}
|
||||
* @api public
|
||||
*/
|
||||
Socket.prototype.disconnect = function(soft){
|
||||
if (this.connectTimeoutTimer) clearTimeout(this.connectTimeoutTimer);
|
||||
if (!soft) this.options.reconnect = false;
|
||||
this.transport.disconnect();
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a new eventListener for the given event.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* var socket = new io.Socket();
|
||||
* socket.on("connect", function(transport){
|
||||
* console.log("Connected to server using the " + socket.transport.type + " transport.");
|
||||
* });
|
||||
* // => "Connected to server using the WebSocket transport."
|
||||
*
|
||||
* @param {String} name The name of the event.
|
||||
* @param {Function} fn The function that is called once the event is emitted.
|
||||
* @returns {io.Socket}
|
||||
* @api public
|
||||
*/
|
||||
Socket.prototype.on = function(name, fn){
|
||||
if (!(name in this.events)) this.events[name] = [];
|
||||
this.events[name].push(fn);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a one time listener, the listener will be removed after the event is emitted.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* var socket = new io.Socket();
|
||||
* socket.once("custom:event", function(){
|
||||
* console.log("I should only log once.");
|
||||
* });
|
||||
* socket.emit("custom:event");
|
||||
* socket.emit("custom:event");
|
||||
* // => "I should only log once."
|
||||
*
|
||||
* @param {String} name The name of the event.
|
||||
* @param {Function} fn The function that is called once the event is emitted.
|
||||
* @returns {io.Socket}
|
||||
* @api public
|
||||
*/
|
||||
Socket.prototype.once = function(name, fn){
|
||||
var self = this
|
||||
, once = function(){
|
||||
self.removeEvent(name, once);
|
||||
fn.apply(self, arguments);
|
||||
};
|
||||
once.ref = fn;
|
||||
self.on(name, once);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Emit a event to all listeners.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* var socket = new io.Socket();
|
||||
* socket.on("custom:event", function(){
|
||||
* console.log("Emitted a custom:event");
|
||||
* });
|
||||
* socket.emit("custom:event");
|
||||
* // => "Emitted a custom:event"
|
||||
*
|
||||
* @param {String} name The name of the event.
|
||||
* @param {Array} args Arguments for the event.
|
||||
* @returns {io.Socket}
|
||||
* @api private
|
||||
*/
|
||||
Socket.prototype.emit = function(name, args){
|
||||
if (name in this.events){
|
||||
var events = this.events[name].concat();
|
||||
for (var i = 0, ii = events.length; i < ii; i++)
|
||||
events[i].apply(this, args === undefined ? [] : args);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes a event listener from the listener array for the specified event.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* var socket = new io.Socket()
|
||||
* , event = function(){};
|
||||
* socket.on("connect", event);
|
||||
* socket.removeEvent("connect", event);
|
||||
*
|
||||
* @param {String} name The name of the event.
|
||||
* @param {Function} fn The function that is called once the event is emitted.
|
||||
* @returns {io.Socket}
|
||||
* @api public
|
||||
*/
|
||||
Socket.prototype.removeEvent = function(name, fn){
|
||||
if (name in this.events){
|
||||
for (var a = 0, l = this.events[name].length; a < l; a++)
|
||||
if (this.events[name][a] == fn || this.events[name][a].ref && this.events[name][a].ref == fn) this.events[name].splice(a, 1);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Queues messages when there isn't a active connection available. Once a connection has been
|
||||
* established you should call the `doQueue` method to send the queued messages to the server.
|
||||
*
|
||||
* @param {Mixed} message The message that was originally send to the `send` method.
|
||||
* @returns {io.Socket}
|
||||
* @api private
|
||||
*/
|
||||
Socket.prototype.queue = function(message){
|
||||
if (!('queueStack' in this)) this.queueStack = [];
|
||||
this.queueStack.push(message);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* If there are queued messages we send all messages to the Socket.IO server and empty
|
||||
* the queue.
|
||||
*
|
||||
* @returns {io.Socket}
|
||||
* @api private
|
||||
*/
|
||||
Socket.prototype.doQueue = function(){
|
||||
if (!('queueStack' in this) || !this.queueStack.length) return this;
|
||||
this.transport.send(this.queueStack);
|
||||
this.queueStack = [];
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if we need to use cross domain enabled transports. Cross domain would
|
||||
* be a different port or different domain name.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
* @api private
|
||||
*/
|
||||
Socket.prototype.isXDomain = function(){
|
||||
var locPort = window.location.port || 80;
|
||||
return this.host !== document.domain || this.options.port != locPort;
|
||||
};
|
||||
|
||||
/**
|
||||
* When the transport established an working connection the Socket.IO server it notifies us
|
||||
* by calling this method so we can set the `connected` and `connecting` properties and emit
|
||||
* the connection event.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
Socket.prototype.onConnect = function(){
|
||||
this.connected = true;
|
||||
this.connecting = false;
|
||||
this.doQueue();
|
||||
if (this.options.rememberTransport) this.options.document.cookie = 'socketio=' + encodeURIComponent(this.transport.type);
|
||||
this.emit('connect');
|
||||
};
|
||||
|
||||
/**
|
||||
* When the transport receives new messages from the Socket.IO server it notifies us by calling
|
||||
* this method with the decoded `data` it received.
|
||||
*
|
||||
* @param data The message from the Socket.IO server.
|
||||
* @api private
|
||||
*/
|
||||
Socket.prototype.onMessage = function(data){
|
||||
this.emit('message', [data]);
|
||||
};
|
||||
|
||||
/**
|
||||
* When the transport is disconnected from the Socket.IO server it notifies us by calling
|
||||
* this method. If we where connected and the `reconnect` is set we will attempt to reconnect.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
Socket.prototype.onDisconnect = function(){
|
||||
var wasConnected = this.connected;
|
||||
this.connected = false;
|
||||
this.connecting = false;
|
||||
this.queueStack = [];
|
||||
if (wasConnected){
|
||||
this.emit('disconnect');
|
||||
if (this.options.reconnect && !this.reconnecting) this.onReconnect();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The reconnection is done using an exponential back off algorithm to prevent
|
||||
* the server from being flooded with connection requests. When the transport
|
||||
* is disconnected we wait until the `reconnectionDelay` finishes. We multiply
|
||||
* the `reconnectionDelay` (if the previous `reconnectionDelay` was 500 it will
|
||||
* be updated to 1000 and than 2000>4000>8000>16000 etc.) and tell the current
|
||||
* transport to connect again. When we run out of `reconnectionAttempts` we will
|
||||
* do one final attempt and loop over all enabled transport methods to see if
|
||||
* other transports might work. If everything fails we emit the `reconnect_failed`
|
||||
* event.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
Socket.prototype.onReconnect = function(){
|
||||
this.reconnecting = true;
|
||||
this.reconnectionAttempts = 0;
|
||||
this.reconnectionDelay = this.options.reconnectionDelay;
|
||||
|
||||
var self = this
|
||||
, tryTransportsOnConnectTimeout = this.options.tryTransportsOnConnectTimeout
|
||||
, rememberTransport = this.options.rememberTransport;
|
||||
|
||||
function reset(){
|
||||
if(self.connected) self.emit('reconnect',[self.transport.type,self.reconnectionAttempts]);
|
||||
self.removeEvent('connect_failed', maybeReconnect).removeEvent('connect', maybeReconnect);
|
||||
self.reconnecting = false;
|
||||
delete self.reconnectionAttempts;
|
||||
delete self.reconnectionDelay;
|
||||
delete self.reconnectionTimer;
|
||||
delete self.redoTransports;
|
||||
self.options.tryTransportsOnConnectTimeout = tryTransportsOnConnectTimeout;
|
||||
self.options.rememberTransport = rememberTransport;
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
function maybeReconnect(){
|
||||
if (!self.reconnecting) return;
|
||||
if (!self.connected){
|
||||
if (self.connecting && self.reconnecting) return self.reconnectionTimer = setTimeout(maybeReconnect, 1000);
|
||||
|
||||
if (self.reconnectionAttempts++ >= self.options.maxReconnectionAttempts){
|
||||
if (!self.redoTransports){
|
||||
self.on('connect_failed', maybeReconnect);
|
||||
self.options.tryTransportsOnConnectTimeout = true;
|
||||
self.transport = self.getTransport(self.options.transports); // override with all enabled transports
|
||||
self.redoTransports = true;
|
||||
self.connect();
|
||||
} else {
|
||||
self.emit('reconnect_failed');
|
||||
reset();
|
||||
}
|
||||
} else {
|
||||
self.reconnectionDelay *= 2; // exponential back off
|
||||
self.connect();
|
||||
self.emit('reconnecting', [self.reconnectionDelay,self.reconnectionAttempts]);
|
||||
self.reconnectionTimer = setTimeout(maybeReconnect, self.reconnectionDelay);
|
||||
}
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
};
|
||||
this.options.tryTransportsOnConnectTimeout = false;
|
||||
this.reconnectionTimer = setTimeout(maybeReconnect, this.reconnectionDelay);
|
||||
|
||||
this.on('connect', maybeReconnect);
|
||||
};
|
||||
|
||||
/**
|
||||
* API compatiblity
|
||||
*/
|
||||
Socket.prototype.fire = Socket.prototype.emit;
|
||||
Socket.prototype.addListener = Socket.prototype.addEvent = Socket.prototype.addEventListener = Socket.prototype.on;
|
||||
Socket.prototype.removeListener = Socket.prototype.removeEventListener = Socket.prototype.removeEvent;
|
||||
|
||||
})();
|
||||
@@ -1,141 +1,295 @@
|
||||
/**
|
||||
* Socket.IO client
|
||||
*
|
||||
* @author Guillermo Rauch <guillermo@learnboost.com>
|
||||
* @license The MIT license.
|
||||
* @copyright Copyright (c) 2010 LearnBoost <dev@learnboost.com>
|
||||
* socket.io-node-client
|
||||
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
// abstract
|
||||
|
||||
(function(){
|
||||
|
||||
var frame = '~m~',
|
||||
|
||||
stringify = function(message){
|
||||
if (Object.prototype.toString.call(message) == '[object Object]'){
|
||||
if (!('JSON' in window)){
|
||||
if ('console' in window && console.error) console.error('Trying to encode as JSON, but JSON.stringify is missing.');
|
||||
return '{ "$error": "Invalid message" }';
|
||||
}
|
||||
return '~j~' + JSON.stringify(message);
|
||||
} else {
|
||||
return String(message);
|
||||
}
|
||||
};
|
||||
|
||||
Transport = io.Transport = function(base, options){
|
||||
this.base = base;
|
||||
this.options = {
|
||||
timeout: 15000 // based on heartbeat interval default
|
||||
};
|
||||
io.util.merge(this.options, options);
|
||||
};
|
||||
var io = this.io,
|
||||
|
||||
/**
|
||||
* Message frame for encoding and decoding responses from the Socket.IO server.
|
||||
*
|
||||
* @const
|
||||
* @type {String}
|
||||
*/
|
||||
frame = '~m~',
|
||||
|
||||
/**
|
||||
* Transforms the message to a string. If the message is an {Object} we will convert it to
|
||||
* a string and prefix it with the `~j~` flag to indicate that message is JSON encoded.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* stringify({foo:"bar"});
|
||||
* // => "~j~{"foo":"bar"}"
|
||||
*
|
||||
* @param {String|Array|Object} message The messages that needs to be transformed to a string.
|
||||
* @throws {Error} When the JSON.stringify implementation is missing in the browser.
|
||||
* @returns {String} Message.
|
||||
* @api private
|
||||
*/
|
||||
stringify = function(message){
|
||||
if (Object.prototype.toString.call(message) == '[object Object]'){
|
||||
if (!('JSON' in window)){
|
||||
var error = 'Socket.IO Error: Trying to encode as JSON, but JSON.stringify is missing.';
|
||||
if ('console' in window && console.error){
|
||||
console.error(error);
|
||||
} else {
|
||||
throw new Error(error);
|
||||
}
|
||||
return '{ "$error": "'+ error +'" }';
|
||||
}
|
||||
return '~j~' + JSON.stringify(message);
|
||||
} else {
|
||||
return String(message);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* This is the transport template for all supported transport methods. It provides the
|
||||
* basic functionality to create a working transport for Socket.IO.
|
||||
*
|
||||
* Options:
|
||||
* - `timeout` Transport shutdown timeout in milliseconds, based on the heartbeat interval.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* var transport = io.Transport.mytransport = function(){
|
||||
* io.Transport.apply(this, arguments);
|
||||
* };
|
||||
* io.util.inherit(transport, io.Transport);
|
||||
*
|
||||
* ... // more code here
|
||||
*
|
||||
* // connect with your new transport
|
||||
* var socket = new io.Socket(null,{transports:['mytransport']});
|
||||
*
|
||||
* @constructor
|
||||
* @param {Object} base The reference to io.Socket.
|
||||
* @param {Object} options The transport options.
|
||||
* @property {io.Socket|Object} base The reference to io.Socket.
|
||||
* @property {Object} options The transport options, these are used to overwrite the default options
|
||||
* @property {String} sessionid The sessionid of the established connection, this is only available a connection is established
|
||||
* @property {Boolean} connected The connection has been established.
|
||||
* @property {Boolean} connecting We are still connecting to the server.
|
||||
* @api public
|
||||
*/
|
||||
Transport = io.Transport = function(base, options){
|
||||
this.base = base;
|
||||
this.options = {
|
||||
timeout: 15000 // based on heartbeat interval default
|
||||
};
|
||||
io.util.merge(this.options, options);
|
||||
};
|
||||
|
||||
Transport.prototype.send = function(){
|
||||
throw new Error('Missing send() implementation');
|
||||
};
|
||||
/**
|
||||
* Send the message to the connected Socket.IO server.
|
||||
*
|
||||
* @throws {Error} When the io.Transport is inherited, it should override this method.
|
||||
* @api public
|
||||
*/
|
||||
Transport.prototype.send = function(){
|
||||
throw new Error('Missing send() implementation');
|
||||
};
|
||||
|
||||
/**
|
||||
* Establish a connection with the Socket.IO server..
|
||||
*
|
||||
* @throws {Error} When the io.Transport is inherited, it should override this method.
|
||||
* @api public
|
||||
*/
|
||||
Transport.prototype.connect = function(){
|
||||
throw new Error('Missing connect() implementation');
|
||||
};
|
||||
|
||||
Transport.prototype.connect = function(){
|
||||
throw new Error('Missing connect() implementation');
|
||||
};
|
||||
|
||||
Transport.prototype.disconnect = function(){
|
||||
throw new Error('Missing disconnect() implementation');
|
||||
};
|
||||
|
||||
Transport.prototype._encode = function(messages){
|
||||
var ret = '', message,
|
||||
messages = io.util.isArray(messages) ? messages : [messages];
|
||||
for (var i = 0, l = messages.length; i < l; i++){
|
||||
message = messages[i] === null || messages[i] === undefined ? '' : stringify(messages[i]);
|
||||
ret += frame + message.length + frame + message;
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
Transport.prototype._decode = function(data){
|
||||
var messages = [], number, n;
|
||||
do {
|
||||
if (data.substr(0, 3) !== frame) return messages;
|
||||
data = data.substr(3);
|
||||
number = '', n = '';
|
||||
for (var i = 0, l = data.length; i < l; i++){
|
||||
n = Number(data.substr(i, 1));
|
||||
if (data.substr(i, 1) == n){
|
||||
number += n;
|
||||
} else {
|
||||
data = data.substr(number.length + frame.length);
|
||||
number = Number(number);
|
||||
break;
|
||||
}
|
||||
}
|
||||
messages.push(data.substr(0, number)); // here
|
||||
data = data.substr(number);
|
||||
} while(data !== '');
|
||||
return messages;
|
||||
};
|
||||
|
||||
Transport.prototype._onData = function(data){
|
||||
this._setTimeout();
|
||||
var msgs = this._decode(data);
|
||||
if (msgs && msgs.length){
|
||||
for (var i = 0, l = msgs.length; i < l; i++){
|
||||
this._onMessage(msgs[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Transport.prototype._setTimeout = function(){
|
||||
var self = this;
|
||||
if (this._timeout) clearTimeout(this._timeout);
|
||||
this._timeout = setTimeout(function(){
|
||||
self._onTimeout();
|
||||
}, this.options.timeout);
|
||||
};
|
||||
|
||||
Transport.prototype._onTimeout = function(){
|
||||
this._onDisconnect();
|
||||
};
|
||||
|
||||
Transport.prototype._onMessage = function(message){
|
||||
if (!this.sessionid){
|
||||
this.sessionid = message;
|
||||
this._onConnect();
|
||||
} else if (message.substr(0, 3) == '~h~'){
|
||||
this._onHeartbeat(message.substr(3));
|
||||
} else if (message.substr(0, 3) == '~j~'){
|
||||
this.base._onMessage(JSON.parse(message.substr(3)));
|
||||
} else {
|
||||
this.base._onMessage(message);
|
||||
}
|
||||
},
|
||||
|
||||
Transport.prototype._onHeartbeat = function(heartbeat){
|
||||
this.send('~h~' + heartbeat); // echo
|
||||
};
|
||||
|
||||
Transport.prototype._onConnect = function(){
|
||||
this.connected = true;
|
||||
this.connecting = false;
|
||||
this.base._onConnect();
|
||||
this._setTimeout();
|
||||
};
|
||||
|
||||
Transport.prototype._onDisconnect = function(){
|
||||
this.connecting = false;
|
||||
this.connected = false;
|
||||
this.sessionid = null;
|
||||
this.base._onDisconnect();
|
||||
};
|
||||
|
||||
Transport.prototype._prepareUrl = function(){
|
||||
return (this.base.options.secure ? 'https' : 'http')
|
||||
+ '://' + this.base.host
|
||||
+ ':' + this.base.options.port
|
||||
+ '/' + this.base.options.resource
|
||||
+ '/' + this.type
|
||||
+ (this.sessionid ? ('/' + this.sessionid) : '/');
|
||||
};
|
||||
/**
|
||||
* Disconnect the established connection.
|
||||
*
|
||||
* @throws {Error} When the io.Transport is inherited, it should override this method.
|
||||
* @api private
|
||||
*/
|
||||
Transport.prototype.disconnect = function(){
|
||||
throw new Error('Missing disconnect() implementation');
|
||||
};
|
||||
|
||||
/**
|
||||
* Encode the message by adding the `frame` to each message. This allows
|
||||
* the client so send multiple messages with only one request.
|
||||
*
|
||||
* @param {String|Array} messages Messages that need to be encoded.
|
||||
* @returns {String} Encoded message.
|
||||
* @api private
|
||||
*/
|
||||
Transport.prototype.encode = function(messages){
|
||||
var ret = '', message;
|
||||
messages = io.util.isArray(messages) ? messages : [messages];
|
||||
for (var i = 0, l = messages.length; i < l; i++){
|
||||
message = messages[i] === null || messages[i] === undefined ? '' : stringify(messages[i]);
|
||||
ret += frame + message.length + frame + message;
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decoded the response from the Socket.IO server, as the server could send multiple
|
||||
* messages in one response.
|
||||
*
|
||||
* @param (String} data The response from the server that requires decoding
|
||||
* @returns {Array} Decoded messages.
|
||||
* @api private
|
||||
*/
|
||||
Transport.prototype.decode = function(data){
|
||||
var messages = [], number, n;
|
||||
do {
|
||||
if (data.substr(0, 3) !== frame) return messages;
|
||||
data = data.substr(3);
|
||||
number = '', n = '';
|
||||
for (var i = 0, l = data.length; i < l; i++){
|
||||
n = Number(data.substr(i, 1));
|
||||
if (data.substr(i, 1) == n){
|
||||
number += n;
|
||||
} else {
|
||||
data = data.substr(number.length + frame.length);
|
||||
number = Number(number);
|
||||
break;
|
||||
}
|
||||
}
|
||||
messages.push(data.substr(0, number)); // here
|
||||
data = data.substr(number);
|
||||
} while(data !== '');
|
||||
return messages;
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles the response from the server. When a new response is received
|
||||
* it will automatically update the timeout, decode the message and
|
||||
* forwards the response to the onMessage function for further processing.
|
||||
*
|
||||
* @param {String} data Response from the server.
|
||||
* @api private
|
||||
*/
|
||||
Transport.prototype.onData = function(data){
|
||||
this.setTimeout();
|
||||
var msgs = this.decode(data);
|
||||
if (msgs && msgs.length){
|
||||
for (var i = 0, l = msgs.length; i < l; i++){
|
||||
this.onMessage(msgs[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* All the transports have a dedicated timeout to detect if
|
||||
* the connection is still alive. We clear the existing timer
|
||||
* and set new one each time this function is called. When the
|
||||
* timeout does occur it will call the `onTimeout` method.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
Transport.prototype.setTimeout = function(){
|
||||
var self = this;
|
||||
if (this.timeout) clearTimeout(this.timeout);
|
||||
this.timeout = setTimeout(function(){
|
||||
self.onTimeout();
|
||||
}, this.options.timeout);
|
||||
};
|
||||
|
||||
/**
|
||||
* Disconnect from the Socket.IO server when a timeout occurs.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
Transport.prototype.onTimeout = function(){
|
||||
this.onDisconnect();
|
||||
};
|
||||
|
||||
/**
|
||||
* After the response from the server has been parsed to individual
|
||||
* messages we need to decode them using the the Socket.IO message
|
||||
* protocol: <https://github.com/learnboost/socket.io-node/>.
|
||||
*
|
||||
* When a message is received we check if a session id has been set,
|
||||
* if the session id is missing we can assume that the received message
|
||||
* contains the sessionid of the connection.
|
||||
|
||||
* When a message is prefixed with `~h~` we dispatch it our heartbeat
|
||||
* processing method `onHeartbeat` with the content of the heartbeat.
|
||||
*
|
||||
* When the message is prefixed with `~j~` we can assume that the contents
|
||||
* of the message is JSON encoded, so we parse the message and notify
|
||||
* the base of the new message.
|
||||
*
|
||||
* If none of the above, we consider it just a plain text message and
|
||||
* notify the base of the new message.
|
||||
*
|
||||
* @param {String} message A decoded message from the server.
|
||||
* @api private
|
||||
*/
|
||||
Transport.prototype.onMessage = function(message){
|
||||
if (!this.sessionid){
|
||||
this.sessionid = message;
|
||||
this.onConnect();
|
||||
} else if (message.substr(0, 3) == '~h~'){
|
||||
this.onHeartbeat(message.substr(3));
|
||||
} else if (message.substr(0, 3) == '~j~'){
|
||||
this.base.onMessage(JSON.parse(message.substr(3)));
|
||||
} else {
|
||||
this.base.onMessage(message);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Send the received heartbeat message back to server. So the server
|
||||
* knows we are still connected.
|
||||
*
|
||||
* @param {String} heartbeat Heartbeat response from the server.
|
||||
* @api private
|
||||
*/
|
||||
Transport.prototype.onHeartbeat = function(heartbeat){
|
||||
this.send('~h~' + heartbeat); // echo
|
||||
};
|
||||
|
||||
/**
|
||||
* Notifies the base when a connection to the Socket.IO server has
|
||||
* been established. And it starts the connection `timeout` timer.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
Transport.prototype.onConnect = function(){
|
||||
this.connected = true;
|
||||
this.connecting = false;
|
||||
this.base.onConnect();
|
||||
this.setTimeout();
|
||||
};
|
||||
|
||||
/**
|
||||
* Notifies the base when the connection with the Socket.IO server
|
||||
* has been disconnected.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
Transport.prototype.onDisconnect = function(){
|
||||
this.connecting = false;
|
||||
this.connected = false;
|
||||
this.sessionid = null;
|
||||
this.base.onDisconnect();
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates a connection url based on the Socket.IO URL Protocol.
|
||||
* See <https://github.com/learnboost/socket.io-node/> for more details.
|
||||
*
|
||||
* @returns {String} Connection url
|
||||
* @api private
|
||||
*/
|
||||
Transport.prototype.prepareUrl = function(){
|
||||
return (this.base.options.secure ? 'https' : 'http')
|
||||
+ '://' + this.base.host
|
||||
+ ':' + this.base.options.port
|
||||
+ '/' + this.base.options.resource
|
||||
+ '/' + this.type
|
||||
+ (this.sessionid ? ('/' + this.sessionid) : '/');
|
||||
};
|
||||
|
||||
})();
|
||||
@@ -1,53 +1,88 @@
|
||||
/**
|
||||
* Socket.IO client
|
||||
*
|
||||
* @author Guillermo Rauch <guillermo@learnboost.com>
|
||||
* @license The MIT license.
|
||||
* @copyright Copyright (c) 2010 LearnBoost <dev@learnboost.com>
|
||||
* socket.io-node-client
|
||||
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
(function(){
|
||||
|
||||
var Flashsocket = io.Transport.flashsocket = function(){
|
||||
io.Transport.websocket.apply(this, arguments);
|
||||
};
|
||||
|
||||
io.util.inherit(Flashsocket, io.Transport.websocket);
|
||||
|
||||
Flashsocket.prototype.type = 'flashsocket';
|
||||
|
||||
Flashsocket.prototype.connect = function(){
|
||||
var self = this, args = arguments;
|
||||
WebSocket.__addTask(function(){
|
||||
io.Transport.websocket.prototype.connect.apply(self, args);
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
Flashsocket.prototype.send = function(){
|
||||
var self = this, args = arguments;
|
||||
WebSocket.__addTask(function(){
|
||||
io.Transport.websocket.prototype.send.apply(self, args);
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
Flashsocket.check = function(){
|
||||
if (typeof WebSocket == 'undefined' || !('__addTask' in WebSocket)) return false;
|
||||
if (io.util.opera) return false; // opera is buggy with this transport
|
||||
if ('navigator' in window && 'plugins' in navigator && navigator.plugins['Shockwave Flash']){
|
||||
return !!navigator.plugins['Shockwave Flash'].description;
|
||||
}
|
||||
if ('ActiveXObject' in window) {
|
||||
try {
|
||||
return !!new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
|
||||
} catch (e) {}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
Flashsocket.xdomainCheck = function(){
|
||||
return true;
|
||||
};
|
||||
|
||||
var io = this.io,
|
||||
|
||||
/**
|
||||
* The Flashsocket transport. This is a API wrapper for the HTML5 WebSocket specification.
|
||||
* It uses a .swf file to communicate with the server. If you want to serve the .swf file
|
||||
* from a other server than where the Socket.IO script is coming from you need to use the
|
||||
* insecure version of the .swf. More information about this can be found on the github page.
|
||||
*
|
||||
* @constructor
|
||||
* @extends {io.Transport.websocket}
|
||||
* @api public
|
||||
*/
|
||||
Flashsocket = io.Transport.flashsocket = function(){
|
||||
io.Transport.websocket.apply(this, arguments);
|
||||
};
|
||||
|
||||
io.util.inherit(Flashsocket, io.Transport.websocket);
|
||||
|
||||
/**
|
||||
* The transport type, you use this to identify which transport was chosen.
|
||||
*
|
||||
* @type {String}
|
||||
* @api public
|
||||
*/
|
||||
Flashsocket.prototype.type = 'flashsocket';
|
||||
|
||||
/**
|
||||
* Disconnect the established `Flashsocket` connection. This is done by adding a new
|
||||
* task to the Flashsocket. The rest will be handled off by the `WebSocket` transport.
|
||||
*
|
||||
* @returns {Transport}
|
||||
* @api public
|
||||
*/
|
||||
Flashsocket.prototype.connect = function(){
|
||||
var self = this, args = arguments;
|
||||
WebSocket.__addTask(function(){
|
||||
io.Transport.websocket.prototype.connect.apply(self, args);
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends a message to the Socket.IO server. This is done by adding a new
|
||||
* task to the Flashsocket. The rest will be handled off by the `WebSocket` transport.
|
||||
*
|
||||
* @returns {Transport}
|
||||
* @api public
|
||||
*/
|
||||
Flashsocket.prototype.send = function(){
|
||||
var self = this, args = arguments;
|
||||
WebSocket.__addTask(function(){
|
||||
io.Transport.websocket.prototype.send.apply(self, args);
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the Flashsocket transport is supported as it requires that the Adobe Flash Player
|
||||
* plugin version `10.0.0` or greater is installed. And also check if the polyfill is correctly
|
||||
* loaded.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
* @api public
|
||||
*/
|
||||
Flashsocket.check = function(){
|
||||
if (typeof WebSocket == 'undefined' || !('__addTask' in WebSocket) || !swfobject) return false;
|
||||
return swfobject.hasFlashPlayerVersion("10.0.0");
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the Flashsocket transport can be used as cross domain / cross origin transport.
|
||||
* Because we can't see which type (secure or insecure) of .swf is used we will just return true.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
* @api public
|
||||
*/
|
||||
Flashsocket.xdomainCheck = function(){
|
||||
return true;
|
||||
};
|
||||
|
||||
})();
|
||||
@@ -1,73 +1,138 @@
|
||||
/**
|
||||
* Socket.IO client
|
||||
*
|
||||
* @author Guillermo Rauch <guillermo@learnboost.com>
|
||||
* @license The MIT license.
|
||||
* @copyright Copyright (c) 2010 LearnBoost <dev@learnboost.com>
|
||||
* socket.io-node-client
|
||||
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
(function(){
|
||||
|
||||
var HTMLFile = io.Transport.htmlfile = function(){
|
||||
io.Transport.XHR.apply(this, arguments);
|
||||
};
|
||||
|
||||
io.util.inherit(HTMLFile, io.Transport.XHR);
|
||||
|
||||
HTMLFile.prototype.type = 'htmlfile';
|
||||
|
||||
HTMLFile.prototype._get = function(){
|
||||
var self = this;
|
||||
this._open();
|
||||
window.attachEvent('onunload', function(){ self._destroy(); });
|
||||
};
|
||||
|
||||
HTMLFile.prototype._open = function(){
|
||||
this._doc = new ActiveXObject('htmlfile');
|
||||
this._doc.open();
|
||||
this._doc.write('<html></html>');
|
||||
this._doc.parentWindow.s = this;
|
||||
this._doc.close();
|
||||
|
||||
var _iframeC = this._doc.createElement('div');
|
||||
this._doc.body.appendChild(_iframeC);
|
||||
this._iframe = this._doc.createElement('iframe');
|
||||
_iframeC.appendChild(this._iframe);
|
||||
this._iframe.src = this._prepareUrl() + '/' + (+ new Date);
|
||||
};
|
||||
|
||||
HTMLFile.prototype._ = function(data, doc){
|
||||
this._onData(data);
|
||||
var script = doc.getElementsByTagName('script')[0];
|
||||
script.parentNode.removeChild(script);
|
||||
};
|
||||
|
||||
HTMLFile.prototype._destroy = function(){
|
||||
if (this._iframe){
|
||||
this._iframe.src = 'about:blank';
|
||||
this._doc = null;
|
||||
var io = this.io,
|
||||
|
||||
/**
|
||||
* The HTMLFile transport creates a `forever iframe` based transport
|
||||
* for Internet Explorer. Regular forever iframe implementations will
|
||||
* continuously trigger the browsers buzy indicators. If the forever iframe
|
||||
* is created inside a `htmlfile` these indicators will not be trigged.
|
||||
*
|
||||
* @constructor
|
||||
* @extends {io.Transport.XHR}
|
||||
* @api public
|
||||
*/
|
||||
HTMLFile = io.Transport.htmlfile = function(){
|
||||
io.Transport.XHR.apply(this, arguments);
|
||||
};
|
||||
|
||||
io.util.inherit(HTMLFile, io.Transport.XHR);
|
||||
|
||||
/**
|
||||
* The transport type, you use this to identify which transport was chosen.
|
||||
*
|
||||
* @type {String}
|
||||
* @api public
|
||||
*/
|
||||
HTMLFile.prototype.type = 'htmlfile';
|
||||
|
||||
/**
|
||||
* Starts the HTMLFile data stream for incoming messages. And registers a
|
||||
* onunload event listener so the HTMLFile will be destroyed.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
HTMLFile.prototype.get = function(){
|
||||
var self = this;
|
||||
this.open();
|
||||
window.attachEvent('onunload', function(){ self.destroy(); });
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new ActiveX `htmlfile` with a forever loading iframe
|
||||
* that can be used to listen to messages. Inside the generated
|
||||
* `htmlfile` a reference will be made to the HTMLFile transport.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
HTMLFile.prototype.open = function(){
|
||||
this.doc = new ActiveXObject('htmlfile');
|
||||
this.doc.open();
|
||||
this.doc.write('<html></html>');
|
||||
this.doc.parentWindow.s = this;
|
||||
this.doc.close();
|
||||
|
||||
var iframeC = this.doc.createElement('div');
|
||||
this.doc.body.appendChild(iframeC);
|
||||
this.iframe = this.doc.createElement('iframe');
|
||||
iframeC.appendChild(this.iframe);
|
||||
this.iframe.src = this.prepareUrl() + '/' + (+ new Date);
|
||||
};
|
||||
|
||||
/**
|
||||
* The Socket.IO server will write script tags inside the forever
|
||||
* iframe, this function will be used as callback for the incoming
|
||||
* information.
|
||||
*
|
||||
* @param {String} data The message
|
||||
* @param {document} doc Reference to the context
|
||||
* @api private
|
||||
*/
|
||||
HTMLFile.prototype._ = function(data, doc){
|
||||
this.onData(data);
|
||||
var script = doc.getElementsByTagName('script')[0];
|
||||
script.parentNode.removeChild(script);
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroy the established connection, iframe and `htmlfile`.
|
||||
* And calls the `CollectGarbage` function of Internet Explorer
|
||||
* to release the memory.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
HTMLFile.prototype.destroy = function(){
|
||||
if (this.iframe){
|
||||
try {
|
||||
this.iframe.src = 'about:blank';
|
||||
} catch(e){}
|
||||
this.doc = null;
|
||||
CollectGarbage();
|
||||
}
|
||||
};
|
||||
|
||||
HTMLFile.prototype.disconnect = function(){
|
||||
this._destroy();
|
||||
return io.Transport.XHR.prototype.disconnect.call(this);
|
||||
};
|
||||
|
||||
HTMLFile.check = function(){
|
||||
if ('ActiveXObject' in window){
|
||||
try {
|
||||
var a = new ActiveXObject('htmlfile');
|
||||
return a && io.Transport.XHR.check();
|
||||
} catch(e){}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
HTMLFile.xdomainCheck = function(){
|
||||
// we can probably do handling for sub-domains, we should test that it's cross domain but a subdomain here
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Disconnects the established connection.
|
||||
*
|
||||
* @returns {Transport} Chaining.
|
||||
* @api public
|
||||
*/
|
||||
HTMLFile.prototype.disconnect = function(){
|
||||
this.destroy();
|
||||
return io.Transport.XHR.prototype.disconnect.call(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the browser supports this transport. The browser
|
||||
* must have an `ActiveXObject` implementation.
|
||||
*
|
||||
* @return {Boolean}
|
||||
* @api public
|
||||
*/
|
||||
HTMLFile.check = function(){
|
||||
if ('ActiveXObject' in window){
|
||||
try {
|
||||
var a = new ActiveXObject('htmlfile');
|
||||
return a && io.Transport.XHR.check();
|
||||
} catch(e){}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if cross domain requests are supported.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
* @api public
|
||||
*/
|
||||
HTMLFile.xdomainCheck = function(){
|
||||
// we can probably do handling for sub-domains, we should test that it's cross domain but a subdomain here
|
||||
return false;
|
||||
};
|
||||
|
||||
})();
|
||||
@@ -1,116 +1,175 @@
|
||||
/**
|
||||
* Socket.IO client
|
||||
*
|
||||
* @author Guillermo Rauch <guillermo@learnboost.com>
|
||||
* @license The MIT license.
|
||||
* @copyright Copyright (c) 2010 LearnBoost <dev@learnboost.com>
|
||||
* socket.io-node-client
|
||||
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
io.JSONP = [];
|
||||
|
||||
JSONPPolling = io.Transport['jsonp-polling'] = function(){
|
||||
io.Transport.XHR.apply(this, arguments);
|
||||
this._insertAt = document.getElementsByTagName('script')[0];
|
||||
this._index = io.JSONP.length;
|
||||
io.JSONP.push(this);
|
||||
};
|
||||
|
||||
io.util.inherit(JSONPPolling, io.Transport['xhr-polling']);
|
||||
|
||||
JSONPPolling.prototype.type = 'jsonp-polling';
|
||||
|
||||
JSONPPolling.prototype._send = function(data){
|
||||
var self = this;
|
||||
if (!('_form' in this)){
|
||||
var form = document.createElement('FORM'),
|
||||
area = document.createElement('TEXTAREA'),
|
||||
id = this._iframeId = 'socket_io_iframe_' + this._index,
|
||||
iframe;
|
||||
|
||||
form.style.position = 'absolute';
|
||||
form.style.top = '-1000px';
|
||||
form.style.left = '-1000px';
|
||||
form.target = id;
|
||||
form.method = 'POST';
|
||||
form.action = this._prepareUrl() + '/' + (+new Date) + '/' + this._index;
|
||||
area.name = 'data';
|
||||
form.appendChild(area);
|
||||
this._insertAt.parentNode.insertBefore(form, this._insertAt);
|
||||
document.body.appendChild(form);
|
||||
|
||||
this._form = form;
|
||||
this._area = area;
|
||||
}
|
||||
|
||||
function complete(){
|
||||
initIframe();
|
||||
self._posting = false;
|
||||
self._checkSend();
|
||||
};
|
||||
|
||||
function initIframe(){
|
||||
if (self._iframe){
|
||||
self._form.removeChild(self._iframe);
|
||||
}
|
||||
|
||||
try {
|
||||
// ie6 dynamic iframes with target="" support (thanks Chris Lambacher)
|
||||
iframe = document.createElement('<iframe name="'+ self._iframeId +'">');
|
||||
} catch(e){
|
||||
iframe = document.createElement('iframe');
|
||||
iframe.name = self._iframeId;
|
||||
}
|
||||
|
||||
iframe.id = self._iframeId;
|
||||
|
||||
self._form.appendChild(iframe);
|
||||
self._iframe = iframe;
|
||||
};
|
||||
|
||||
initIframe();
|
||||
|
||||
this._posting = true;
|
||||
this._area.value = data;
|
||||
|
||||
try {
|
||||
this._form.submit();
|
||||
} catch(e){}
|
||||
|
||||
if (this._iframe.attachEvent){
|
||||
iframe.onreadystatechange = function(){
|
||||
if (self._iframe.readyState == 'complete') complete();
|
||||
};
|
||||
} else {
|
||||
this._iframe.onload = complete;
|
||||
}
|
||||
};
|
||||
|
||||
JSONPPolling.prototype._get = function(){
|
||||
var self = this,
|
||||
script = document.createElement('SCRIPT');
|
||||
if (this._script){
|
||||
this._script.parentNode.removeChild(this._script);
|
||||
this._script = null;
|
||||
}
|
||||
script.async = true;
|
||||
script.src = this._prepareUrl() + '/' + (+new Date) + '/' + this._index;
|
||||
script.onerror = function(){
|
||||
self._onDisconnect();
|
||||
};
|
||||
this._insertAt.parentNode.insertBefore(script, this._insertAt);
|
||||
this._script = script;
|
||||
};
|
||||
|
||||
JSONPPolling.prototype._ = function(){
|
||||
this._onData.apply(this, arguments);
|
||||
this._get();
|
||||
return this;
|
||||
};
|
||||
|
||||
JSONPPolling.check = function(){
|
||||
return true;
|
||||
};
|
||||
|
||||
JSONPPolling.xdomainCheck = function(){
|
||||
return true;
|
||||
};
|
||||
(function(){
|
||||
var io = this.io,
|
||||
|
||||
/**
|
||||
* The JSONP transport creates an persistent connection by dynamically
|
||||
* inserting a script tag in the page. This script tag will receive the
|
||||
* information of the Socket.IO server. When new information is received
|
||||
* it creates a new script tag for the new data stream.
|
||||
*
|
||||
* @constructor
|
||||
* @extends {io.Transport.xhr-polling}
|
||||
* @api public
|
||||
*/
|
||||
JSONPPolling = io.Transport['jsonp-polling'] = function(){
|
||||
io.Transport.XHR.apply(this, arguments);
|
||||
this.insertAt = document.getElementsByTagName('script')[0];
|
||||
this.index = io.JSONP.length;
|
||||
io.JSONP.push(this);
|
||||
};
|
||||
|
||||
io.util.inherit(JSONPPolling, io.Transport['xhr-polling']);
|
||||
|
||||
/**
|
||||
* A list of all JSONPolling transports, this is used for by
|
||||
* the Socket.IO server to distribute the callbacks.
|
||||
*
|
||||
* @type {Array}
|
||||
* @api private
|
||||
*/
|
||||
io.JSONP = [];
|
||||
|
||||
/**
|
||||
* The transport type, you use this to identify which transport was chosen.
|
||||
*
|
||||
* @type {String}
|
||||
* @api public
|
||||
*/
|
||||
JSONPPolling.prototype.type = 'jsonp-polling';
|
||||
|
||||
/**
|
||||
* Posts a encoded message to the Socket.IO server using an iframe.
|
||||
* The iframe is used because script tags can create POST based requests.
|
||||
* The iframe is positioned outside of the view so the user does not
|
||||
* notice it's existence.
|
||||
*
|
||||
* @param {String} data A encoded message.
|
||||
* @api private
|
||||
*/
|
||||
JSONPPolling.prototype.sendIORequest = function(data){
|
||||
var self = this;
|
||||
if (!('form' in this)){
|
||||
var form = document.createElement('FORM'),
|
||||
area = document.createElement('TEXTAREA'),
|
||||
id = this.iframeId = 'socket_io_iframe_' + this.index,
|
||||
iframe;
|
||||
|
||||
form.style.position = 'absolute';
|
||||
form.style.top = '-1000px';
|
||||
form.style.left = '-1000px';
|
||||
form.target = id;
|
||||
form.method = 'POST';
|
||||
form.action = this.prepareUrl() + '/' + (+new Date) + '/' + this.index;
|
||||
area.name = 'data';
|
||||
form.appendChild(area);
|
||||
this.insertAt.parentNode.insertBefore(form, this.insertAt);
|
||||
document.body.appendChild(form);
|
||||
|
||||
this.form = form;
|
||||
this.area = area;
|
||||
}
|
||||
|
||||
function complete(){
|
||||
initIframe();
|
||||
self.posting = false;
|
||||
self.checkSend();
|
||||
};
|
||||
|
||||
function initIframe(){
|
||||
if (self.iframe){
|
||||
self.form.removeChild(self.iframe);
|
||||
}
|
||||
|
||||
try {
|
||||
// ie6 dynamic iframes with target="" support (thanks Chris Lambacher)
|
||||
iframe = document.createElement('<iframe name="'+ self.iframeId +'">');
|
||||
} catch(e){
|
||||
iframe = document.createElement('iframe');
|
||||
iframe.name = self.iframeId;
|
||||
}
|
||||
|
||||
iframe.id = self.iframeId;
|
||||
|
||||
self.form.appendChild(iframe);
|
||||
self.iframe = iframe;
|
||||
};
|
||||
|
||||
initIframe();
|
||||
|
||||
this.posting = true;
|
||||
this.area.value = data;
|
||||
|
||||
try {
|
||||
this.form.submit();
|
||||
} catch(e){}
|
||||
|
||||
if (this.iframe.attachEvent){
|
||||
iframe.onreadystatechange = function(){
|
||||
if (self.iframe.readyState == 'complete') complete();
|
||||
};
|
||||
} else {
|
||||
this.iframe.onload = complete;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new JSONP poll that can be used to listen
|
||||
* for messages from the Socket.IO server.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
JSONPPolling.prototype.get = function(){
|
||||
var self = this,
|
||||
script = document.createElement('SCRIPT');
|
||||
if (this.script){
|
||||
this.script.parentNode.removeChild(this.script);
|
||||
this.script = null;
|
||||
}
|
||||
script.async = true;
|
||||
script.src = this.prepareUrl() + '/' + (+new Date) + '/' + this.index;
|
||||
script.onerror = function(){
|
||||
self.onDisconnect();
|
||||
};
|
||||
this.insertAt.parentNode.insertBefore(script, this.insertAt);
|
||||
this.script = script;
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback function for the incoming message stream from the Socket.IO server.
|
||||
*
|
||||
* @param {String} data The message
|
||||
* @param {document} doc Reference to the context
|
||||
* @api private
|
||||
*/
|
||||
JSONPPolling.prototype._ = function(){
|
||||
this.onData.apply(this, arguments);
|
||||
this.get();
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if browser supports this transport.
|
||||
*
|
||||
* @return {Boolean}
|
||||
* @api public
|
||||
*/
|
||||
JSONPPolling.check = function(){
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if cross domain requests are supported
|
||||
*
|
||||
* @returns {Boolean}
|
||||
* @api public
|
||||
*/
|
||||
JSONPPolling.xdomainCheck = function(){
|
||||
return true;
|
||||
};
|
||||
})();
|
||||
@@ -1,60 +1,121 @@
|
||||
/**
|
||||
* Socket.IO client
|
||||
*
|
||||
* @author Guillermo Rauch <guillermo@learnboost.com>
|
||||
* @license The MIT license.
|
||||
* @copyright Copyright (c) 2010 LearnBoost <dev@learnboost.com>
|
||||
* socket.io-node-client
|
||||
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
(function(){
|
||||
|
||||
var WS = io.Transport.websocket = function(){
|
||||
io.Transport.apply(this, arguments);
|
||||
};
|
||||
|
||||
io.util.inherit(WS, io.Transport);
|
||||
|
||||
WS.prototype.type = 'websocket';
|
||||
|
||||
WS.prototype.connect = function(){
|
||||
var self = this;
|
||||
this.socket = new WebSocket(this._prepareUrl());
|
||||
this.socket.onmessage = function(ev){ self._onData(ev.data); };
|
||||
this.socket.onclose = function(ev){ self._onClose(); };
|
||||
return this;
|
||||
};
|
||||
|
||||
WS.prototype.send = function(data){
|
||||
if (this.socket) this.socket.send(this._encode(data));
|
||||
return this;
|
||||
};
|
||||
|
||||
WS.prototype.disconnect = function(){
|
||||
if (this.socket) this.socket.close();
|
||||
return this;
|
||||
};
|
||||
|
||||
WS.prototype._onClose = function(){
|
||||
this._onDisconnect();
|
||||
return this;
|
||||
};
|
||||
|
||||
WS.prototype._prepareUrl = function(){
|
||||
return (this.base.options.secure ? 'wss' : 'ws')
|
||||
+ '://' + this.base.host
|
||||
+ ':' + this.base.options.port
|
||||
+ '/' + this.base.options.resource
|
||||
+ '/' + this.type
|
||||
+ (this.sessionid ? ('/' + this.sessionid) : '');
|
||||
};
|
||||
|
||||
WS.check = function(){
|
||||
// we make sure WebSocket is not confounded with a previously loaded flash WebSocket
|
||||
return 'WebSocket' in window && WebSocket.prototype && ( WebSocket.prototype.send && !!WebSocket.prototype.send.toString().match(/native/i)) && typeof WebSocket !== "undefined";
|
||||
};
|
||||
|
||||
WS.xdomainCheck = function(){
|
||||
return true;
|
||||
};
|
||||
|
||||
})();
|
||||
var io = this.io,
|
||||
|
||||
/**
|
||||
* The WebSocket transport uses the HTML5 WebSocket API to establish an persistent
|
||||
* connection with the Socket.IO server. This transport will also be inherited by the
|
||||
* FlashSocket fallback as it provides a API compatible polyfill for the WebSockets.
|
||||
*
|
||||
* @constructor
|
||||
* @extends {io.Transport}
|
||||
* @api public
|
||||
*/
|
||||
WS = io.Transport.websocket = function(){
|
||||
io.Transport.apply(this, arguments);
|
||||
};
|
||||
|
||||
io.util.inherit(WS, io.Transport);
|
||||
|
||||
/**
|
||||
* The transport type, you use this to identify which transport was chosen.
|
||||
*
|
||||
* @type {String}
|
||||
* @api public
|
||||
*/
|
||||
WS.prototype.type = 'websocket';
|
||||
|
||||
/**
|
||||
* Initializes a new `WebSocket` connection with the Socket.IO server. We attach
|
||||
* all the appropriate listeners to handle the responses from the server.
|
||||
*
|
||||
* @returns {Transport}
|
||||
* @api public
|
||||
*/
|
||||
WS.prototype.connect = function(){
|
||||
var self = this;
|
||||
this.socket = new WebSocket(this.prepareUrl());
|
||||
this.socket.onmessage = function(ev){ self.onData(ev.data); };
|
||||
this.socket.onclose = function(ev){ self.onDisconnect(); };
|
||||
this.socket.onerror = function(e){ self.onError(e); };
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Send a message to the Socket.IO server. The message will automatically be encoded
|
||||
* in the correct message format.
|
||||
*
|
||||
* @returns {Transport}
|
||||
* @api public
|
||||
*/
|
||||
WS.prototype.send = function(data){
|
||||
if (this.socket) this.socket.send(this.encode(data));
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Disconnect the established `WebSocket` connection.
|
||||
*
|
||||
* @returns {Transport}
|
||||
* @api public
|
||||
*/
|
||||
WS.prototype.disconnect = function(){
|
||||
if (this.socket) this.socket.close();
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle the errors that `WebSocket` might be giving when we
|
||||
* are attempting to connect or send messages.
|
||||
*
|
||||
* @param {Error} e The error.
|
||||
* @api private
|
||||
*/
|
||||
WS.prototype.onError = function(e){
|
||||
this.base.emit('error', [e]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate a `WebSocket` compatible URL based on the options
|
||||
* the user supplied in our Socket.IO base.
|
||||
*
|
||||
* @returns {String} Connection url
|
||||
* @api private
|
||||
*/
|
||||
WS.prototype.prepareUrl = function(){
|
||||
return (this.base.options.secure ? 'wss' : 'ws')
|
||||
+ '://' + this.base.host
|
||||
+ ':' + this.base.options.port
|
||||
+ '/' + this.base.options.resource
|
||||
+ '/' + this.type
|
||||
+ (this.sessionid ? ('/' + this.sessionid) : '');
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the browser has support for native `WebSockets` and that
|
||||
* it's not the polyfill created for the FlashSocket transport.
|
||||
*
|
||||
* @return {Boolean}
|
||||
* @api public
|
||||
*/
|
||||
WS.check = function(){
|
||||
// we make sure WebSocket is not confounded with a previously loaded flash WebSocket
|
||||
return 'WebSocket' in window && WebSocket.prototype && ( WebSocket.prototype.send && !!WebSocket.prototype.send.toString().match(/native/i)) && typeof WebSocket !== "undefined";
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the `WebSocket` transport support cross domain communications.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
* @api public
|
||||
*/
|
||||
WS.xdomainCheck = function(){
|
||||
return true;
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
@@ -1,36 +1,66 @@
|
||||
/**
|
||||
* Socket.IO client
|
||||
*
|
||||
* @author Guillermo Rauch <guillermo@learnboost.com>
|
||||
* @license The MIT license.
|
||||
* @copyright Copyright (c) 2010 LearnBoost <dev@learnboost.com>
|
||||
* socket.io-node-client
|
||||
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
(function(){
|
||||
|
||||
var XHRMultipart = io.Transport['xhr-multipart'] = function(){
|
||||
io.Transport.XHR.apply(this, arguments);
|
||||
};
|
||||
|
||||
io.util.inherit(XHRMultipart, io.Transport.XHR);
|
||||
|
||||
XHRMultipart.prototype.type = 'xhr-multipart';
|
||||
|
||||
XHRMultipart.prototype._get = function(){
|
||||
var self = this;
|
||||
this._xhr = this._request('', 'GET', true);
|
||||
this._xhr.onreadystatechange = function(){
|
||||
if (self._xhr.readyState == 3) self._onData(self._xhr.responseText);
|
||||
};
|
||||
this._xhr.send();
|
||||
};
|
||||
|
||||
XHRMultipart.check = function(){
|
||||
return 'XMLHttpRequest' in window && 'prototype' in XMLHttpRequest && 'multipart' in XMLHttpRequest.prototype;
|
||||
};
|
||||
|
||||
XHRMultipart.xdomainCheck = function(){
|
||||
return true;
|
||||
};
|
||||
|
||||
var io = this.io,
|
||||
|
||||
/**
|
||||
* The XHR-Multipart transport uses the a multipart XHR connection to
|
||||
* stream in the data from the Socket.IO server
|
||||
*
|
||||
* @constructor
|
||||
* @extends {io.Transport.XHR}
|
||||
* @api public
|
||||
*/
|
||||
XHRMultipart = io.Transport['xhr-multipart'] = function(){
|
||||
io.Transport.XHR.apply(this, arguments);
|
||||
};
|
||||
|
||||
io.util.inherit(XHRMultipart, io.Transport.XHR);
|
||||
|
||||
/**
|
||||
* The transport type, you use this to identify which transport was chosen.
|
||||
*
|
||||
* @type {String}
|
||||
* @api public
|
||||
*/
|
||||
XHRMultipart.prototype.type = 'xhr-multipart';
|
||||
|
||||
/**
|
||||
* Starts the multipart stream for incomming messages.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
XHRMultipart.prototype.get = function(){
|
||||
var self = this;
|
||||
this.xhr = this.request('', 'GET', true);
|
||||
this.xhr.onreadystatechange = function(){
|
||||
if (self.xhr.readyState == 4) self.onData(self.xhr.responseText);
|
||||
};
|
||||
this.xhr.send(null);
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if browser supports this transport.
|
||||
*
|
||||
* @return {Boolean}
|
||||
* @api public
|
||||
*/
|
||||
XHRMultipart.check = function(){
|
||||
return 'XMLHttpRequest' in window && 'prototype' in XMLHttpRequest && 'multipart' in XMLHttpRequest.prototype;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if cross domain requests are supported.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
* @api public
|
||||
*/
|
||||
XHRMultipart.xdomainCheck = function(){
|
||||
return true;
|
||||
};
|
||||
|
||||
})();
|
||||
@@ -1,68 +1,97 @@
|
||||
/**
|
||||
* Socket.IO client
|
||||
*
|
||||
* @author Guillermo Rauch <guillermo@learnboost.com>
|
||||
* @license The MIT license.
|
||||
* @copyright Copyright (c) 2010 LearnBoost <dev@learnboost.com>
|
||||
* socket.io-node-client
|
||||
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
(function(){
|
||||
var io = this.io,
|
||||
|
||||
/**
|
||||
* A small stub function that will be used to reduce memory leaks.
|
||||
*
|
||||
* @type {Function}
|
||||
* @api private
|
||||
*/
|
||||
empty = new Function(),
|
||||
|
||||
/**
|
||||
* The XHR-polling transport uses long polling XHR requests to create a
|
||||
* "persistent" connection with the server.
|
||||
*
|
||||
* @constructor
|
||||
* @extends {io.Transport.XHR}
|
||||
* @api public
|
||||
*/
|
||||
XHRPolling = io.Transport['xhr-polling'] = function(){
|
||||
io.Transport.XHR.apply(this, arguments);
|
||||
};
|
||||
|
||||
io.util.inherit(XHRPolling, io.Transport.XHR);
|
||||
|
||||
/**
|
||||
* The transport type, you use this to identify which transport was chosen.
|
||||
*
|
||||
* @type {string}
|
||||
* @api public
|
||||
*/
|
||||
XHRPolling.prototype.type = 'xhr-polling';
|
||||
|
||||
/**
|
||||
* Establish a connection, for iPhone and Android this will be done once the page
|
||||
* is loaded.
|
||||
*
|
||||
* @returns {Transport} Chaining.
|
||||
* @api public
|
||||
*/
|
||||
XHRPolling.prototype.connect = function(){
|
||||
var self = this;
|
||||
io.util.defer(function(){ io.Transport.XHR.prototype.connect.call(self) });
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Starts a XHR request to wait for incoming messages.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
XHRPolling.prototype.get = function(){
|
||||
var self = this;
|
||||
this.xhr = this.request(+ new Date, 'GET');
|
||||
this.xhr.onreadystatechange = function(){
|
||||
var status;
|
||||
if (self.xhr.readyState == 4){
|
||||
self.xhr.onreadystatechange = empty;
|
||||
try { status = self.xhr.status; } catch(e){}
|
||||
if (status == 200){
|
||||
self.onData(self.xhr.responseText);
|
||||
self.get();
|
||||
} else {
|
||||
self.onDisconnect();
|
||||
}
|
||||
}
|
||||
};
|
||||
this.xhr.send(null);
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if browser supports this transport.
|
||||
*
|
||||
* @return {Boolean}
|
||||
* @api public
|
||||
*/
|
||||
XHRPolling.check = function(){
|
||||
return io.Transport.XHR.check();
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if cross domain requests are supported
|
||||
*
|
||||
* @returns {Boolean}
|
||||
* @api public
|
||||
*/
|
||||
XHRPolling.xdomainCheck = function(){
|
||||
return io.Transport.XHR.xdomainCheck();
|
||||
};
|
||||
|
||||
var empty = new Function(),
|
||||
|
||||
XHRPolling = io.Transport['xhr-polling'] = function(){
|
||||
io.Transport.XHR.apply(this, arguments);
|
||||
};
|
||||
|
||||
io.util.inherit(XHRPolling, io.Transport.XHR);
|
||||
|
||||
XHRPolling.prototype.type = 'xhr-polling';
|
||||
|
||||
XHRPolling.prototype.connect = function(){
|
||||
if (io.util.ios || io.util.android){
|
||||
var self = this;
|
||||
io.util.load(function(){
|
||||
setTimeout(function(){
|
||||
io.Transport.XHR.prototype.connect.call(self);
|
||||
}, 10);
|
||||
});
|
||||
} else {
|
||||
io.Transport.XHR.prototype.connect.call(this);
|
||||
}
|
||||
};
|
||||
|
||||
XHRPolling.prototype._get = function(){
|
||||
var self = this;
|
||||
this._xhr = this._request(+ new Date, 'GET');
|
||||
if ('onload' in this._xhr){
|
||||
this._xhr.onload = function(){
|
||||
self._onData(this.responseText);
|
||||
self._get();
|
||||
};
|
||||
} else {
|
||||
this._xhr.onreadystatechange = function(){
|
||||
var status;
|
||||
if (self._xhr.readyState == 4){
|
||||
self._xhr.onreadystatechange = empty;
|
||||
try { status = self._xhr.status; } catch(e){}
|
||||
if (status == 200){
|
||||
self._onData(self._xhr.responseText);
|
||||
self._get();
|
||||
} else {
|
||||
self._onDisconnect();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
this._xhr.send();
|
||||
};
|
||||
|
||||
XHRPolling.check = function(){
|
||||
return io.Transport.XHR.check();
|
||||
};
|
||||
|
||||
XHRPolling.xdomainCheck = function(){
|
||||
return io.Transport.XHR.xdomainCheck();
|
||||
};
|
||||
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -1,131 +1,221 @@
|
||||
/**
|
||||
* Socket.IO client
|
||||
*
|
||||
* @author Guillermo Rauch <guillermo@learnboost.com>
|
||||
* @license The MIT license.
|
||||
* @copyright Copyright (c) 2010 LearnBoost <dev@learnboost.com>
|
||||
* socket.io-node-client
|
||||
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
(function(){
|
||||
|
||||
var empty = new Function,
|
||||
|
||||
XMLHttpRequestCORS = (function(){
|
||||
if (!('XMLHttpRequest' in window)) return false;
|
||||
// CORS feature detection
|
||||
var a = new XMLHttpRequest();
|
||||
return a.withCredentials != undefined;
|
||||
})(),
|
||||
|
||||
request = function(xdomain){
|
||||
if ('XDomainRequest' in window && xdomain) return new XDomainRequest();
|
||||
if ('XMLHttpRequest' in window && (!xdomain || XMLHttpRequestCORS)) return new XMLHttpRequest();
|
||||
if (!xdomain){
|
||||
try {
|
||||
var a = new ActiveXObject('MSXML2.XMLHTTP');
|
||||
return a;
|
||||
} catch(e){}
|
||||
|
||||
try {
|
||||
var b = new ActiveXObject('Microsoft.XMLHTTP');
|
||||
return b;
|
||||
} catch(e){}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
XHR = io.Transport.XHR = function(){
|
||||
io.Transport.apply(this, arguments);
|
||||
this._sendBuffer = [];
|
||||
};
|
||||
|
||||
io.util.inherit(XHR, io.Transport);
|
||||
|
||||
XHR.prototype.connect = function(){
|
||||
this._get();
|
||||
return this;
|
||||
};
|
||||
|
||||
XHR.prototype._checkSend = function(){
|
||||
if (!this._posting && this._sendBuffer.length){
|
||||
var encoded = this._encode(this._sendBuffer);
|
||||
this._sendBuffer = [];
|
||||
this._send(encoded);
|
||||
}
|
||||
};
|
||||
|
||||
XHR.prototype.send = function(data){
|
||||
if (io.util.isArray(data)){
|
||||
this._sendBuffer.push.apply(this._sendBuffer, data);
|
||||
} else {
|
||||
this._sendBuffer.push(data);
|
||||
}
|
||||
this._checkSend();
|
||||
return this;
|
||||
};
|
||||
|
||||
XHR.prototype._send = function(data){
|
||||
var self = this;
|
||||
this._posting = true;
|
||||
this._sendXhr = this._request('send', 'POST');
|
||||
this._sendXhr.onreadystatechange = function(){
|
||||
var status;
|
||||
if (self._sendXhr.readyState == 4){
|
||||
self._sendXhr.onreadystatechange = empty;
|
||||
try { status = self._sendXhr.status; } catch(e){}
|
||||
self._posting = false;
|
||||
if (status == 200){
|
||||
self._checkSend();
|
||||
} else {
|
||||
self._onDisconnect();
|
||||
}
|
||||
}
|
||||
};
|
||||
this._sendXhr.send('data=' + encodeURIComponent(data));
|
||||
};
|
||||
|
||||
XHR.prototype.disconnect = function(){
|
||||
// send disconnection signal
|
||||
this._onDisconnect();
|
||||
return this;
|
||||
};
|
||||
|
||||
XHR.prototype._onDisconnect = function(){
|
||||
if (this._xhr){
|
||||
this._xhr.onreadystatechange = this._xhr.onload = empty;
|
||||
this._xhr.abort();
|
||||
this._xhr = null;
|
||||
}
|
||||
if (this._sendXhr){
|
||||
this._sendXhr.onreadystatechange = this._sendXhr.onload = empty;
|
||||
this._sendXhr.abort();
|
||||
this._sendXhr = null;
|
||||
}
|
||||
this._sendBuffer = [];
|
||||
io.Transport.prototype._onDisconnect.call(this);
|
||||
};
|
||||
|
||||
XHR.prototype._request = function(url, method, multipart){
|
||||
var req = request(this.base._isXDomain());
|
||||
if (multipart) req.multipart = true;
|
||||
req.open(method || 'GET', this._prepareUrl() + (url ? '/' + url : ''));
|
||||
if (method == 'POST' && 'setRequestHeader' in req){
|
||||
req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded; charset=utf-8');
|
||||
}
|
||||
return req;
|
||||
};
|
||||
|
||||
XHR.check = function(xdomain){
|
||||
try {
|
||||
if (request(xdomain)) return true;
|
||||
} catch(e){}
|
||||
return false;
|
||||
};
|
||||
|
||||
XHR.xdomainCheck = function(){
|
||||
return XHR.check(true);
|
||||
};
|
||||
|
||||
XHR.request = request;
|
||||
|
||||
})();
|
||||
var io = this.io,
|
||||
|
||||
/**
|
||||
* A small stub function that will be used to reduce memory leaks.
|
||||
*
|
||||
* @type {Function}
|
||||
* @api private
|
||||
*/
|
||||
empty = new Function,
|
||||
|
||||
/**
|
||||
* We preform a small feature detection to see if `Cross Origin Resource Sharing`
|
||||
* is supported in the `XMLHttpRequest` object, so we can use it for cross domain requests.
|
||||
*
|
||||
* @type {Boolean}
|
||||
* @api private
|
||||
*/
|
||||
XMLHttpRequestCORS = (function(){
|
||||
if (!('XMLHttpRequest' in window)) return false;
|
||||
// CORS feature detection
|
||||
var a = new XMLHttpRequest();
|
||||
return a.withCredentials != undefined;
|
||||
})(),
|
||||
|
||||
/**
|
||||
* Generates the correct `XMLHttpRequest` for regular and cross domain requests.
|
||||
*
|
||||
* @param {Boolean} [xdomain] Create a request that can be used cross domain.
|
||||
* @returns {XMLHttpRequest|false} If we can create a XMLHttpRequest we will return that.
|
||||
* @api private
|
||||
*/
|
||||
request = function(xdomain){
|
||||
if ('XDomainRequest' in window && xdomain) return new XDomainRequest();
|
||||
if ('XMLHttpRequest' in window && (!xdomain || XMLHttpRequestCORS)) return new XMLHttpRequest();
|
||||
if (!xdomain){
|
||||
try {
|
||||
var a = new ActiveXObject('MSXML2.XMLHTTP');
|
||||
return a;
|
||||
} catch(e){}
|
||||
|
||||
try {
|
||||
var b = new ActiveXObject('Microsoft.XMLHTTP');
|
||||
return b;
|
||||
} catch(e){}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* This is the base for XHR based transports, the `XHR-Polling` and the `XHR-multipart`
|
||||
* transports will extend this class.
|
||||
*
|
||||
* @constructor
|
||||
* @extends {io.Transport}
|
||||
* @property {Array} sendBuffer Used to queue up messages so they can be send as one request.
|
||||
* @api public
|
||||
*/
|
||||
XHR = io.Transport.XHR = function(){
|
||||
io.Transport.apply(this, arguments);
|
||||
this.sendBuffer = [];
|
||||
};
|
||||
|
||||
io.util.inherit(XHR, io.Transport);
|
||||
|
||||
/**
|
||||
* Establish a connection
|
||||
*
|
||||
* @returns {Transport}
|
||||
* @api public
|
||||
*/
|
||||
XHR.prototype.connect = function(){
|
||||
this.get();
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if we need to send data to the Socket.IO server, if we have data in our buffer
|
||||
* we encode it and forward it to the sendIORequest method.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
XHR.prototype.checkSend = function(){
|
||||
if (!this.posting && this.sendBuffer.length){
|
||||
var encoded = this.encode(this.sendBuffer);
|
||||
this.sendBuffer = [];
|
||||
this.sendIORequest(encoded);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Send data to the Socket.IO server.
|
||||
*
|
||||
* @param data The message
|
||||
* @returns {Transport}
|
||||
* @api public
|
||||
*/
|
||||
XHR.prototype.send = function(data){
|
||||
if (io.util.isArray(data)){
|
||||
this.sendBuffer.push.apply(this.sendBuffer, data);
|
||||
} else {
|
||||
this.sendBuffer.push(data);
|
||||
}
|
||||
this.checkSend();
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Posts a encoded message to the Socket.IO server.
|
||||
*
|
||||
* @param {String} data A encoded message.
|
||||
* @api private
|
||||
*/
|
||||
XHR.prototype.sendIORequest = function(data){
|
||||
var self = this;
|
||||
this.posting = true;
|
||||
this.sendXHR = this.request('send', 'POST');
|
||||
this.sendXHR.onreadystatechange = function(){
|
||||
var status;
|
||||
if (self.sendXHR.readyState == 4){
|
||||
self.sendXHR.onreadystatechange = empty;
|
||||
try { status = self.sendXHR.status; } catch(e){}
|
||||
self.posting = false;
|
||||
if (status == 200){
|
||||
self.checkSend();
|
||||
} else {
|
||||
self.onDisconnect();
|
||||
}
|
||||
}
|
||||
};
|
||||
this.sendXHR.send('data=' + encodeURIComponent(data));
|
||||
};
|
||||
|
||||
/**
|
||||
* Disconnect the established connection.
|
||||
*
|
||||
* @returns {Transport}.
|
||||
* @api public
|
||||
*/
|
||||
XHR.prototype.disconnect = function(){
|
||||
// send disconnection signal
|
||||
this.onDisconnect();
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle the disconnect request.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
XHR.prototype.onDisconnect = function(){
|
||||
if (this.xhr){
|
||||
this.xhr.onreadystatechange = empty;
|
||||
try {
|
||||
this.xhr.abort();
|
||||
} catch(e){}
|
||||
this.xhr = null;
|
||||
}
|
||||
if (this.sendXHR){
|
||||
this.sendXHR.onreadystatechange = empty;
|
||||
try {
|
||||
this.sendXHR.abort();
|
||||
} catch(e){}
|
||||
this.sendXHR = null;
|
||||
}
|
||||
this.sendBuffer = [];
|
||||
io.Transport.prototype.onDisconnect.call(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates a configured XHR request
|
||||
*
|
||||
* @param {String} url The url that needs to be requested.
|
||||
* @param {String} method The method the request should use.
|
||||
* @param {Boolean} multipart Do a multipart XHR request
|
||||
* @returns {XMLHttpRequest}
|
||||
* @api private
|
||||
*/
|
||||
XHR.prototype.request = function(url, method, multipart){
|
||||
var req = request(this.base.isXDomain());
|
||||
if (multipart) req.multipart = true;
|
||||
req.open(method || 'GET', this.prepareUrl() + (url ? '/' + url : ''));
|
||||
if (method == 'POST' && 'setRequestHeader' in req){
|
||||
req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded; charset=utf-8');
|
||||
}
|
||||
return req;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the XHR transports are supported
|
||||
*
|
||||
* @param {Boolean} xdomain Check if we support cross domain requests.
|
||||
* @returns {Boolean}
|
||||
* @api public
|
||||
*/
|
||||
XHR.check = function(xdomain){
|
||||
try {
|
||||
if (request(xdomain)) return true;
|
||||
} catch(e){}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the XHR transport supports corss domain requests.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
* @api public
|
||||
*/
|
||||
XHR.xdomainCheck = function(){
|
||||
return XHR.check(true);
|
||||
};
|
||||
|
||||
XHR.request = request;
|
||||
|
||||
})();
|
||||
|
||||
@@ -1,60 +1,161 @@
|
||||
/**
|
||||
* Socket.IO client
|
||||
*
|
||||
* @author Guillermo Rauch <guillermo@learnboost.com>
|
||||
* @license The MIT license.
|
||||
* @copyright Copyright (c) 2010 LearnBoost <dev@learnboost.com>
|
||||
* socket.io-node-client
|
||||
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
(function(){
|
||||
|
||||
var _pageLoaded = false;
|
||||
|
||||
io.util = {
|
||||
|
||||
ios: false,
|
||||
|
||||
load: function(fn){
|
||||
if (document.readyState == 'complete' || _pageLoaded) return fn();
|
||||
if ('attachEvent' in window){
|
||||
window.attachEvent('onload', fn);
|
||||
} else {
|
||||
window.addEventListener('load', fn, false);
|
||||
}
|
||||
},
|
||||
|
||||
inherit: function(ctor, superCtor){
|
||||
// no support for `instanceof` for now
|
||||
for (var i in superCtor.prototype){
|
||||
ctor.prototype[i] = superCtor.prototype[i];
|
||||
}
|
||||
},
|
||||
|
||||
indexOf: function(arr, item, from){
|
||||
for (var l = arr.length, i = (from < 0) ? Math.max(0, l + from) : from || 0; i < l; i++){
|
||||
if (arr[i] === item) return i;
|
||||
}
|
||||
return -1;
|
||||
},
|
||||
|
||||
isArray: function(obj){
|
||||
return Object.prototype.toString.call(obj) === '[object Array]';
|
||||
},
|
||||
|
||||
var io = this.io,
|
||||
|
||||
/**
|
||||
* Set when the `onload` event is executed on the page. This variable is used by
|
||||
* `io.util.load` to detect if we need to execute the function immediately or add
|
||||
* it to a onload listener.
|
||||
*
|
||||
* @type {Boolean}
|
||||
* @api private
|
||||
*/
|
||||
pageLoaded = false;
|
||||
|
||||
/**
|
||||
* @namespace
|
||||
*/
|
||||
io.util = {
|
||||
/**
|
||||
* Executes the given function when the page is loaded.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* io.util.load(function(){ console.log('page loaded') });
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @api public
|
||||
*/
|
||||
load: function(fn){
|
||||
if (/loaded|complete/.test(document.readyState) || pageLoaded) return fn();
|
||||
if ('attachEvent' in window){
|
||||
window.attachEvent('onload', fn);
|
||||
} else {
|
||||
window.addEventListener('load', fn, false);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Defers the function untill it's the function can be executed without
|
||||
* blocking the load process. This is especially needed for WebKit based
|
||||
* browsers. If a long running connection is made before the onload event
|
||||
* a loading indicator spinner will be present at all times untill a
|
||||
* reconnect has been made.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @api public
|
||||
*/
|
||||
defer: function(fn){
|
||||
if (!io.util.webkit) return fn();
|
||||
io.util.load(function(){
|
||||
setTimeout(fn,100);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Inherit the prototype methods from one constructor into another.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* function foo(){};
|
||||
* foo.prototype.hello = function(){ console.log( this.words )};
|
||||
*
|
||||
* function bar(){
|
||||
* this.words = "Hello world";
|
||||
* };
|
||||
*
|
||||
* io.util.inherit(bar,foo);
|
||||
* var person = new bar();
|
||||
* person.hello();
|
||||
* // => "Hello World"
|
||||
*
|
||||
* @param {Constructor} ctor The constructor that needs to inherit the methods.
|
||||
* @param {Constructor} superCtor The constructor to inherit from.
|
||||
* @api public
|
||||
*/
|
||||
inherit: function(ctor, superCtor){
|
||||
// no support for `instanceof` for now
|
||||
for (var i in superCtor.prototype){
|
||||
ctor.prototype[i] = superCtor.prototype[i];
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Finds the index of item in a given Array.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* var data = ['socket',2,3,4,'socket',5,6,7,'io'];
|
||||
* io.util.indexOf(data,'socket',1);
|
||||
* // => 4
|
||||
*
|
||||
* @param {Array} arr The array
|
||||
* @param item The item that we need to find
|
||||
* @param {Integer} from Starting point
|
||||
* @api public
|
||||
*/
|
||||
indexOf: function(arr, item, from){
|
||||
for (var l = arr.length, i = (from < 0) ? Math.max(0, l + from) : from || 0; i < l; i++){
|
||||
if (arr[i] === item) return i;
|
||||
}
|
||||
return -1;
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if the given object is an Array.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* io.util.isArray([]);
|
||||
* // => true
|
||||
* io.util.isArray({});
|
||||
* // => false
|
||||
*
|
||||
* @param obj
|
||||
* @api public
|
||||
*/
|
||||
isArray: function(obj){
|
||||
return Object.prototype.toString.call(obj) === '[object Array]';
|
||||
},
|
||||
|
||||
/**
|
||||
* Merges the properties of two objects.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* var a = {foo:'bar'}
|
||||
* , b = {bar:'baz'};
|
||||
*
|
||||
* io.util.merge(a,b);
|
||||
* // => {foo:'bar',bar:'baz'}
|
||||
*
|
||||
* @param {Object} target The object that receives the keys
|
||||
* @param {Object} additional The object that supplies the keys
|
||||
* @api public
|
||||
*/
|
||||
merge: function(target, additional){
|
||||
for (var i in additional)
|
||||
if (additional.hasOwnProperty(i))
|
||||
target[i] = additional[i];
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
io.util.ios = /iphone|ipad/i.test(navigator.userAgent);
|
||||
io.util.android = /android/i.test(navigator.userAgent);
|
||||
io.util.opera = /opera/i.test(navigator.userAgent);
|
||||
|
||||
io.util.load(function(){
|
||||
_pageLoaded = true;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Detect the Webkit platform based on the userAgent string.
|
||||
* This includes Mobile Webkit.
|
||||
*
|
||||
* @type {Boolean}
|
||||
* @api public
|
||||
*/
|
||||
io.util.webkit = /webkit/i.test(navigator.userAgent);
|
||||
|
||||
io.util.load(function(){
|
||||
pageLoaded = true;
|
||||
});
|
||||
|
||||
})();
|
||||
4
support/socket.io-client/lib/vendor/uglifyjs/.gitignore
vendored
Executable file
4
support/socket.io-client/lib/vendor/uglifyjs/.gitignore
vendored
Executable file
@@ -0,0 +1,4 @@
|
||||
.DS_Store
|
||||
.tmp*~
|
||||
*.local.*
|
||||
.pinf-*
|
||||
782
support/socket.io-client/lib/vendor/uglifyjs/README.html
vendored
Executable file
782
support/socket.io-client/lib/vendor/uglifyjs/README.html
vendored
Executable file
@@ -0,0 +1,782 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||
lang="en" xml:lang="en">
|
||||
<head>
|
||||
<title>UglifyJS -- a JavaScript parser/compressor/beautifier</title>
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
|
||||
<meta name="generator" content="Org-mode"/>
|
||||
<meta name="generated" content="2011-02-28 22:35:00 EET"/>
|
||||
<meta name="author" content="Mihai Bazon"/>
|
||||
<meta name="description" content="a JavaScript parser/compressor/beautifier in JavaScript"/>
|
||||
<meta name="keywords" content="javascript, js, parser, compiler, compressor, mangle, minify, minifier"/>
|
||||
<style type="text/css">
|
||||
<!--/*--><![CDATA[/*><!--*/
|
||||
html { font-family: Times, serif; font-size: 12pt; }
|
||||
.title { text-align: center; }
|
||||
.todo { color: red; }
|
||||
.done { color: green; }
|
||||
.tag { background-color: #add8e6; font-weight:normal }
|
||||
.target { }
|
||||
.timestamp { color: #bebebe; }
|
||||
.timestamp-kwd { color: #5f9ea0; }
|
||||
p.verse { margin-left: 3% }
|
||||
pre {
|
||||
border: 1pt solid #AEBDCC;
|
||||
background-color: #F3F5F7;
|
||||
padding: 5pt;
|
||||
font-family: courier, monospace;
|
||||
font-size: 90%;
|
||||
overflow:auto;
|
||||
}
|
||||
table { border-collapse: collapse; }
|
||||
td, th { vertical-align: top; }
|
||||
dt { font-weight: bold; }
|
||||
div.figure { padding: 0.5em; }
|
||||
div.figure p { text-align: center; }
|
||||
textarea { overflow-x: auto; }
|
||||
.linenr { font-size:smaller }
|
||||
.code-highlighted {background-color:#ffff00;}
|
||||
.org-info-js_info-navigation { border-style:none; }
|
||||
#org-info-js_console-label { font-size:10px; font-weight:bold;
|
||||
white-space:nowrap; }
|
||||
.org-info-js_search-highlight {background-color:#ffff00; color:#000000;
|
||||
font-weight:bold; }
|
||||
/*]]>*/-->
|
||||
</style>
|
||||
<link rel="stylesheet" type="text/css" href="docstyle.css" />
|
||||
<script type="text/javascript">
|
||||
<!--/*--><![CDATA[/*><!--*/
|
||||
function CodeHighlightOn(elem, id)
|
||||
{
|
||||
var target = document.getElementById(id);
|
||||
if(null != target) {
|
||||
elem.cacheClassElem = elem.className;
|
||||
elem.cacheClassTarget = target.className;
|
||||
target.className = "code-highlighted";
|
||||
elem.className = "code-highlighted";
|
||||
}
|
||||
}
|
||||
function CodeHighlightOff(elem, id)
|
||||
{
|
||||
var target = document.getElementById(id);
|
||||
if(elem.cacheClassElem)
|
||||
elem.className = elem.cacheClassElem;
|
||||
if(elem.cacheClassTarget)
|
||||
target.className = elem.cacheClassTarget;
|
||||
}
|
||||
/*]]>*///-->
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div id="content">
|
||||
|
||||
<h1 class="title">UglifyJS – a JavaScript parser/compressor/beautifier</h1>
|
||||
|
||||
|
||||
<div id="table-of-contents">
|
||||
<h2>Table of Contents</h2>
|
||||
<div id="text-table-of-contents">
|
||||
<ul>
|
||||
<li><a href="#sec-1">1 UglifyJS — a JavaScript parser/compressor/beautifier </a>
|
||||
<ul>
|
||||
<li><a href="#sec-1_1">1.1 Unsafe transformations </a>
|
||||
<ul>
|
||||
<li><a href="#sec-1_1_1">1.1.1 Calls involving the global Array constructor </a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#sec-1_2">1.2 Usage </a>
|
||||
<ul>
|
||||
<li><a href="#sec-1_2_1">1.2.1 API </a></li>
|
||||
<li><a href="#sec-1_2_2">1.2.2 Beautifier shortcoming – no more comments </a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#sec-1_3">1.3 Compression – how good is it? </a></li>
|
||||
<li><a href="#sec-1_4">1.4 Bugs? </a></li>
|
||||
<li><a href="#sec-1_5">1.5 Links </a></li>
|
||||
<li><a href="#sec-1_6">1.6 License </a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="outline-container-1" class="outline-2">
|
||||
<h2 id="sec-1"><span class="section-number-2">1</span> UglifyJS — a JavaScript parser/compressor/beautifier </h2>
|
||||
<div class="outline-text-2" id="text-1">
|
||||
|
||||
|
||||
<p>
|
||||
<b>Update</b>: please read the section on <a href="#sec-1_1">unsafe transformations</a>.
|
||||
</p>
|
||||
<p>
|
||||
This package implements a general-purpose JavaScript
|
||||
parser/compressor/beautifier toolkit. It is developed on <a href="http://nodejs.org/">NodeJS</a>, but it
|
||||
should work on any JavaScript platform supporting the CommonJS module system
|
||||
(and if your platform of choice doesn't support CommonJS, you can easily
|
||||
implement it, or discard the <code>exports.*</code> lines from UglifyJS sources).
|
||||
</p>
|
||||
<p>
|
||||
The tokenizer/parser generates an abstract syntax tree from JS code. You
|
||||
can then traverse the AST to learn more about the code, or do various
|
||||
manipulations on it. This part is implemented in <a href="../lib/parse-js.js">parse-js.js</a> and it's a
|
||||
port to JavaScript of the excellent <a href="http://marijn.haverbeke.nl/parse-js/">parse-js</a> Common Lisp library from <a href="http://marijn.haverbeke.nl/">Marijn Haverbeke</a>.
|
||||
</p>
|
||||
<p>
|
||||
( See <a href="http://github.com/mishoo/cl-uglify-js">cl-uglify-js</a> if you're looking for the Common Lisp version of
|
||||
UglifyJS. )
|
||||
</p>
|
||||
<p>
|
||||
The second part of this package, implemented in <a href="../lib/process.js">process.js</a>, inspects and
|
||||
manipulates the AST generated by the parser to provide the following:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
ability to re-generate JavaScript code from the AST. Optionally
|
||||
indented—you can use this if you want to “beautify” a program that has
|
||||
been compressed, so that you can inspect the source. But you can also run
|
||||
our code generator to print out an AST without any whitespace, so you
|
||||
achieve compression as well.
|
||||
|
||||
</li>
|
||||
<li>
|
||||
shorten variable names (usually to single characters). Our mangler will
|
||||
analyze the code and generate proper variable names, depending on scope
|
||||
and usage, and is smart enough to deal with globals defined elsewhere, or
|
||||
with <code>eval()</code> calls or <code>with{}</code> statements. In short, if <code>eval()</code> or
|
||||
<code>with{}</code> are used in some scope, then all variables in that scope and any
|
||||
variables in the parent scopes will remain unmangled, and any references
|
||||
to such variables remain unmangled as well.
|
||||
|
||||
</li>
|
||||
<li>
|
||||
various small optimizations that may lead to faster code but certainly
|
||||
lead to smaller code. Where possible, we do the following:
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
foo["bar"] ==> foo.bar
|
||||
|
||||
</li>
|
||||
<li>
|
||||
remove block brackets <code>{}</code>
|
||||
|
||||
</li>
|
||||
<li>
|
||||
join consecutive var declarations:
|
||||
var a = 10; var b = 20; ==> var a=10,b=20;
|
||||
|
||||
</li>
|
||||
<li>
|
||||
resolve simple constant expressions: 1 +2 * 3 ==> 7. We only do the
|
||||
replacement if the result occupies less bytes; for example 1/3 would
|
||||
translate to 0.333333333333, so in this case we don't replace it.
|
||||
|
||||
</li>
|
||||
<li>
|
||||
consecutive statements in blocks are merged into a sequence; in many
|
||||
cases, this leaves blocks with a single statement, so then we can remove
|
||||
the block brackets.
|
||||
|
||||
</li>
|
||||
<li>
|
||||
various optimizations for IF statements:
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
if (foo) bar(); else baz(); ==> foo?bar():baz();
|
||||
</li>
|
||||
<li>
|
||||
if (!foo) bar(); else baz(); ==> foo?baz():bar();
|
||||
</li>
|
||||
<li>
|
||||
if (foo) bar(); ==> foo&&bar();
|
||||
</li>
|
||||
<li>
|
||||
if (!foo) bar(); ==> foo||bar();
|
||||
</li>
|
||||
<li>
|
||||
if (foo) return bar(); else return baz(); ==> return foo?bar():baz();
|
||||
</li>
|
||||
<li>
|
||||
if (foo) return bar(); else something(); ==> {if(foo)return bar();something()}
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
remove some unreachable code and warn about it (code that follows a
|
||||
<code>return</code>, <code>throw</code>, <code>break</code> or <code>continue</code> statement, except
|
||||
function/variable declarations).
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div id="outline-container-1_1" class="outline-3">
|
||||
<h3 id="sec-1_1"><span class="section-number-3">1.1</span> <span class="target">Unsafe transformations</span> </h3>
|
||||
<div class="outline-text-3" id="text-1_1">
|
||||
|
||||
|
||||
<p>
|
||||
UglifyJS tries its best to achieve great compression while leaving the
|
||||
semantics of the code intact. In general, if your code logic is broken by
|
||||
UglifyJS then it's a bug in UglifyJS and you should report it and I should
|
||||
fix it. :-)
|
||||
</p>
|
||||
<p>
|
||||
However, I opted to include the following potentially unsafe transformations
|
||||
as default behavior. Discussion is welcome, if you have ideas of how to
|
||||
handle this better, or any objections to these optimizations, please let me
|
||||
know.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="outline-container-1_1_1" class="outline-4">
|
||||
<h4 id="sec-1_1_1"><span class="section-number-4">1.1.1</span> Calls involving the global Array constructor </h4>
|
||||
<div class="outline-text-4" id="text-1_1_1">
|
||||
|
||||
|
||||
<p>
|
||||
The following transformations occur:
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
<pre class="src src-espresso"><span style="color: #afeeee; font-weight: bold;">new</span> <span style="color: #87ceeb; font-weight: bold;">Array</span>(1, 2, 3, 4) => [1,2,3,4]
|
||||
Array(a, b, c) => [a,b,c]
|
||||
<span style="color: #afeeee; font-weight: bold;">new</span> <span style="color: #87ceeb; font-weight: bold;">Array</span>(5) => Array(5)
|
||||
<span style="color: #afeeee; font-weight: bold;">new</span> <span style="color: #87ceeb; font-weight: bold;">Array</span>(a) => Array(a)
|
||||
</pre>
|
||||
|
||||
|
||||
|
||||
<p>
|
||||
These are all safe if the Array name isn't redefined. JavaScript does allow
|
||||
one to globally redefine Array (and pretty much everything, in fact) but I
|
||||
personally don't see why would anyone do that.
|
||||
</p>
|
||||
<p>
|
||||
UglifyJS does handle the case where Array is redefined locally, or even
|
||||
globally but with a <code>function</code> or <code>var</code> declaration. Therefore, in the
|
||||
following cases UglifyJS <b>doesn't touch</b> calls or instantiations of Array:
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
<pre class="src src-espresso"><span style="color: #add8e6;">// </span><span style="color: #add8e6;">case 1. globally declared variable
|
||||
</span> <span style="color: #afeeee; font-weight: bold;">var</span> <span style="color: #40e0d0; font-weight: bold;">Array</span>;
|
||||
<span style="color: #afeeee; font-weight: bold;">new</span> <span style="color: #87ceeb; font-weight: bold;">Array</span>(1, 2, 3);
|
||||
Array(a, b);
|
||||
|
||||
<span style="color: #add8e6;">// </span><span style="color: #add8e6;">or (can be declared later)
|
||||
</span> <span style="color: #afeeee; font-weight: bold;">new</span> <span style="color: #87ceeb; font-weight: bold;">Array</span>(1, 2, 3);
|
||||
<span style="color: #afeeee; font-weight: bold;">var</span> <span style="color: #40e0d0; font-weight: bold;">Array</span>;
|
||||
|
||||
<span style="color: #add8e6;">// </span><span style="color: #add8e6;">or (can be a function)
|
||||
</span> <span style="color: #afeeee; font-weight: bold;">new</span> <span style="color: #87ceeb; font-weight: bold;">Array</span>(1, 2, 3);
|
||||
<span style="color: #afeeee; font-weight: bold;">function</span> <span style="color: #7fffd4; font-weight: bold;">Array</span>() { ... }
|
||||
|
||||
<span style="color: #add8e6;">// </span><span style="color: #add8e6;">case 2. declared in a function
|
||||
</span> (<span style="color: #afeeee; font-weight: bold;">function</span>(){
|
||||
a = <span style="color: #afeeee; font-weight: bold;">new</span> <span style="color: #87ceeb; font-weight: bold;">Array</span>(1, 2, 3);
|
||||
b = Array(5, 6);
|
||||
<span style="color: #afeeee; font-weight: bold;">var</span> <span style="color: #40e0d0; font-weight: bold;">Array</span>;
|
||||
})();
|
||||
|
||||
<span style="color: #add8e6;">// </span><span style="color: #add8e6;">or
|
||||
</span> (<span style="color: #afeeee; font-weight: bold;">function</span>(<span style="color: #40e0d0; font-weight: bold;">Array</span>){
|
||||
<span style="color: #afeeee; font-weight: bold;">return</span> Array(5, 6, 7);
|
||||
})();
|
||||
|
||||
<span style="color: #add8e6;">// </span><span style="color: #add8e6;">or
|
||||
</span> (<span style="color: #afeeee; font-weight: bold;">function</span>(){
|
||||
<span style="color: #afeeee; font-weight: bold;">return</span> <span style="color: #afeeee; font-weight: bold;">new</span> <span style="color: #87ceeb; font-weight: bold;">Array</span>(1, 2, 3, 4);
|
||||
<span style="color: #afeeee; font-weight: bold;">function</span> <span style="color: #7fffd4; font-weight: bold;">Array</span>() { ... }
|
||||
})();
|
||||
|
||||
<span style="color: #add8e6;">// </span><span style="color: #add8e6;">etc.
|
||||
</span></pre>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="outline-container-1_2" class="outline-3">
|
||||
<h3 id="sec-1_2"><span class="section-number-3">1.2</span> Usage </h3>
|
||||
<div class="outline-text-3" id="text-1_2">
|
||||
|
||||
|
||||
<p>
|
||||
There is a helper script now — <code>bin/uglifyjs</code> — that uses the library to
|
||||
compress a script using the maximum compression settings. Synopsis:
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
<pre class="src src-sh">uglifyjs [ options... ] [ filename ]
|
||||
</pre>
|
||||
|
||||
|
||||
|
||||
<p>
|
||||
<code>filename</code> should be the last argument and should name the file from which
|
||||
to read the JavaScript code. If you don't specify it, it will read code
|
||||
from STDIN.
|
||||
</p>
|
||||
<p>
|
||||
Supported options:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<code>-b</code> or <code>--beautify</code> — output indented code; when passed, additional
|
||||
options control the beautifier:
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<code>-i N</code> or <code>--indent N</code> — indentation level (number of spaces)
|
||||
|
||||
</li>
|
||||
<li>
|
||||
<code>-q</code> or <code>--quote-keys</code> — quote keys in literal objects (by default,
|
||||
only keys that cannot be identifier names will be quotes).
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<code>--ascii</code> — pass this argument to encode non-ASCII characters as
|
||||
<code>\uXXXX</code> sequences. By default UglifyJS won't bother to do it and will
|
||||
output Unicode characters instead. (the output is always encoded in UTF8,
|
||||
but if you pass this option you'll only get ASCII).
|
||||
|
||||
</li>
|
||||
<li>
|
||||
<code>-nm</code> or <code>--no-mangle</code> — don't mangle variable names
|
||||
|
||||
</li>
|
||||
<li>
|
||||
<code>-ns</code> or <code>--no-squeeze</code> — don't call <code>ast_squeeze()</code> (which does various
|
||||
optimizations that result in smaller, less readable code).
|
||||
|
||||
</li>
|
||||
<li>
|
||||
<code>-mt</code> or <code>--mangle-toplevel</code> — mangle names in the toplevel scope too
|
||||
(by default we don't do this).
|
||||
|
||||
</li>
|
||||
<li>
|
||||
<code>--no-seqs</code> — when <code>ast_squeeze()</code> is called (thus, unless you pass
|
||||
<code>--no-squeeze</code>) it will reduce consecutive statements in blocks into a
|
||||
sequence. For example, "a = 10; b = 20; foo();" will be written as
|
||||
"a=10,b=20,foo();". In various occasions, this allows us to discard the
|
||||
block brackets (since the block becomes a single statement). This is ON
|
||||
by default because it seems safe and saves a few hundred bytes on some
|
||||
libs that I tested it on, but pass <code>--no-seqs</code> to disable it.
|
||||
|
||||
</li>
|
||||
<li>
|
||||
<code>--no-dead-code</code> — by default, UglifyJS will remove code that is
|
||||
obviously unreachable (code that follows a <code>return</code>, <code>throw</code>, <code>break</code> or
|
||||
<code>continue</code> statement and is not a function/variable declaration). Pass
|
||||
this option to disable this optimization.
|
||||
|
||||
</li>
|
||||
<li>
|
||||
<code>-nc</code> or <code>--no-copyright</code> — by default, <code>uglifyjs</code> will keep the initial
|
||||
comment tokens in the generated code (assumed to be copyright information
|
||||
etc.). If you pass this it will discard it.
|
||||
|
||||
</li>
|
||||
<li>
|
||||
<code>-o filename</code> or <code>--output filename</code> — put the result in <code>filename</code>. If
|
||||
this isn't given, the result goes to standard output (or see next one).
|
||||
|
||||
</li>
|
||||
<li>
|
||||
<code>--overwrite</code> — if the code is read from a file (not from STDIN) and you
|
||||
pass <code>--overwrite</code> then the output will be written in the same file.
|
||||
|
||||
</li>
|
||||
<li>
|
||||
<code>--ast</code> — pass this if you want to get the Abstract Syntax Tree instead
|
||||
of JavaScript as output. Useful for debugging or learning more about the
|
||||
internals.
|
||||
|
||||
</li>
|
||||
<li>
|
||||
<code>-v</code> or <code>--verbose</code> — output some notes on STDERR (for now just how long
|
||||
each operation takes).
|
||||
|
||||
</li>
|
||||
<li>
|
||||
<code>--extra</code> — enable additional optimizations that have not yet been
|
||||
extensively tested. These might, or might not, break your code. If you
|
||||
find a bug using this option, please report a test case.
|
||||
|
||||
</li>
|
||||
<li>
|
||||
<code>--unsafe</code> — enable other additional optimizations that are known to be
|
||||
unsafe in some contrived situations, but could still be generally useful.
|
||||
For now only this:
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
foo.toString() ==> foo+""
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<code>--max-line-len</code> (default 32K characters) — add a newline after around
|
||||
32K characters. I've seen both FF and Chrome croak when all the code was
|
||||
on a single line of around 670K. Pass –max-line-len 0 to disable this
|
||||
safety feature.
|
||||
|
||||
</li>
|
||||
<li>
|
||||
<code>--reserved-names</code> — some libraries rely on certain names to be used, as
|
||||
pointed out in issue #92 and #81, so this option allow you to exclude such
|
||||
names from the mangler. For example, to keep names <code>require</code> and <code>$super</code>
|
||||
intact you'd specify –reserved-names "require,$super".
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div id="outline-container-1_2_1" class="outline-4">
|
||||
<h4 id="sec-1_2_1"><span class="section-number-4">1.2.1</span> API </h4>
|
||||
<div class="outline-text-4" id="text-1_2_1">
|
||||
|
||||
|
||||
<p>
|
||||
Symlink the <b>lib</b> directory as <b>~/.node_libraries/uglifyjs</b>, so that the
|
||||
<b>require</b> calls in the following sample will work:
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
<pre class="src src-espresso"><span style="color: #afeeee; font-weight: bold;">var</span> <span style="color: #40e0d0; font-weight: bold;">jsp</span> = require(<span style="color: #87cefa;">"uglifyjs/parse-js"</span>);
|
||||
<span style="color: #afeeee; font-weight: bold;">var</span> <span style="color: #40e0d0; font-weight: bold;">pro</span> = require(<span style="color: #87cefa;">"uglifyjs/process"</span>);
|
||||
|
||||
<span style="color: #afeeee; font-weight: bold;">var</span> <span style="color: #40e0d0; font-weight: bold;">orig_code</span> = <span style="color: #87cefa;">"... JS code here"</span>;
|
||||
<span style="color: #afeeee; font-weight: bold;">var</span> <span style="color: #40e0d0; font-weight: bold;">ast</span> = jsp.parse(orig_code); <span style="color: #add8e6;">// </span><span style="color: #add8e6;">parse code and get the initial AST
|
||||
</span>ast = pro.ast_mangle(ast); <span style="color: #add8e6;">// </span><span style="color: #add8e6;">get a new AST with mangled names
|
||||
</span>ast = pro.ast_squeeze(ast); <span style="color: #add8e6;">// </span><span style="color: #add8e6;">get an AST with compression optimizations
|
||||
</span><span style="color: #afeeee; font-weight: bold;">var</span> <span style="color: #40e0d0; font-weight: bold;">final_code</span> = pro.gen_code(ast); <span style="color: #add8e6;">// </span><span style="color: #add8e6;">compressed code here
|
||||
</span></pre>
|
||||
|
||||
|
||||
|
||||
<p>
|
||||
The above performs the full compression that is possible right now. As you
|
||||
can see, there are a sequence of steps which you can apply. For example if
|
||||
you want compressed output but for some reason you don't want to mangle
|
||||
variable names, you would simply skip the line that calls
|
||||
<code>pro.ast_mangle(ast)</code>.
|
||||
</p>
|
||||
<p>
|
||||
Some of these functions take optional arguments. Here's a description:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<code>jsp.parse(code, strict_semicolons)</code> – parses JS code and returns an AST.
|
||||
<code>strict_semicolons</code> is optional and defaults to <code>false</code>. If you pass
|
||||
<code>true</code> then the parser will throw an error when it expects a semicolon and
|
||||
it doesn't find it. For most JS code you don't want that, but it's useful
|
||||
if you want to strictly sanitize your code.
|
||||
|
||||
</li>
|
||||
<li>
|
||||
<code>pro.ast_mangle(ast, options)</code> – generates a new AST containing mangled
|
||||
(compressed) variable and function names. It supports the following
|
||||
options:
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<code>toplevel</code> – mangle toplevel names (by default we don't touch them).
|
||||
</li>
|
||||
<li>
|
||||
<code>except</code> – an array of names to exclude from compression.
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<code>pro.ast_squeeze(ast, options)</code> – employs further optimizations designed
|
||||
to reduce the size of the code that <code>gen_code</code> would generate from the
|
||||
AST. Returns a new AST. <code>options</code> can be a hash; the supported options
|
||||
are:
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<code>make_seqs</code> (default true) which will cause consecutive statements in a
|
||||
block to be merged using the "sequence" (comma) operator
|
||||
|
||||
</li>
|
||||
<li>
|
||||
<code>dead_code</code> (default true) which will remove unreachable code.
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<code>pro.gen_code(ast, options)</code> – generates JS code from the AST. By
|
||||
default it's minified, but using the <code>options</code> argument you can get nicely
|
||||
formatted output. <code>options</code> is, well, optional :-) and if you pass it it
|
||||
must be an object and supports the following properties (below you can see
|
||||
the default values):
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<code>beautify: false</code> – pass <code>true</code> if you want indented output
|
||||
</li>
|
||||
<li>
|
||||
<code>indent_start: 0</code> (only applies when <code>beautify</code> is <code>true</code>) – initial
|
||||
indentation in spaces
|
||||
</li>
|
||||
<li>
|
||||
<code>indent_level: 4</code> (only applies when <code>beautify</code> is <code>true</code>) --
|
||||
indentation level, in spaces (pass an even number)
|
||||
</li>
|
||||
<li>
|
||||
<code>quote_keys: false</code> – if you pass <code>true</code> it will quote all keys in
|
||||
literal objects
|
||||
</li>
|
||||
<li>
|
||||
<code>space_colon: false</code> (only applies when <code>beautify</code> is <code>true</code>) – wether
|
||||
to put a space before the colon in object literals
|
||||
</li>
|
||||
<li>
|
||||
<code>ascii_only: false</code> – pass <code>true</code> if you want to encode non-ASCII
|
||||
characters as <code>\uXXXX</code>.
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="outline-container-1_2_2" class="outline-4">
|
||||
<h4 id="sec-1_2_2"><span class="section-number-4">1.2.2</span> Beautifier shortcoming – no more comments </h4>
|
||||
<div class="outline-text-4" id="text-1_2_2">
|
||||
|
||||
|
||||
<p>
|
||||
The beautifier can be used as a general purpose indentation tool. It's
|
||||
useful when you want to make a minified file readable. One limitation,
|
||||
though, is that it discards all comments, so you don't really want to use it
|
||||
to reformat your code, unless you don't have, or don't care about, comments.
|
||||
</p>
|
||||
<p>
|
||||
In fact it's not the beautifier who discards comments — they are dumped at
|
||||
the parsing stage, when we build the initial AST. Comments don't really
|
||||
make sense in the AST, and while we could add nodes for them, it would be
|
||||
inconvenient because we'd have to add special rules to ignore them at all
|
||||
the processing stages.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="outline-container-1_3" class="outline-3">
|
||||
<h3 id="sec-1_3"><span class="section-number-3">1.3</span> Compression – how good is it? </h3>
|
||||
<div class="outline-text-3" id="text-1_3">
|
||||
|
||||
|
||||
<p>
|
||||
(XXX: this is somewhat outdated. On the jQuery source code we beat Closure
|
||||
by 168 bytes (560 after gzip) and by many seconds.)
|
||||
</p>
|
||||
<p>
|
||||
There are a few popular JS minifiers nowadays – the two most well known
|
||||
being the GoogleClosure (GCL) compiler and the YUI compressor. For some
|
||||
reason they are both written in Java. I didn't really hope to beat any of
|
||||
them, but finally I did – UglifyJS compresses better than the YUI
|
||||
compressor, and safer than GoogleClosure.
|
||||
</p>
|
||||
<p>
|
||||
I tested it on two big libraries. <a href="http://www.dynarchlib.com/">DynarchLIB</a> is my own, and it's big enough
|
||||
to contain probably all the JavaScript tricks known to mankind. <a href="http://jquery.com/">jQuery</a> is
|
||||
definitely the most popular JavaScript library (to some people, it's a
|
||||
synonym to JavaScript itself).
|
||||
</p>
|
||||
<p>
|
||||
I cannot swear that there are no bugs in the generated codes, but they
|
||||
appear to work fine.
|
||||
</p>
|
||||
<p>
|
||||
Compression results:
|
||||
</p>
|
||||
<table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides">
|
||||
<caption></caption>
|
||||
<colgroup><col align="left" /><col align="right" /><col align="right" /><col align="left" /><col align="left" />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr><th scope="col">Library</th><th scope="col">Orig. size</th><th scope="col">UglifyJS</th><th scope="col">YUI</th><th scope="col">GCL</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>DynarchLIB</td><td>636896</td><td>241441</td><td>246452 (+5011)</td><td>240439 (-1002) (buggy)</td></tr>
|
||||
<tr><td>jQuery</td><td>163855</td><td>72006</td><td>79702 (+7696)</td><td>71858 (-148)</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
<p>
|
||||
UglifyJS is the fastest to run. On my laptop UglifyJS takes 1.35s for
|
||||
DynarchLIB, while YUI takes 2.7s and GCL takes 6.5s.
|
||||
</p>
|
||||
<p>
|
||||
GoogleClosure does a lot of smart ass optimizations. I had to strive really
|
||||
hard to get close to it. It should be possible to even beat it, but then
|
||||
again, GCL has a gazillion lines of code and runs terribly slow, so I'm not
|
||||
sure it worths spending the effort to save a few bytes. Also, GCL doesn't
|
||||
cope with <code>eval()</code> or <code>with{}</code> – it just dumps a warning and proceeds to
|
||||
mangle names anyway; my DynarchLIB compiled with it is buggy because of
|
||||
this.
|
||||
</p>
|
||||
<p>
|
||||
UglifyJS consists of ~1100 lines of code for the tokenizer/parser, and ~1100
|
||||
lines for the compressor and code generator. That should make it very
|
||||
maintainable and easily extensible, so I would say it has a good place in
|
||||
this field and it's bound to become the de-facto standard JS minifier. And
|
||||
I shall rule the world. :-) Use it, and <b>spread the word</b>!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="outline-container-1_4" class="outline-3">
|
||||
<h3 id="sec-1_4"><span class="section-number-3">1.4</span> Bugs? </h3>
|
||||
<div class="outline-text-3" id="text-1_4">
|
||||
|
||||
|
||||
<p>
|
||||
Unfortunately, for the time being there is no automated test suite. But I
|
||||
ran the compressor manually on non-trivial code, and then I tested that the
|
||||
generated code works as expected. A few hundred times.
|
||||
</p>
|
||||
<p>
|
||||
DynarchLIB was started in times when there was no good JS minifier.
|
||||
Therefore I was quite religious about trying to write short code manually,
|
||||
and as such DL contains a lot of syntactic hacks<sup><a class="footref" name="fnr.1" href="#fn.1">1</a></sup> such as “foo == bar ? a
|
||||
= 10 : b = 20”, though the more readable version would clearly be to use
|
||||
“if/else”.
|
||||
</p>
|
||||
<p>
|
||||
Since the parser/compressor runs fine on DL and jQuery, I'm quite confident
|
||||
that it's solid enough for production use. If you can identify any bugs,
|
||||
I'd love to hear about them (<a href="http://groups.google.com/group/uglifyjs">use the Google Group</a> or email me directly).
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="outline-container-1_5" class="outline-3">
|
||||
<h3 id="sec-1_5"><span class="section-number-3">1.5</span> Links </h3>
|
||||
<div class="outline-text-3" id="text-1_5">
|
||||
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
Project at GitHub: <a href="http://github.com/mishoo/UglifyJS">http://github.com/mishoo/UglifyJS</a>
|
||||
</li>
|
||||
<li>
|
||||
Google Group: <a href="http://groups.google.com/group/uglifyjs">http://groups.google.com/group/uglifyjs</a>
|
||||
</li>
|
||||
<li>
|
||||
Common Lisp JS parser: <a href="http://marijn.haverbeke.nl/parse-js/">http://marijn.haverbeke.nl/parse-js/</a>
|
||||
</li>
|
||||
<li>
|
||||
JS-to-Lisp compiler: <a href="http://github.com/marijnh/js">http://github.com/marijnh/js</a>
|
||||
</li>
|
||||
<li>
|
||||
Common Lisp JS uglifier: <a href="http://github.com/mishoo/cl-uglify-js">http://github.com/mishoo/cl-uglify-js</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="outline-container-1_6" class="outline-3">
|
||||
<h3 id="sec-1_6"><span class="section-number-3">1.6</span> License </h3>
|
||||
<div class="outline-text-3" id="text-1_6">
|
||||
|
||||
|
||||
<p>
|
||||
UglifyJS is released under the BSD license:
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
<pre class="example">Copyright 2010 (c) Mihai Bazon <mihai.bazon@gmail.com>
|
||||
Based on parse-js (http://marijn.haverbeke.nl/parse-js/).
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials
|
||||
provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
|
||||
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||||
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
||||
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGE.
|
||||
</pre>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="footnotes">
|
||||
<h2 class="footnotes">Footnotes: </h2>
|
||||
<div id="text-footnotes">
|
||||
<p class="footnote"><sup><a class="footnum" name="fn.1" href="#fnr.1">1</a></sup> I even reported a few bugs and suggested some fixes in the original
|
||||
<a href="http://marijn.haverbeke.nl/parse-js/">parse-js</a> library, and Marijn pushed fixes literally in minutes.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="postamble">
|
||||
<p class="author"> Author: Mihai Bazon
|
||||
</p>
|
||||
<p class="date"> Date: 2011-02-28 22:35:00 EET</p>
|
||||
<p class="creator">HTML generated by org-mode 7.01trans in emacs 23</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
408
support/socket.io-client/lib/vendor/uglifyjs/README.org
vendored
Executable file
408
support/socket.io-client/lib/vendor/uglifyjs/README.org
vendored
Executable file
@@ -0,0 +1,408 @@
|
||||
#+TITLE: UglifyJS -- a JavaScript parser/compressor/beautifier
|
||||
#+KEYWORDS: javascript, js, parser, compiler, compressor, mangle, minify, minifier
|
||||
#+DESCRIPTION: a JavaScript parser/compressor/beautifier in JavaScript
|
||||
#+STYLE: <link rel="stylesheet" type="text/css" href="docstyle.css" />
|
||||
#+AUTHOR: Mihai Bazon
|
||||
#+EMAIL: mihai.bazon@gmail.com
|
||||
|
||||
* UglifyJS --- a JavaScript parser/compressor/beautifier
|
||||
|
||||
*Update*: please read the section on [[unsafe transformations]].
|
||||
|
||||
This package implements a general-purpose JavaScript
|
||||
parser/compressor/beautifier toolkit. It is developed on [[http://nodejs.org/][NodeJS]], but it
|
||||
should work on any JavaScript platform supporting the CommonJS module system
|
||||
(and if your platform of choice doesn't support CommonJS, you can easily
|
||||
implement it, or discard the =exports.*= lines from UglifyJS sources).
|
||||
|
||||
The tokenizer/parser generates an abstract syntax tree from JS code. You
|
||||
can then traverse the AST to learn more about the code, or do various
|
||||
manipulations on it. This part is implemented in [[../lib/parse-js.js][parse-js.js]] and it's a
|
||||
port to JavaScript of the excellent [[http://marijn.haverbeke.nl/parse-js/][parse-js]] Common Lisp library from [[http://marijn.haverbeke.nl/][Marijn
|
||||
Haverbeke]].
|
||||
|
||||
( See [[http://github.com/mishoo/cl-uglify-js][cl-uglify-js]] if you're looking for the Common Lisp version of
|
||||
UglifyJS. )
|
||||
|
||||
The second part of this package, implemented in [[../lib/process.js][process.js]], inspects and
|
||||
manipulates the AST generated by the parser to provide the following:
|
||||
|
||||
- ability to re-generate JavaScript code from the AST. Optionally
|
||||
indented---you can use this if you want to “beautify” a program that has
|
||||
been compressed, so that you can inspect the source. But you can also run
|
||||
our code generator to print out an AST without any whitespace, so you
|
||||
achieve compression as well.
|
||||
|
||||
- shorten variable names (usually to single characters). Our mangler will
|
||||
analyze the code and generate proper variable names, depending on scope
|
||||
and usage, and is smart enough to deal with globals defined elsewhere, or
|
||||
with =eval()= calls or =with{}= statements. In short, if =eval()= or
|
||||
=with{}= are used in some scope, then all variables in that scope and any
|
||||
variables in the parent scopes will remain unmangled, and any references
|
||||
to such variables remain unmangled as well.
|
||||
|
||||
- various small optimizations that may lead to faster code but certainly
|
||||
lead to smaller code. Where possible, we do the following:
|
||||
|
||||
- foo["bar"] ==> foo.bar
|
||||
|
||||
- remove block brackets ={}=
|
||||
|
||||
- join consecutive var declarations:
|
||||
var a = 10; var b = 20; ==> var a=10,b=20;
|
||||
|
||||
- resolve simple constant expressions: 1 +2 * 3 ==> 7. We only do the
|
||||
replacement if the result occupies less bytes; for example 1/3 would
|
||||
translate to 0.333333333333, so in this case we don't replace it.
|
||||
|
||||
- consecutive statements in blocks are merged into a sequence; in many
|
||||
cases, this leaves blocks with a single statement, so then we can remove
|
||||
the block brackets.
|
||||
|
||||
- various optimizations for IF statements:
|
||||
|
||||
- if (foo) bar(); else baz(); ==> foo?bar():baz();
|
||||
- if (!foo) bar(); else baz(); ==> foo?baz():bar();
|
||||
- if (foo) bar(); ==> foo&&bar();
|
||||
- if (!foo) bar(); ==> foo||bar();
|
||||
- if (foo) return bar(); else return baz(); ==> return foo?bar():baz();
|
||||
- if (foo) return bar(); else something(); ==> {if(foo)return bar();something()}
|
||||
|
||||
- remove some unreachable code and warn about it (code that follows a
|
||||
=return=, =throw=, =break= or =continue= statement, except
|
||||
function/variable declarations).
|
||||
|
||||
** <<Unsafe transformations>>
|
||||
|
||||
UglifyJS tries its best to achieve great compression while leaving the
|
||||
semantics of the code intact. In general, if your code logic is broken by
|
||||
UglifyJS then it's a bug in UglifyJS and you should report it and I should
|
||||
fix it. :-)
|
||||
|
||||
However, I opted to include the following potentially unsafe transformations
|
||||
as default behavior. Discussion is welcome, if you have ideas of how to
|
||||
handle this better, or any objections to these optimizations, please let me
|
||||
know.
|
||||
|
||||
*** Calls involving the global Array constructor
|
||||
|
||||
The following transformations occur:
|
||||
|
||||
#+BEGIN_SRC espresso
|
||||
new Array(1, 2, 3, 4) => [1,2,3,4]
|
||||
Array(a, b, c) => [a,b,c]
|
||||
new Array(5) => Array(5)
|
||||
new Array(a) => Array(a)
|
||||
#+END_SRC
|
||||
|
||||
These are all safe if the Array name isn't redefined. JavaScript does allow
|
||||
one to globally redefine Array (and pretty much everything, in fact) but I
|
||||
personally don't see why would anyone do that.
|
||||
|
||||
UglifyJS does handle the case where Array is redefined locally, or even
|
||||
globally but with a =function= or =var= declaration. Therefore, in the
|
||||
following cases UglifyJS *doesn't touch* calls or instantiations of Array:
|
||||
|
||||
#+BEGIN_SRC espresso
|
||||
// case 1. globally declared variable
|
||||
var Array;
|
||||
new Array(1, 2, 3);
|
||||
Array(a, b);
|
||||
|
||||
// or (can be declared later)
|
||||
new Array(1, 2, 3);
|
||||
var Array;
|
||||
|
||||
// or (can be a function)
|
||||
new Array(1, 2, 3);
|
||||
function Array() { ... }
|
||||
|
||||
// case 2. declared in a function
|
||||
(function(){
|
||||
a = new Array(1, 2, 3);
|
||||
b = Array(5, 6);
|
||||
var Array;
|
||||
})();
|
||||
|
||||
// or
|
||||
(function(Array){
|
||||
return Array(5, 6, 7);
|
||||
})();
|
||||
|
||||
// or
|
||||
(function(){
|
||||
return new Array(1, 2, 3, 4);
|
||||
function Array() { ... }
|
||||
})();
|
||||
|
||||
// etc.
|
||||
#+END_SRC
|
||||
|
||||
** Usage
|
||||
|
||||
There is a helper script now --- =bin/uglifyjs= --- that uses the library to
|
||||
compress a script using the maximum compression settings. Synopsis:
|
||||
|
||||
#+BEGIN_SRC sh
|
||||
uglifyjs [ options... ] [ filename ]
|
||||
#+END_SRC
|
||||
|
||||
=filename= should be the last argument and should name the file from which
|
||||
to read the JavaScript code. If you don't specify it, it will read code
|
||||
from STDIN.
|
||||
|
||||
Supported options:
|
||||
|
||||
- =-b= or =--beautify= --- output indented code; when passed, additional
|
||||
options control the beautifier:
|
||||
|
||||
- =-i N= or =--indent N= --- indentation level (number of spaces)
|
||||
|
||||
- =-q= or =--quote-keys= --- quote keys in literal objects (by default,
|
||||
only keys that cannot be identifier names will be quotes).
|
||||
|
||||
- =--ascii= --- pass this argument to encode non-ASCII characters as
|
||||
=\uXXXX= sequences. By default UglifyJS won't bother to do it and will
|
||||
output Unicode characters instead. (the output is always encoded in UTF8,
|
||||
but if you pass this option you'll only get ASCII).
|
||||
|
||||
- =-nm= or =--no-mangle= --- don't mangle variable names
|
||||
|
||||
- =-ns= or =--no-squeeze= --- don't call =ast_squeeze()= (which does various
|
||||
optimizations that result in smaller, less readable code).
|
||||
|
||||
- =-mt= or =--mangle-toplevel= --- mangle names in the toplevel scope too
|
||||
(by default we don't do this).
|
||||
|
||||
- =--no-seqs= --- when =ast_squeeze()= is called (thus, unless you pass
|
||||
=--no-squeeze=) it will reduce consecutive statements in blocks into a
|
||||
sequence. For example, "a = 10; b = 20; foo();" will be written as
|
||||
"a=10,b=20,foo();". In various occasions, this allows us to discard the
|
||||
block brackets (since the block becomes a single statement). This is ON
|
||||
by default because it seems safe and saves a few hundred bytes on some
|
||||
libs that I tested it on, but pass =--no-seqs= to disable it.
|
||||
|
||||
- =--no-dead-code= --- by default, UglifyJS will remove code that is
|
||||
obviously unreachable (code that follows a =return=, =throw=, =break= or
|
||||
=continue= statement and is not a function/variable declaration). Pass
|
||||
this option to disable this optimization.
|
||||
|
||||
- =-nc= or =--no-copyright= --- by default, =uglifyjs= will keep the initial
|
||||
comment tokens in the generated code (assumed to be copyright information
|
||||
etc.). If you pass this it will discard it.
|
||||
|
||||
- =-o filename= or =--output filename= --- put the result in =filename=. If
|
||||
this isn't given, the result goes to standard output (or see next one).
|
||||
|
||||
- =--overwrite= --- if the code is read from a file (not from STDIN) and you
|
||||
pass =--overwrite= then the output will be written in the same file.
|
||||
|
||||
- =--ast= --- pass this if you want to get the Abstract Syntax Tree instead
|
||||
of JavaScript as output. Useful for debugging or learning more about the
|
||||
internals.
|
||||
|
||||
- =-v= or =--verbose= --- output some notes on STDERR (for now just how long
|
||||
each operation takes).
|
||||
|
||||
- =--extra= --- enable additional optimizations that have not yet been
|
||||
extensively tested. These might, or might not, break your code. If you
|
||||
find a bug using this option, please report a test case.
|
||||
|
||||
- =--unsafe= --- enable other additional optimizations that are known to be
|
||||
unsafe in some contrived situations, but could still be generally useful.
|
||||
For now only this:
|
||||
|
||||
- foo.toString() ==> foo+""
|
||||
|
||||
- =--max-line-len= (default 32K characters) --- add a newline after around
|
||||
32K characters. I've seen both FF and Chrome croak when all the code was
|
||||
on a single line of around 670K. Pass --max-line-len 0 to disable this
|
||||
safety feature.
|
||||
|
||||
- =--reserved-names= --- some libraries rely on certain names to be used, as
|
||||
pointed out in issue #92 and #81, so this option allow you to exclude such
|
||||
names from the mangler. For example, to keep names =require= and =$super=
|
||||
intact you'd specify --reserved-names "require,$super".
|
||||
|
||||
*** API
|
||||
|
||||
Symlink the *lib* directory as *~/.node\_libraries/uglifyjs*, so that the
|
||||
*require* calls in the following sample will work:
|
||||
|
||||
#+BEGIN_SRC espresso
|
||||
var jsp = require("uglifyjs/parse-js");
|
||||
var pro = require("uglifyjs/process");
|
||||
|
||||
var orig_code = "... JS code here";
|
||||
var ast = jsp.parse(orig_code); // parse code and get the initial AST
|
||||
ast = pro.ast_mangle(ast); // get a new AST with mangled names
|
||||
ast = pro.ast_squeeze(ast); // get an AST with compression optimizations
|
||||
var final_code = pro.gen_code(ast); // compressed code here
|
||||
#+END_SRC
|
||||
|
||||
The above performs the full compression that is possible right now. As you
|
||||
can see, there are a sequence of steps which you can apply. For example if
|
||||
you want compressed output but for some reason you don't want to mangle
|
||||
variable names, you would simply skip the line that calls
|
||||
=pro.ast_mangle(ast)=.
|
||||
|
||||
Some of these functions take optional arguments. Here's a description:
|
||||
|
||||
- =jsp.parse(code, strict_semicolons)= -- parses JS code and returns an AST.
|
||||
=strict_semicolons= is optional and defaults to =false=. If you pass
|
||||
=true= then the parser will throw an error when it expects a semicolon and
|
||||
it doesn't find it. For most JS code you don't want that, but it's useful
|
||||
if you want to strictly sanitize your code.
|
||||
|
||||
- =pro.ast_mangle(ast, options)= -- generates a new AST containing mangled
|
||||
(compressed) variable and function names. It supports the following
|
||||
options:
|
||||
|
||||
- =toplevel= -- mangle toplevel names (by default we don't touch them).
|
||||
- =except= -- an array of names to exclude from compression.
|
||||
|
||||
- =pro.ast_squeeze(ast, options)= -- employs further optimizations designed
|
||||
to reduce the size of the code that =gen_code= would generate from the
|
||||
AST. Returns a new AST. =options= can be a hash; the supported options
|
||||
are:
|
||||
|
||||
- =make_seqs= (default true) which will cause consecutive statements in a
|
||||
block to be merged using the "sequence" (comma) operator
|
||||
|
||||
- =dead_code= (default true) which will remove unreachable code.
|
||||
|
||||
- =pro.gen_code(ast, options)= -- generates JS code from the AST. By
|
||||
default it's minified, but using the =options= argument you can get nicely
|
||||
formatted output. =options= is, well, optional :-) and if you pass it it
|
||||
must be an object and supports the following properties (below you can see
|
||||
the default values):
|
||||
|
||||
- =beautify: false= -- pass =true= if you want indented output
|
||||
- =indent_start: 0= (only applies when =beautify= is =true=) -- initial
|
||||
indentation in spaces
|
||||
- =indent_level: 4= (only applies when =beautify= is =true=) --
|
||||
indentation level, in spaces (pass an even number)
|
||||
- =quote_keys: false= -- if you pass =true= it will quote all keys in
|
||||
literal objects
|
||||
- =space_colon: false= (only applies when =beautify= is =true=) -- wether
|
||||
to put a space before the colon in object literals
|
||||
- =ascii_only: false= -- pass =true= if you want to encode non-ASCII
|
||||
characters as =\uXXXX=.
|
||||
|
||||
*** Beautifier shortcoming -- no more comments
|
||||
|
||||
The beautifier can be used as a general purpose indentation tool. It's
|
||||
useful when you want to make a minified file readable. One limitation,
|
||||
though, is that it discards all comments, so you don't really want to use it
|
||||
to reformat your code, unless you don't have, or don't care about, comments.
|
||||
|
||||
In fact it's not the beautifier who discards comments --- they are dumped at
|
||||
the parsing stage, when we build the initial AST. Comments don't really
|
||||
make sense in the AST, and while we could add nodes for them, it would be
|
||||
inconvenient because we'd have to add special rules to ignore them at all
|
||||
the processing stages.
|
||||
|
||||
** Compression -- how good is it?
|
||||
|
||||
(XXX: this is somewhat outdated. On the jQuery source code we beat Closure
|
||||
by 168 bytes (560 after gzip) and by many seconds.)
|
||||
|
||||
There are a few popular JS minifiers nowadays -- the two most well known
|
||||
being the GoogleClosure (GCL) compiler and the YUI compressor. For some
|
||||
reason they are both written in Java. I didn't really hope to beat any of
|
||||
them, but finally I did -- UglifyJS compresses better than the YUI
|
||||
compressor, and safer than GoogleClosure.
|
||||
|
||||
I tested it on two big libraries. [[http://www.dynarchlib.com/][DynarchLIB]] is my own, and it's big enough
|
||||
to contain probably all the JavaScript tricks known to mankind. [[http://jquery.com/][jQuery]] is
|
||||
definitely the most popular JavaScript library (to some people, it's a
|
||||
synonym to JavaScript itself).
|
||||
|
||||
I cannot swear that there are no bugs in the generated codes, but they
|
||||
appear to work fine.
|
||||
|
||||
Compression results:
|
||||
|
||||
| Library | Orig. size | UglifyJS | YUI | GCL |
|
||||
|------------+------------+----------+----------------+------------------------|
|
||||
| DynarchLIB | 636896 | 241441 | 246452 (+5011) | 240439 (-1002) (buggy) |
|
||||
| jQuery | 163855 | 72006 | 79702 (+7696) | 71858 (-148) |
|
||||
|
||||
UglifyJS is the fastest to run. On my laptop UglifyJS takes 1.35s for
|
||||
DynarchLIB, while YUI takes 2.7s and GCL takes 6.5s.
|
||||
|
||||
GoogleClosure does a lot of smart ass optimizations. I had to strive really
|
||||
hard to get close to it. It should be possible to even beat it, but then
|
||||
again, GCL has a gazillion lines of code and runs terribly slow, so I'm not
|
||||
sure it worths spending the effort to save a few bytes. Also, GCL doesn't
|
||||
cope with =eval()= or =with{}= -- it just dumps a warning and proceeds to
|
||||
mangle names anyway; my DynarchLIB compiled with it is buggy because of
|
||||
this.
|
||||
|
||||
UglifyJS consists of ~1100 lines of code for the tokenizer/parser, and ~1100
|
||||
lines for the compressor and code generator. That should make it very
|
||||
maintainable and easily extensible, so I would say it has a good place in
|
||||
this field and it's bound to become the de-facto standard JS minifier. And
|
||||
I shall rule the world. :-) Use it, and *spread the word*!
|
||||
|
||||
** Bugs?
|
||||
|
||||
Unfortunately, for the time being there is no automated test suite. But I
|
||||
ran the compressor manually on non-trivial code, and then I tested that the
|
||||
generated code works as expected. A few hundred times.
|
||||
|
||||
DynarchLIB was started in times when there was no good JS minifier.
|
||||
Therefore I was quite religious about trying to write short code manually,
|
||||
and as such DL contains a lot of syntactic hacks[1] such as “foo == bar ? a
|
||||
= 10 : b = 20”, though the more readable version would clearly be to use
|
||||
“if/else”.
|
||||
|
||||
Since the parser/compressor runs fine on DL and jQuery, I'm quite confident
|
||||
that it's solid enough for production use. If you can identify any bugs,
|
||||
I'd love to hear about them ([[http://groups.google.com/group/uglifyjs][use the Google Group]] or email me directly).
|
||||
|
||||
[1] I even reported a few bugs and suggested some fixes in the original
|
||||
[[http://marijn.haverbeke.nl/parse-js/][parse-js]] library, and Marijn pushed fixes literally in minutes.
|
||||
|
||||
** Links
|
||||
|
||||
- Project at GitHub: [[http://github.com/mishoo/UglifyJS][http://github.com/mishoo/UglifyJS]]
|
||||
- Google Group: [[http://groups.google.com/group/uglifyjs][http://groups.google.com/group/uglifyjs]]
|
||||
- Common Lisp JS parser: [[http://marijn.haverbeke.nl/parse-js/][http://marijn.haverbeke.nl/parse-js/]]
|
||||
- JS-to-Lisp compiler: [[http://github.com/marijnh/js][http://github.com/marijnh/js]]
|
||||
- Common Lisp JS uglifier: [[http://github.com/mishoo/cl-uglify-js][http://github.com/mishoo/cl-uglify-js]]
|
||||
|
||||
** License
|
||||
|
||||
UglifyJS is released under the BSD license:
|
||||
|
||||
#+BEGIN_EXAMPLE
|
||||
Copyright 2010 (c) Mihai Bazon <mihai.bazon@gmail.com>
|
||||
Based on parse-js (http://marijn.haverbeke.nl/parse-js/).
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials
|
||||
provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
|
||||
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||||
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
||||
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGE.
|
||||
#+END_EXAMPLE
|
||||
212
support/socket.io-client/lib/vendor/uglifyjs/bin/uglifyjs
vendored
Executable file
212
support/socket.io-client/lib/vendor/uglifyjs/bin/uglifyjs
vendored
Executable file
@@ -0,0 +1,212 @@
|
||||
#! /usr/bin/env node
|
||||
// -*- js2 -*-
|
||||
|
||||
global.sys = require(/^v0\.[012]/.test(process.version) ? "sys" : "util");
|
||||
var fs = require("fs");
|
||||
var uglify = require("uglify-js"), // symlink ~/.node_libraries/uglify-js.js to ../uglify-js.js
|
||||
jsp = uglify.parser,
|
||||
pro = uglify.uglify;
|
||||
|
||||
var options = {
|
||||
ast: false,
|
||||
mangle: true,
|
||||
mangle_toplevel: false,
|
||||
squeeze: true,
|
||||
make_seqs: true,
|
||||
dead_code: true,
|
||||
verbose: false,
|
||||
show_copyright: true,
|
||||
out_same_file: false,
|
||||
max_line_length: 32 * 1024,
|
||||
unsafe: false,
|
||||
reserved_names: null,
|
||||
codegen_options: {
|
||||
ascii_only: false,
|
||||
beautify: false,
|
||||
indent_level: 4,
|
||||
indent_start: 0,
|
||||
quote_keys: false,
|
||||
space_colon: false
|
||||
},
|
||||
output: true // stdout
|
||||
};
|
||||
|
||||
var args = jsp.slice(process.argv, 2);
|
||||
var filename;
|
||||
|
||||
out: while (args.length > 0) {
|
||||
var v = args.shift();
|
||||
switch (v) {
|
||||
case "-b":
|
||||
case "--beautify":
|
||||
options.codegen_options.beautify = true;
|
||||
break;
|
||||
case "-i":
|
||||
case "--indent":
|
||||
options.codegen_options.indent_level = args.shift();
|
||||
break;
|
||||
case "-q":
|
||||
case "--quote-keys":
|
||||
options.codegen_options.quote_keys = true;
|
||||
break;
|
||||
case "-mt":
|
||||
case "--mangle-toplevel":
|
||||
options.mangle_toplevel = true;
|
||||
break;
|
||||
case "--no-mangle":
|
||||
case "-nm":
|
||||
options.mangle = false;
|
||||
break;
|
||||
case "--no-squeeze":
|
||||
case "-ns":
|
||||
options.squeeze = false;
|
||||
break;
|
||||
case "--no-seqs":
|
||||
options.make_seqs = false;
|
||||
break;
|
||||
case "--no-dead-code":
|
||||
options.dead_code = false;
|
||||
break;
|
||||
case "--no-copyright":
|
||||
case "-nc":
|
||||
options.show_copyright = false;
|
||||
break;
|
||||
case "-o":
|
||||
case "--output":
|
||||
options.output = args.shift();
|
||||
break;
|
||||
case "--overwrite":
|
||||
options.out_same_file = true;
|
||||
break;
|
||||
case "-v":
|
||||
case "--verbose":
|
||||
options.verbose = true;
|
||||
break;
|
||||
case "--ast":
|
||||
options.ast = true;
|
||||
break;
|
||||
case "--unsafe":
|
||||
options.unsafe = true;
|
||||
break;
|
||||
case "--max-line-len":
|
||||
options.max_line_length = parseInt(args.shift(), 10);
|
||||
break;
|
||||
case "--reserved-names":
|
||||
options.reserved_names = args.shift().split(",");
|
||||
break;
|
||||
case "--ascii":
|
||||
options.codegen_options.ascii_only = true;
|
||||
break;
|
||||
default:
|
||||
filename = v;
|
||||
break out;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.verbose) {
|
||||
pro.set_logger(function(msg){
|
||||
sys.debug(msg);
|
||||
});
|
||||
}
|
||||
|
||||
jsp.set_logger(function(msg){
|
||||
sys.debug(msg);
|
||||
});
|
||||
|
||||
if (filename) {
|
||||
fs.readFile(filename, "utf8", function(err, text){
|
||||
if (err) throw err;
|
||||
output(squeeze_it(text));
|
||||
});
|
||||
} else {
|
||||
var stdin = process.openStdin();
|
||||
stdin.setEncoding("utf8");
|
||||
var text = "";
|
||||
stdin.on("data", function(chunk){
|
||||
text += chunk;
|
||||
});
|
||||
stdin.on("end", function() {
|
||||
output(squeeze_it(text));
|
||||
});
|
||||
}
|
||||
|
||||
function output(text) {
|
||||
var out;
|
||||
if (options.out_same_file && filename)
|
||||
options.output = filename;
|
||||
if (options.output === true) {
|
||||
out = process.stdout;
|
||||
} else {
|
||||
out = fs.createWriteStream(options.output, {
|
||||
flags: "w",
|
||||
encoding: "utf8",
|
||||
mode: 0644
|
||||
});
|
||||
}
|
||||
out.write(text);
|
||||
if (options.output !== true) {
|
||||
out.end();
|
||||
}
|
||||
};
|
||||
|
||||
// --------- main ends here.
|
||||
|
||||
function show_copyright(comments) {
|
||||
var ret = "";
|
||||
for (var i = 0; i < comments.length; ++i) {
|
||||
var c = comments[i];
|
||||
if (c.type == "comment1") {
|
||||
ret += "//" + c.value + "\n";
|
||||
} else {
|
||||
ret += "/*" + c.value + "*/";
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
function squeeze_it(code) {
|
||||
var result = "";
|
||||
if (options.show_copyright) {
|
||||
var tok = jsp.tokenizer(code), c;
|
||||
c = tok();
|
||||
result += show_copyright(c.comments_before);
|
||||
}
|
||||
try {
|
||||
var ast = time_it("parse", function(){ return jsp.parse(code); });
|
||||
if (options.mangle) ast = time_it("mangle", function(){
|
||||
return pro.ast_mangle(ast, {
|
||||
toplevel: options.mangle_toplevel,
|
||||
except: options.reserved_names
|
||||
});
|
||||
});
|
||||
if (options.squeeze) ast = time_it("squeeze", function(){
|
||||
ast = pro.ast_squeeze(ast, {
|
||||
make_seqs : options.make_seqs,
|
||||
dead_code : options.dead_code,
|
||||
keep_comps : !options.unsafe
|
||||
});
|
||||
if (options.unsafe)
|
||||
ast = pro.ast_squeeze_more(ast);
|
||||
return ast;
|
||||
});
|
||||
if (options.ast)
|
||||
return sys.inspect(ast, null, null);
|
||||
result += time_it("generate", function(){ return pro.gen_code(ast, options.codegen_options) });
|
||||
if (!options.codegen_options.beautify && options.max_line_length) {
|
||||
result = time_it("split", function(){ return pro.split_lines(result, options.max_line_length) });
|
||||
}
|
||||
return result;
|
||||
} catch(ex) {
|
||||
sys.debug(ex.stack);
|
||||
sys.debug(sys.inspect(ex));
|
||||
sys.debug(JSON.stringify(ex));
|
||||
}
|
||||
};
|
||||
|
||||
function time_it(name, cont) {
|
||||
if (!options.verbose)
|
||||
return cont();
|
||||
var t1 = new Date().getTime();
|
||||
try { return cont(); }
|
||||
finally { sys.debug("// " + name + ": " + ((new Date().getTime() - t1) / 1000).toFixed(3) + " sec."); }
|
||||
};
|
||||
75
support/socket.io-client/lib/vendor/uglifyjs/docstyle.css
vendored
Executable file
75
support/socket.io-client/lib/vendor/uglifyjs/docstyle.css
vendored
Executable file
@@ -0,0 +1,75 @@
|
||||
html { font-family: "Lucida Grande","Trebuchet MS",sans-serif; font-size: 12pt; }
|
||||
body { max-width: 60em; }
|
||||
.title { text-align: center; }
|
||||
.todo { color: red; }
|
||||
.done { color: green; }
|
||||
.tag { background-color:lightblue; font-weight:normal }
|
||||
.target { }
|
||||
.timestamp { color: grey }
|
||||
.timestamp-kwd { color: CadetBlue }
|
||||
p.verse { margin-left: 3% }
|
||||
pre {
|
||||
border: 1pt solid #AEBDCC;
|
||||
background-color: #F3F5F7;
|
||||
padding: 5pt;
|
||||
font-family: monospace;
|
||||
font-size: 90%;
|
||||
overflow:auto;
|
||||
}
|
||||
pre.src {
|
||||
background-color: #333; color: #ffd; border: 1px solid #000;
|
||||
}
|
||||
table { border-collapse: collapse; }
|
||||
td, th { vertical-align: top; }
|
||||
dt { font-weight: bold; }
|
||||
div.figure { padding: 0.5em; }
|
||||
div.figure p { text-align: center; }
|
||||
.linenr { font-size:smaller }
|
||||
.code-highlighted {background-color:#ffff00;}
|
||||
.org-info-js_info-navigation { border-style:none; }
|
||||
#org-info-js_console-label { font-size:10px; font-weight:bold;
|
||||
white-space:nowrap; }
|
||||
.org-info-js_search-highlight {background-color:#ffff00; color:#000000;
|
||||
font-weight:bold; }
|
||||
|
||||
sup {
|
||||
vertical-align: baseline;
|
||||
position: relative;
|
||||
top: -0.5em;
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
sup a:link, sup a:visited {
|
||||
text-decoration: none;
|
||||
color: #c00;
|
||||
}
|
||||
|
||||
sup a:before { content: "["; color: #999; }
|
||||
sup a:after { content: "]"; color: #999; }
|
||||
|
||||
h1.title { border-bottom: 4px solid #000; padding-bottom: 5px; margin-bottom: 2em; }
|
||||
|
||||
#postamble {
|
||||
color: #777;
|
||||
font-size: 90%;
|
||||
padding-top: 1em; padding-bottom: 1em; border-top: 1px solid #999;
|
||||
margin-top: 2em;
|
||||
padding-left: 2em;
|
||||
padding-right: 2em;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#postamble p { margin: 0; }
|
||||
|
||||
#footnotes { border-top: 1px solid #000; }
|
||||
|
||||
h1 { font-size: 200% }
|
||||
h2 { font-size: 175% }
|
||||
h3 { font-size: 150% }
|
||||
h4 { font-size: 125% }
|
||||
|
||||
h1, h2, h3, h4 { font-family: "Bookman",Georgia,"Times New Roman",serif; font-weight: normal; }
|
||||
|
||||
@media print {
|
||||
html { font-size: 11pt; }
|
||||
}
|
||||
2
support/socket.io-client/lib/vendor/uglifyjs/index.js
vendored
Executable file
2
support/socket.io-client/lib/vendor/uglifyjs/index.js
vendored
Executable file
@@ -0,0 +1,2 @@
|
||||
exports.parser = require("./lib/parse-js");
|
||||
exports.uglify = require("./lib/process");
|
||||
1320
support/socket.io-client/lib/vendor/uglifyjs/lib/parse-js.js
vendored
Executable file
1320
support/socket.io-client/lib/vendor/uglifyjs/lib/parse-js.js
vendored
Executable file
File diff suppressed because it is too large
Load Diff
1609
support/socket.io-client/lib/vendor/uglifyjs/lib/process.js
vendored
Executable file
1609
support/socket.io-client/lib/vendor/uglifyjs/lib/process.js
vendored
Executable file
File diff suppressed because it is too large
Load Diff
22
support/socket.io-client/lib/vendor/uglifyjs/lib/squeeze-more.js
vendored
Executable file
22
support/socket.io-client/lib/vendor/uglifyjs/lib/squeeze-more.js
vendored
Executable file
@@ -0,0 +1,22 @@
|
||||
var jsp = require("./parse-js"),
|
||||
pro = require("./process"),
|
||||
slice = jsp.slice,
|
||||
member = jsp.member,
|
||||
PRECEDENCE = jsp.PRECEDENCE,
|
||||
OPERATORS = jsp.OPERATORS;
|
||||
|
||||
function ast_squeeze_more(ast) {
|
||||
var w = pro.ast_walker(), walk = w.walk;
|
||||
return w.with_walkers({
|
||||
"call": function(expr, args) {
|
||||
if (expr[0] == "dot" && expr[2] == "toString" && args.length == 0) {
|
||||
// foo.toString() ==> foo+""
|
||||
return [ "binary", "+", expr[1], [ "string", "" ]];
|
||||
}
|
||||
}
|
||||
}, function() {
|
||||
return walk(ast);
|
||||
});
|
||||
};
|
||||
|
||||
exports.ast_squeeze_more = ast_squeeze_more;
|
||||
6
support/socket.io-client/lib/vendor/uglifyjs/package.json
vendored
Executable file
6
support/socket.io-client/lib/vendor/uglifyjs/package.json
vendored
Executable file
@@ -0,0 +1,6 @@
|
||||
{"name" : "uglify-js",
|
||||
"author" : "Mihai Bazon - http://github.com/mishoo",
|
||||
"version" : "0.0.1",
|
||||
"main" : "index.js",
|
||||
"bin" : { "uglifyjs" : "./bin/uglifyjs" },
|
||||
}
|
||||
28
support/socket.io-client/lib/vendor/uglifyjs/test/beautify.js
vendored
Executable file
28
support/socket.io-client/lib/vendor/uglifyjs/test/beautify.js
vendored
Executable file
@@ -0,0 +1,28 @@
|
||||
#! /usr/bin/env node
|
||||
|
||||
global.sys = require("sys");
|
||||
var fs = require("fs");
|
||||
|
||||
var jsp = require("../lib/parse-js");
|
||||
var pro = require("../lib/process");
|
||||
|
||||
var filename = process.argv[2];
|
||||
fs.readFile(filename, "utf8", function(err, text){
|
||||
try {
|
||||
var ast = time_it("parse", function(){ return jsp.parse(text); });
|
||||
ast = time_it("mangle", function(){ return pro.ast_mangle(ast); });
|
||||
ast = time_it("squeeze", function(){ return pro.ast_squeeze(ast); });
|
||||
var gen = time_it("generate", function(){ return pro.gen_code(ast, false); });
|
||||
sys.puts(gen);
|
||||
} catch(ex) {
|
||||
sys.debug(ex.stack);
|
||||
sys.debug(sys.inspect(ex));
|
||||
sys.debug(JSON.stringify(ex));
|
||||
}
|
||||
});
|
||||
|
||||
function time_it(name, cont) {
|
||||
var t1 = new Date().getTime();
|
||||
try { return cont(); }
|
||||
finally { sys.debug("// " + name + ": " + ((new Date().getTime() - t1) / 1000).toFixed(3) + " sec."); }
|
||||
};
|
||||
402
support/socket.io-client/lib/vendor/uglifyjs/test/testparser.js
vendored
Executable file
402
support/socket.io-client/lib/vendor/uglifyjs/test/testparser.js
vendored
Executable file
@@ -0,0 +1,402 @@
|
||||
#! /usr/bin/env node
|
||||
|
||||
var parseJS = require("../lib/parse-js");
|
||||
var sys = require("sys");
|
||||
|
||||
// write debug in a very straightforward manner
|
||||
var debug = function(){
|
||||
sys.log(Array.prototype.slice.call(arguments).join(', '));
|
||||
};
|
||||
|
||||
ParserTestSuite(function(i, input, desc){
|
||||
try {
|
||||
parseJS.parse(input);
|
||||
debug("ok " + i + ": " + desc);
|
||||
} catch(e){
|
||||
debug("FAIL " + i + " " + desc + " (" + e + ")");
|
||||
}
|
||||
});
|
||||
|
||||
function ParserTestSuite(callback){
|
||||
var inps = [
|
||||
["var abc;", "Regular variable statement w/o assignment"],
|
||||
["var abc = 5;", "Regular variable statement with assignment"],
|
||||
["/* */;", "Multiline comment"],
|
||||
['/** **/;', 'Double star multiline comment'],
|
||||
["var f = function(){;};", "Function expression in var assignment"],
|
||||
['hi; // moo\n;', 'single line comment'],
|
||||
['var varwithfunction;', 'Dont match keywords as substrings'], // difference between `var withsomevar` and `"str"` (local search and lits)
|
||||
['a + b;', 'addition'],
|
||||
["'a';", 'single string literal'],
|
||||
["'a\\n';", 'single string literal with escaped return'],
|
||||
['"a";', 'double string literal'],
|
||||
['"a\\n";', 'double string literal with escaped return'],
|
||||
['"var";', 'string is a keyword'],
|
||||
['"variable";', 'string starts with a keyword'],
|
||||
['"somevariable";', 'string contains a keyword'],
|
||||
['"somevar";', 'string ends with a keyword'],
|
||||
['500;', 'int literal'],
|
||||
['500.;', 'float literal w/o decimals'],
|
||||
['500.432;', 'float literal with decimals'],
|
||||
['.432432;', 'float literal w/o int'],
|
||||
['(a,b,c);', 'parens and comma'],
|
||||
['[1,2,abc];', 'array literal'],
|
||||
['var o = {a:1};', 'object literal unquoted key'],
|
||||
['var o = {"b":2};', 'object literal quoted key'], // opening curly may not be at the start of a statement...
|
||||
['var o = {c:c};', 'object literal keyname is identifier'],
|
||||
['var o = {a:1,"b":2,c:c};', 'object literal combinations'],
|
||||
['var x;\nvar y;', 'two lines'],
|
||||
['var x;\nfunction n(){; }', 'function def'],
|
||||
['var x;\nfunction n(abc){; }', 'function def with arg'],
|
||||
['var x;\nfunction n(abc, def){ ;}', 'function def with args'],
|
||||
['function n(){ "hello"; }', 'function def with body'],
|
||||
['/a/;', 'regex literal'],
|
||||
['/a/b;', 'regex literal with flag'],
|
||||
['/a/ / /b/;', 'regex div regex'],
|
||||
['a/b/c;', 'triple division looks like regex'],
|
||||
['+function(){/regex/;};', 'regex at start of function body'],
|
||||
// http://code.google.com/p/es-lab/source/browse/trunk/tests/parser/parsertests.js?r=86
|
||||
// http://code.google.com/p/es-lab/source/browse/trunk/tests/parser/parsertests.js?r=430
|
||||
|
||||
// first tests for the lexer, should also parse as program (when you append a semi)
|
||||
|
||||
// comments
|
||||
['//foo!@#^&$1234\nbar;', 'single line comment'],
|
||||
['/* abcd!@#@$* { } && null*/;', 'single line multi line comment'],
|
||||
['/*foo\nbar*/;','multi line comment'],
|
||||
['/*x*x*/;','multi line comment with *'],
|
||||
['/**/;','empty comment'],
|
||||
// identifiers
|
||||
["x;",'1 identifier'],
|
||||
["_x;",'2 identifier'],
|
||||
["xyz;",'3 identifier'],
|
||||
["$x;",'4 identifier'],
|
||||
["x$;",'5 identifier'],
|
||||
["_;",'6 identifier'],
|
||||
["x5;",'7 identifier'],
|
||||
["x_y;",'8 identifier'],
|
||||
["x+5;",'9 identifier'],
|
||||
["xyz123;",'10 identifier'],
|
||||
["x1y1z1;",'11 identifier'],
|
||||
["foo\\u00D8bar;",'12 identifier unicode escape'],
|
||||
//["foo<6F>bar;",'13 identifier unicode embedded (might fail)'],
|
||||
// numbers
|
||||
["5;", '1 number'],
|
||||
["5.5;", '2 number'],
|
||||
["0;", '3 number'],
|
||||
["0.0;", '4 number'],
|
||||
["0.001;", '5 number'],
|
||||
["1.e2;", '6 number'],
|
||||
["1.e-2;", '7 number'],
|
||||
["1.E2;", '8 number'],
|
||||
["1.E-2;", '9 number'],
|
||||
[".5;", '10 number'],
|
||||
[".5e3;", '11 number'],
|
||||
[".5e-3;", '12 number'],
|
||||
["0.5e3;", '13 number'],
|
||||
["55;", '14 number'],
|
||||
["123;", '15 number'],
|
||||
["55.55;", '16 number'],
|
||||
["55.55e10;", '17 number'],
|
||||
["123.456;", '18 number'],
|
||||
["1+e;", '20 number'],
|
||||
["0x01;", '22 number'],
|
||||
["0XCAFE;", '23 number'],
|
||||
["0x12345678;", '24 number'],
|
||||
["0x1234ABCD;", '25 number'],
|
||||
["0x0001;", '26 number'],
|
||||
// strings
|
||||
["\"foo\";", '1 string'],
|
||||
["\'foo\';", '2 string'],
|
||||
["\"x\";", '3 string'],
|
||||
["\'\';", '4 string'],
|
||||
["\"foo\\tbar\";", '5 string'],
|
||||
["\"!@#$%^&*()_+{}[]\";", '6 string'],
|
||||
["\"/*test*/\";", '7 string'],
|
||||
["\"//test\";", '8 string'],
|
||||
["\"\\\\\";", '9 string'],
|
||||
["\"\\u0001\";", '10 string'],
|
||||
["\"\\uFEFF\";", '11 string'],
|
||||
["\"\\u10002\";", '12 string'],
|
||||
["\"\\x55\";", '13 string'],
|
||||
["\"\\x55a\";", '14 string'],
|
||||
["\"a\\\\nb\";", '15 string'],
|
||||
['";"', '16 string: semi in a string'],
|
||||
['"a\\\nb";', '17 string: line terminator escape'],
|
||||
// literals
|
||||
["null;", "null"],
|
||||
["true;", "true"],
|
||||
["false;", "false"],
|
||||
// regex
|
||||
["/a/;", "1 regex"],
|
||||
["/abc/;", "2 regex"],
|
||||
["/abc[a-z]*def/g;", "3 regex"],
|
||||
["/\\b/;", "4 regex"],
|
||||
["/[a-zA-Z]/;", "5 regex"],
|
||||
|
||||
// program tests (for as far as they havent been covered above)
|
||||
|
||||
// regexp
|
||||
["/foo(.*)/g;", "another regexp"],
|
||||
// arrays
|
||||
["[];", "1 array"],
|
||||
["[ ];", "2 array"],
|
||||
["[1];", "3 array"],
|
||||
["[1,2];", "4 array"],
|
||||
["[1,2,,];", "5 array"],
|
||||
["[1,2,3];", "6 array"],
|
||||
["[1,2,3,,,];", "7 array"],
|
||||
// objects
|
||||
["{};", "1 object"],
|
||||
["({x:5});", "2 object"],
|
||||
["({x:5,y:6});", "3 object"],
|
||||
["({x:5,});", "4 object"],
|
||||
["({if:5});", "5 object"],
|
||||
["({ get x() {42;} });", "6 object"],
|
||||
["({ set y(a) {1;} });", "7 object"],
|
||||
// member expression
|
||||
["o.m;", "1 member expression"],
|
||||
["o['m'];", "2 member expression"],
|
||||
["o['n']['m'];", "3 member expression"],
|
||||
["o.n.m;", "4 member expression"],
|
||||
["o.if;", "5 member expression"],
|
||||
// call and invoke expressions
|
||||
["f();", "1 call/invoke expression"],
|
||||
["f(x);", "2 call/invoke expression"],
|
||||
["f(x,y);", "3 call/invoke expression"],
|
||||
["o.m();", "4 call/invoke expression"],
|
||||
["o['m'];", "5 call/invoke expression"],
|
||||
["o.m(x);", "6 call/invoke expression"],
|
||||
["o['m'](x);", "7 call/invoke expression"],
|
||||
["o.m(x,y);", "8 call/invoke expression"],
|
||||
["o['m'](x,y);", "9 call/invoke expression"],
|
||||
["f(x)(y);", "10 call/invoke expression"],
|
||||
["f().x;", "11 call/invoke expression"],
|
||||
|
||||
// eval
|
||||
["eval('x');", "1 eval"],
|
||||
["(eval)('x');", "2 eval"],
|
||||
["(1,eval)('x');", "3 eval"],
|
||||
["eval(x,y);", "4 eval"],
|
||||
// new expression
|
||||
["new f();", "1 new expression"],
|
||||
["new o;", "2 new expression"],
|
||||
["new o.m;", "3 new expression"],
|
||||
["new o.m(x);", "4 new expression"],
|
||||
["new o.m(x,y);", "5 new expression"],
|
||||
// prefix/postfix
|
||||
["++x;", "1 pre/postfix"],
|
||||
["x++;", "2 pre/postfix"],
|
||||
["--x;", "3 pre/postfix"],
|
||||
["x--;", "4 pre/postfix"],
|
||||
["x ++;", "5 pre/postfix"],
|
||||
["x /* comment */ ++;", "6 pre/postfix"],
|
||||
["++ /* comment */ x;", "7 pre/postfix"],
|
||||
// unary operators
|
||||
["delete x;", "1 unary operator"],
|
||||
["void x;", "2 unary operator"],
|
||||
["+ x;", "3 unary operator"],
|
||||
["-x;", "4 unary operator"],
|
||||
["~x;", "5 unary operator"],
|
||||
["!x;", "6 unary operator"],
|
||||
// meh
|
||||
["new Date++;", "new date ++"],
|
||||
["+x++;", " + x ++"],
|
||||
// expression expressions
|
||||
["1 * 2;", "1 expression expressions"],
|
||||
["1 / 2;", "2 expression expressions"],
|
||||
["1 % 2;", "3 expression expressions"],
|
||||
["1 + 2;", "4 expression expressions"],
|
||||
["1 - 2;", "5 expression expressions"],
|
||||
["1 << 2;", "6 expression expressions"],
|
||||
["1 >>> 2;", "7 expression expressions"],
|
||||
["1 >> 2;", "8 expression expressions"],
|
||||
["1 * 2 + 3;", "9 expression expressions"],
|
||||
["(1+2)*3;", "10 expression expressions"],
|
||||
["1*(2+3);", "11 expression expressions"],
|
||||
["x<y;", "12 expression expressions"],
|
||||
["x>y;", "13 expression expressions"],
|
||||
["x<=y;", "14 expression expressions"],
|
||||
["x>=y;", "15 expression expressions"],
|
||||
["x instanceof y;", "16 expression expressions"],
|
||||
["x in y;", "17 expression expressions"],
|
||||
["x&y;", "18 expression expressions"],
|
||||
["x^y;", "19 expression expressions"],
|
||||
["x|y;", "20 expression expressions"],
|
||||
["x+y<z;", "21 expression expressions"],
|
||||
["x<y+z;", "22 expression expressions"],
|
||||
["x+y+z;", "23 expression expressions"],
|
||||
["x+y<z;", "24 expression expressions"],
|
||||
["x<y+z;", "25 expression expressions"],
|
||||
["x&y|z;", "26 expression expressions"],
|
||||
["x&&y;", "27 expression expressions"],
|
||||
["x||y;", "28 expression expressions"],
|
||||
["x&&y||z;", "29 expression expressions"],
|
||||
["x||y&&z;", "30 expression expressions"],
|
||||
["x<y?z:w;", "31 expression expressions"],
|
||||
// assignment
|
||||
["x >>>= y;", "1 assignment"],
|
||||
["x <<= y;", "2 assignment"],
|
||||
["x = y;", "3 assignment"],
|
||||
["x += y;", "4 assignment"],
|
||||
["x /= y;", "5 assignment"],
|
||||
// comma
|
||||
["x, y;", "comma"],
|
||||
// block
|
||||
["{};", "1 block"],
|
||||
["{x;};", "2 block"],
|
||||
["{x;y;};", "3 block"],
|
||||
// vars
|
||||
["var x;", "1 var"],
|
||||
["var x,y;", "2 var"],
|
||||
["var x=1,y=2;", "3 var"],
|
||||
["var x,y=2;", "4 var"],
|
||||
// empty
|
||||
[";", "1 empty"],
|
||||
["\n;", "2 empty"],
|
||||
// expression statement
|
||||
["x;", "1 expression statement"],
|
||||
["5;", "2 expression statement"],
|
||||
["1+2;", "3 expression statement"],
|
||||
// if
|
||||
["if (c) x; else y;", "1 if statement"],
|
||||
["if (c) x;", "2 if statement"],
|
||||
["if (c) {} else {};", "3 if statement"],
|
||||
["if (c1) if (c2) s1; else s2;", "4 if statement"],
|
||||
// while
|
||||
["do s; while (e);", "1 while statement"],
|
||||
["do { s; } while (e);", "2 while statement"],
|
||||
["while (e) s;", "3 while statement"],
|
||||
["while (e) { s; };", "4 while statement"],
|
||||
// for
|
||||
["for (;;) ;", "1 for statement"],
|
||||
["for (;c;x++) x;", "2 for statement"],
|
||||
["for (i;i<len;++i){};", "3 for statement"],
|
||||
["for (var i=0;i<len;++i) {};", "4 for statement"],
|
||||
["for (var i=0,j=0;;){};", "5 for statement"],
|
||||
//["for (x in b; c; u) {};", "6 for statement"],
|
||||
["for ((x in b); c; u) {};", "7 for statement"],
|
||||
["for (x in a);", "8 for statement"],
|
||||
["for (var x in a){};", "9 for statement"],
|
||||
["for (var x=5 in a) {};", "10 for statement"],
|
||||
["for (var x = a in b in c) {};", "11 for statement"],
|
||||
["for (var x=function(){a+b;}; a<b; ++i) some;", "11 for statement, testing for parsingForHeader reset with the function"],
|
||||
["for (var x=function(){for (x=0; x<15; ++x) alert(foo); }; a<b; ++i) some;", "11 for statement, testing for parsingForHeader reset with the function"],
|
||||
// flow statements
|
||||
["while(1){ continue; }", "1 flow statement"],
|
||||
["label: while(1){ continue label; }", "2 flow statement"],
|
||||
["while(1){ break; }", "3 flow statement"],
|
||||
["somewhere: while(1){ break somewhere; }", "4 flow statement"],
|
||||
["while(1){ continue /* comment */ ; }", "5 flow statement"],
|
||||
["while(1){ continue \n; }", "6 flow statement"],
|
||||
["(function(){ return; })()", "7 flow statement"],
|
||||
["(function(){ return 0; })()", "8 flow statement"],
|
||||
["(function(){ return 0 + \n 1; })()", "9 flow statement"],
|
||||
// with
|
||||
["with (e) s;", "with statement"],
|
||||
// switch
|
||||
["switch (e) { case x: s; };", "1 switch statement"],
|
||||
["switch (e) { case x: s1;s2; default: s3; case y: s4; };", "2 switch statement"],
|
||||
["switch (e) { default: s1; case x: s2; case y: s3; };", "3 switch statement"],
|
||||
["switch (e) { default: s; };", "4 switch statement"],
|
||||
["switch (e) { case x: s1; case y: s2; };", "5 switch statement"],
|
||||
// labels
|
||||
["foo : x;", " flow statement"],
|
||||
// throw
|
||||
["throw x;", "1 throw statement"],
|
||||
["throw x\n;", "2 throw statement"],
|
||||
// try catch finally
|
||||
["try { s1; } catch (e) { s2; };", "1 trycatchfinally statement"],
|
||||
["try { s1; } finally { s2; };", "2 trycatchfinally statement"],
|
||||
["try { s1; } catch (e) { s2; } finally { s3; };", "3 trycatchfinally statement"],
|
||||
// debugger
|
||||
["debugger;", "debuger statement"],
|
||||
// function decl
|
||||
["function f(x) { e; return x; };", "1 function declaration"],
|
||||
["function f() { x; y; };", "2 function declaration"],
|
||||
["function f(x,y) { var z; return x; };", "3 function declaration"],
|
||||
// function exp
|
||||
["(function f(x) { return x; });", "1 function expression"],
|
||||
["(function empty() {;});", "2 function expression"],
|
||||
["(function empty() {;});", "3 function expression"],
|
||||
["(function (x) {; });", "4 function expression"],
|
||||
// program
|
||||
["var x; function f(){;}; null;", "1 program"],
|
||||
[";;", "2 program"],
|
||||
["{ x; y; z; }", "3 program"],
|
||||
["function f(){ function g(){;}};", "4 program"],
|
||||
["x;\n/*foo*/\n ;", "5 program"],
|
||||
|
||||
// asi
|
||||
["foo: while(1){ continue \n foo; }", "1 asi"],
|
||||
["foo: while(1){ break \n foo; }", "2 asi"],
|
||||
["(function(){ return\nfoo; })()", "3 asi"],
|
||||
["var x; { 1 \n 2 } 3", "4 asi"],
|
||||
["ab /* hi */\ncd", "5 asi"],
|
||||
["ab/*\n*/cd", "6 asi (multi line multilinecomment counts as eol)"],
|
||||
["foo: while(1){ continue /* wtf \n busta */ foo; }", "7 asi illegal with multi line comment"],
|
||||
["function f() { s }", "8 asi"],
|
||||
["function f() { return }", "9 asi"],
|
||||
|
||||
// use strict
|
||||
// XXX: some of these should actually fail?
|
||||
// no support for "use strict" yet...
|
||||
['"use strict"; \'bla\'\n; foo;', "1 directive"],
|
||||
['(function() { "use strict"; \'bla\';\n foo; });', "2 directive"],
|
||||
['"use\\n strict";', "3 directive"],
|
||||
['foo; "use strict";', "4 directive"],
|
||||
|
||||
// tests from http://es5conform.codeplex.com/
|
||||
|
||||
['"use strict"; var o = { eval: 42};', "8.7.2-3-1-s: the use of eval as property name is allowed"],
|
||||
['({foo:0,foo:1});', 'Duplicate property name allowed in not strict mode'],
|
||||
['function foo(a,a){}', 'Duplicate parameter name allowed in not strict mode'],
|
||||
['(function foo(eval){})', 'Eval allowed as parameter name in non strict mode'],
|
||||
['(function foo(arguments){})', 'Arguments allowed as parameter name in non strict mode'],
|
||||
|
||||
// empty programs
|
||||
|
||||
['', '1 Empty program'],
|
||||
['// test', '2 Empty program'],
|
||||
['//test\n', '3 Empty program'],
|
||||
['\n// test', '4 Empty program'],
|
||||
['\n// test\n', '5 Empty program'],
|
||||
['/* */', '6 Empty program'],
|
||||
['/*\ns,fd\n*/', '7 Empty program'],
|
||||
['/*\ns,fd\n*/\n', '8 Empty program'],
|
||||
[' ', '9 Empty program'],
|
||||
[' /*\nsmeh*/ \n ', '10 Empty program'],
|
||||
|
||||
// trailing whitespace
|
||||
|
||||
['a ', '1 Trailing whitespace'],
|
||||
['a /* something */', '2 Trailing whitespace'],
|
||||
['a\n // hah', '3 Trailing whitespace'],
|
||||
['/abc/de//f', '4 Trailing whitespace'],
|
||||
['/abc/de/*f*/\n ', '5 Trailing whitespace'],
|
||||
|
||||
// things the parser tripped over at one point or the other (prevents regression bugs)
|
||||
['for (x;function(){ a\nb };z) x;', 'for header with function body forcing ASI'],
|
||||
['c=function(){return;return};', 'resetting noAsi after literal'],
|
||||
['d\nd()', 'asi exception causing token overflow'],
|
||||
['for(;;){x=function(){}}', 'function expression in a for header'],
|
||||
['for(var k;;){}', 'parser failing due to ASI accepting the incorrect "for" rule'],
|
||||
['({get foo(){ }})', 'getter with empty function body'],
|
||||
['\nreturnr', 'eol causes return statement to ignore local search requirement'],
|
||||
[' / /', '1 whitespace before regex causes regex to fail?'],
|
||||
['/ // / /', '2 whitespace before regex causes regex to fail?'],
|
||||
['/ / / / /', '3 whitespace before regex causes regex to fail?'],
|
||||
|
||||
['\n\t// Used for trimming whitespace\n\ttrimLeft = /^\\s+/;\n\ttrimRight = /\\s+$/;\t\n','turned out this didnt crash (the test below did), but whatever.'],
|
||||
['/[\\/]/;', 'escaped forward slash inside class group (would choke on fwd slash)'],
|
||||
['/[/]/;', 'also broke but is valid in es5 (not es3)'],
|
||||
['({get:5});','get property name thats not a getter'],
|
||||
['({set:5});','set property name thats not a setter'],
|
||||
['l !== "px" && (d.style(h, c, (k || 1) + l), j = (k || 1) / f.cur() * j, d.style(h, c, j + l)), i[1] && (k = (i[1] === "-=" ? -1 : 1) * k + j), f.custom(j, k, l)', 'this choked regex/div at some point'],
|
||||
['(/\'/g, \'\\\\\\\'\') + "\'";', 'the sequence of escaped characters confused the tokenizer']
|
||||
];
|
||||
|
||||
for (var i=0; i<inps.length; ++i) {
|
||||
callback(i, inps[i][0], inps[i][1]);
|
||||
};
|
||||
};
|
||||
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/array1.js
vendored
Executable file
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/array1.js
vendored
Executable file
@@ -0,0 +1 @@
|
||||
[],Array(1),[1,2,3]
|
||||
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/array2.js
vendored
Executable file
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/array2.js
vendored
Executable file
@@ -0,0 +1 @@
|
||||
(function(){var a=function(){};return new a(1,2,3,4)})()
|
||||
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/array3.js
vendored
Executable file
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/array3.js
vendored
Executable file
@@ -0,0 +1 @@
|
||||
(function(){function a(){}return new a(1,2,3,4)})()
|
||||
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/array4.js
vendored
Executable file
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/array4.js
vendored
Executable file
@@ -0,0 +1 @@
|
||||
(function(){function a(){}(function(){return new a(1,2,3)})()})()
|
||||
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/assignment.js
vendored
Executable file
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/assignment.js
vendored
Executable file
@@ -0,0 +1 @@
|
||||
a=1,b=a,c=1,d=b,e=d,longname=2;if(longname+1){x=3;if(x)var z=7}z=1,y=1,x=1,g+=1,h=g,++i,j=i,i++,j=i+17
|
||||
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/concatstring.js
vendored
Executable file
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/concatstring.js
vendored
Executable file
@@ -0,0 +1 @@
|
||||
var a=a+"a"+"b"+1+c,b=a+"c"+"ds"+123+c,c=a+"c"+123+d+"ds"+c
|
||||
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/const.js
vendored
Executable file
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/const.js
vendored
Executable file
@@ -0,0 +1 @@
|
||||
var a=13,b=1/3
|
||||
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/empty-blocks.js
vendored
Executable file
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/empty-blocks.js
vendored
Executable file
@@ -0,0 +1 @@
|
||||
function mak(){for(;;);}function foo(){while(bar());}function bar(){return--x}var x=5
|
||||
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/forstatement.js
vendored
Executable file
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/forstatement.js
vendored
Executable file
@@ -0,0 +1 @@
|
||||
a=func(),b=z;for(a++;i<10;i++)alert(i);var z=1;g=2;for(;i<10;i++)alert(i);var a=2;for(var i=1;i<10;i++)alert(i)
|
||||
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/if.js
vendored
Executable file
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/if.js
vendored
Executable file
@@ -0,0 +1 @@
|
||||
var a=1;a==1?a=2:a=17
|
||||
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/ifreturn.js
vendored
Executable file
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/ifreturn.js
vendored
Executable file
@@ -0,0 +1 @@
|
||||
function a(a){return a==1?2:17}
|
||||
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue10.js
vendored
Executable file
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue10.js
vendored
Executable file
@@ -0,0 +1 @@
|
||||
function f(){var a;return(a="a")?a:a}f()
|
||||
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue11.js
vendored
Executable file
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue11.js
vendored
Executable file
@@ -0,0 +1 @@
|
||||
new(A,B),new(A||B),new(X?A:B)
|
||||
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue13.js
vendored
Executable file
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue13.js
vendored
Executable file
@@ -0,0 +1 @@
|
||||
var a=/^(?:(\w+):)?(?:\/\/(?:(?:([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#])(?::(\d))?)?(..?$|(?:[^?#\/]\/))([^?#]*)(?:\?([^#]))?(?:#(.))?/
|
||||
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue14.js
vendored
Executable file
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue14.js
vendored
Executable file
@@ -0,0 +1 @@
|
||||
var a={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"}
|
||||
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue16.js
vendored
Executable file
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue16.js
vendored
Executable file
@@ -0,0 +1 @@
|
||||
var a=3250441966
|
||||
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue17.js
vendored
Executable file
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue17.js
vendored
Executable file
@@ -0,0 +1 @@
|
||||
var a=function(b){b(),a()}
|
||||
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue20.js
vendored
Executable file
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue20.js
vendored
Executable file
@@ -0,0 +1 @@
|
||||
a:1
|
||||
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue21.js
vendored
Executable file
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue21.js
vendored
Executable file
@@ -0,0 +1 @@
|
||||
var a=0;switch(a){case 0:a++}
|
||||
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue25.js
vendored
Executable file
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue25.js
vendored
Executable file
@@ -0,0 +1 @@
|
||||
label1:{label2:break label2;console.log(1)}
|
||||
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue27.js
vendored
Executable file
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue27.js
vendored
Executable file
@@ -0,0 +1 @@
|
||||
(a?b:c)?d:e
|
||||
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue28.js
vendored
Executable file
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue28.js
vendored
Executable file
@@ -0,0 +1 @@
|
||||
o={".5":.5},o={.5:.5},o={.5:.5}
|
||||
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue29.js
vendored
Executable file
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue29.js
vendored
Executable file
@@ -0,0 +1 @@
|
||||
result=function(){return 1}()
|
||||
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue30.js
vendored
Executable file
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue30.js
vendored
Executable file
@@ -0,0 +1 @@
|
||||
var a=8,b=4,c=4
|
||||
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue34.js
vendored
Executable file
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue34.js
vendored
Executable file
@@ -0,0 +1 @@
|
||||
var a={};a["this"]=1,a.that=2
|
||||
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue4.js
vendored
Executable file
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue4.js
vendored
Executable file
@@ -0,0 +1 @@
|
||||
var a=2e3,b=.002,c=2e-5
|
||||
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue48.js
vendored
Executable file
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue48.js
vendored
Executable file
@@ -0,0 +1 @@
|
||||
var s,i;s="",i=0
|
||||
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue50.js
vendored
Executable file
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue50.js
vendored
Executable file
@@ -0,0 +1 @@
|
||||
function bar(a){try{foo()}catch(b){alert("Exception caught (foo not defined)")}alert(a)}bar(10)
|
||||
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue53.js
vendored
Executable file
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue53.js
vendored
Executable file
@@ -0,0 +1 @@
|
||||
x=(y,z)
|
||||
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue54.1.js
vendored
Executable file
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue54.1.js
vendored
Executable file
@@ -0,0 +1 @@
|
||||
foo+"",a.toString(16),b.toString.call(c)
|
||||
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue68.js
vendored
Executable file
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue68.js
vendored
Executable file
@@ -0,0 +1 @@
|
||||
function f(){function b(){}a||b()}
|
||||
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue69.js
vendored
Executable file
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue69.js
vendored
Executable file
@@ -0,0 +1 @@
|
||||
[(a,b)]
|
||||
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue9.js
vendored
Executable file
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/issue9.js
vendored
Executable file
@@ -0,0 +1 @@
|
||||
var a={a:1,b:2}
|
||||
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/strict-equals.js
vendored
Executable file
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/strict-equals.js
vendored
Executable file
@@ -0,0 +1 @@
|
||||
typeof a=="string",b+""!=c+"",d<e==f<g
|
||||
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/var.js
vendored
Executable file
1
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/expected/var.js
vendored
Executable file
@@ -0,0 +1 @@
|
||||
var a=1,b=2
|
||||
3
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/test/array1.js
vendored
Executable file
3
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/test/array1.js
vendored
Executable file
@@ -0,0 +1,3 @@
|
||||
new Array();
|
||||
new Array(1);
|
||||
new Array(1, 2, 3);
|
||||
4
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/test/array2.js
vendored
Executable file
4
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/test/array2.js
vendored
Executable file
@@ -0,0 +1,4 @@
|
||||
(function(){
|
||||
var Array = function(){};
|
||||
return new Array(1, 2, 3, 4);
|
||||
})();
|
||||
4
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/test/array3.js
vendored
Executable file
4
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/test/array3.js
vendored
Executable file
@@ -0,0 +1,4 @@
|
||||
(function(){
|
||||
return new Array(1, 2, 3, 4);
|
||||
function Array() {};
|
||||
})();
|
||||
6
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/test/array4.js
vendored
Executable file
6
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/test/array4.js
vendored
Executable file
@@ -0,0 +1,6 @@
|
||||
(function(){
|
||||
(function(){
|
||||
return new Array(1, 2, 3);
|
||||
})();
|
||||
function Array(){};
|
||||
})();
|
||||
20
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/test/assignment.js
vendored
Executable file
20
support/socket.io-client/lib/vendor/uglifyjs/test/unit/compress/test/assignment.js
vendored
Executable file
@@ -0,0 +1,20 @@
|
||||
a=1;
|
||||
b=a;
|
||||
c=1;
|
||||
d=b;
|
||||
e=d;
|
||||
longname=2;
|
||||
if (longname+1) {
|
||||
x=3;
|
||||
if (x) var z = 7;
|
||||
}
|
||||
z=1,y=1,x=1
|
||||
|
||||
g+=1;
|
||||
h=g;
|
||||
|
||||
++i;
|
||||
j=i;
|
||||
|
||||
i++;
|
||||
j=i+17;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user