Compare commits

...

56 Commits
0.6.3 ... 06

Author SHA1 Message Date
Guillermo Rauch
4395b86c60 README updates. 2011-05-17 18:56:02 -07:00
Guillermo Rauch
43d58186d8 Rebuild client 2011-05-16 13:19:35 -07:00
Guillermo Rauch
2066ddd8fe Release 0.6.18 2011-05-16 13:12:08 -07:00
Guillermo Rauch
db50f3e8ee Updated client 2011-05-13 14:37:24 -07:00
Guillermo Rauch
b79faf8998 Updated client. 2011-05-13 14:17:36 -07:00
Justin Randell
ce88922bea #169: fix returning 'ws' for location even when the client connects via 'wss' 2011-03-31 12:01:05 +11:00
Guillermo Rauch
5c4c681a83 Updated client 2011-03-30 12:01:23 -07:00
Guillermo Rauch
7ca0606670 Release 0.6.17 2011-03-30 11:45:06 -07:00
Guillermo Rauch
f6036007fc Merge branch 'master' of github.com:LearnBoost/Socket.IO-node 2011-03-30 11:43:23 -07:00
Guillermo Rauch
aa027ab571 Preparing awesome 0.7 changelog 2011-03-07 10:16:51 -08:00
Guillermo Rauch
b4f24c6995 Added reconnect events to chat example 2011-03-07 10:11:24 -08:00
Guillermo Rauch
3a53c63778 Added Arnout as official contributor 2011-03-07 09:46:12 -08:00
Arnout Kazemier
d8e3ccc637 Cleaned up the code, changed tabs to spaces and incorporated feedback on from the pull request 2011-03-06 15:25:35 +01:00
Arnout Kazemier
cef5fb3574 Merge branch 'master' of git://github.com/LearnBoost/Socket.IO-node 2011-03-05 19:36:42 +01:00
Guillermo Rauch
cfbae2ac15 Release 0.6.16 2011-03-04 09:06:01 -08:00
Guillermo Rauch
1072add73f Merge branch 'master' of https://github.com/tifroz/Socket.IO-node into tifroz-master 2011-03-04 09:03:43 -08:00
Arnout Kazemier
648b1d0792 Passes test-cases again, without hanging the test suite.
The fix for it is not as elegant as I would have hoped but it does the
job, preventing un-needed listeners to be added on the connections.
2011-03-03 21:39:27 +01:00
Arnout Kazemier
63624e5996 Added more listeners, to prevent uncaught exceptions. See https://gist.github.com/615009 for uncaptured 'timeout' errors. 2011-03-03 14:13:19 +01:00
Arnout Kazemier
1ee5285136 Fixed the 'possible EventEmitter memory leak detected' bug for the XHR transport. 2011-03-03 13:36:06 +01:00
Guillermo Rauch
5308452b8a Release 0.6.15 2011-02-23 11:20:33 -08:00
mlinnell
2ee09436ce Setting WebSocket buffer to empty once nonce is received (intended to include in previous commit) 2011-02-23 19:01:35 +00:00
mlinnell
d2ecaff462 Fixed memory leak in WebSocket transport.
The WebSocket.buffer would continue to grow, unabated, for each new message.
This buffer is now only utilized to process the nonce, and is set to empty (and no longer filled) once nonce has been received.
Parser instance appears to handle all message buffering, instead of WebSocket entity.
2011-02-23 18:55:24 +00:00
Guillermo Rauch
023566e03b Release 0.6.14 2011-02-22 11:09:42 -08:00
Shripad K
c306a3c303 fixed minor bug in xhr-polling, xhr-multipart, htmlfile. (this.listener.options.log -> self.listener.options.log) 2011-02-22 17:34:39 +05:30
Guillermo Rauch
80f1d9780b Release 0.6.13 2011-02-18 15:18:46 -08:00
Guillermo Rauch
ff10eeecba Fixed references to listener when logging 2011-02-18 15:18:10 -08:00
Guillermo Rauch
cd9cbb500b Fixed listener reference 2011-02-18 15:17:14 -08:00
Guillermo Rauch
de8d573948 Release 0.6.12 2011-02-18 10:36:13 -08:00
Guillermo Rauch
ea9e5ed2cc Fixed noDelay missing file descriptor problem 2011-02-18 10:34:51 -08:00
tifroz
d648fc5a72 Allow x-domain xhr-polling with Origin: null for Chrome/Safari 2011-02-16 00:16:41 -08:00
Guillermo Rauch
109a59ca9a Release 0.6.11 2011-02-15 15:43:35 -08:00
Guillermo Rauch
d304ce19d1 Fixed; Make sure to not execute any other connection operations after WebSocket write error 2011-02-15 15:41:45 -08:00
Marcus Westin
f534a260b3 Don't let errors in an application's message handling code get silently swallowed by any of the transports 2011-02-14 19:56:20 -05:00
Marcus Westin
cff4669d57 Log errors from closing the flash socket server 2011-02-14 19:54:00 -05:00
Guillermo Rauch
4b0a1f22c8 Release 0.6.10 2011-02-09 18:45:00 -08:00
Guillermo Rauch
e79bdb00e9 Added SSL chat example (make example-ssl) 2011-02-09 18:44:07 -08:00
Guillermo Rauch
d11ca00b49 Fixed; possible write errors when a connection error event fires 2011-02-09 18:41:13 -08:00
Guillermo Rauch
f3ba4173c7 Release 0.6.9 2011-02-06 10:09:13 -08:00
Guillermo Rauch
569103e19a Updated expresso 2011-02-06 09:34:16 -08:00
Guillermo Rauch
06445a0faa Added comments and version number to socket.io/index 2011-02-06 09:33:40 -08:00
Guillermo Rauch
2506b06961 Tests refactored for 0.3 and new expresso 2011-02-06 09:33:15 -08:00
Guillermo Rauch
992eda86b4 Updated socket.io client to 0.6.2 2011-02-05 11:40:39 -08:00
Guillermo Rauch
6fa8b1f051 Fixed Flash inline policy serving for Firefox 4 2011-02-05 11:40:14 -08:00
Guillermo Rauch
a91c6f26f4 Release 0.6.8 2011-01-10 01:50:26 -08:00
Guillermo Rauch
aa9f2596cb Fixed issue with terminating connection twice 2011-01-10 01:48:05 -08:00
Guillermo Rauch
e2a97588ef Removed unnecessary code 2011-01-09 19:07:11 -08:00
Guillermo Rauch
0b904d79c2 Release 0.6.7 2011-01-09 18:55:30 -08:00
Guillermo Rauch
f99ac54df5 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.
2011-01-09 18:53:40 -08:00
Guillermo Rauch
b306cc77d7 Release 0.6.6 2011-01-09 18:17:42 -08:00
Guillermo Rauch
ba70be4e0b 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
2011-01-09 18:16:24 -08:00
Guillermo Rauch
0b17ec9cb8 Release 0.6.5 2011-01-09 16:56:54 -08:00
Guillermo Rauch
71e77561bb Make sure not to trigger multiple timeouts when closing
Important fix for polling transports.
2011-01-09 16:54:35 -08:00
Guillermo Rauch
f5b2028577 Release 0.6.4 2011-01-05 11:21:37 -08:00
Fabian Jakobs
7f08d8fd59 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.

According to the node documentation "destroy" is
only necessary in case of a errors.
2011-01-05 13:25:35 +01:00
Mathew Rodley
ffb0574a76 Added try/catch block around JSON.parse and return an empty object literal
if JSON parsing fails.
2010-12-30 13:18:27 +11:00
Guillermo Rauch
e57e27ee43 Added missing .connect() to example 2010-12-24 13:02:35 -08:00
154 changed files with 10570 additions and 5077 deletions

View File

@@ -1,4 +1,95 @@
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
==================

View File

@@ -7,4 +7,7 @@ test-cov:
example:
node ./example/server.js
.PHONY: example
example-ssl:
node ./example/server-ssl.js
.PHONY: example

View File

@@ -32,10 +32,14 @@ and point your browser to `http://localhost:8080`. In addition to `8080`, if the
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:
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
@@ -59,6 +63,7 @@ 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(){ … })

21
example/cert.crt Normal file
View 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
View 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, '&lt;').replace(/>/g, '&gt;');
};
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>

View File

@@ -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>

27
example/key.key Normal file
View 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
View 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' });
});
});

View File

@@ -48,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]);
@@ -57,31 +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();
});
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 = [];
@@ -121,15 +138,11 @@ Client.prototype._onHeartbeat = function(h){
};
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;
if (this.connection){
this.connection.end();
this.connection.destroy();
this.connection = null;
}
this.request = null;
this.response = null;
if (skipDisconnect !== false){

View File

@@ -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';

View File

@@ -21,7 +21,9 @@ Flashsocket.init = function(listener){
if (listeners.length === 0 && netserver){
try {
netserver.close();
} catch(e){}
} catch(e){
listener.options.log('flashsocket netserver close error - ' + e.stack)
}
}
});
@@ -42,7 +44,7 @@ Flashsocket.init = function(listener){
netserver.listen(843);
} catch(e){
if (e.errno == 13)
listener.options.log('Your node instance does not have root privileges.'
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.');

View File

@@ -32,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();

View File

@@ -33,7 +33,7 @@ WebSocket.prototype._onConnect = function(req, socket){
}
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;
@@ -78,14 +78,14 @@ WebSocket.prototype._onConnect = function(req, socket){
try {
this.connection.write(headers.concat('', '').join('\r\n'));
this.connection.setTimeout(0);
this.connection.setNoDelay(true);
this.connection.setEncoding('utf-8');
} catch(e){
this._onClose();
return;
}
this.connection.setTimeout(0);
this.connection.setNoDelay(true);
this.connection.setEncoding('utf-8');
if (this.waitingForNonce) {
// Since we will be receiving the binary nonce through the normal HTTP
// data event, set the connection to 'binary' temporarily
@@ -99,18 +99,16 @@ WebSocket.prototype._onConnect = function(req, socket){
this.buffer = "";
this.connection.addListener('data', function(data){
self.buffer += 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 = self.buffer.substr(8);
if (self.buffer.length > 0) {
self.parser.add(self.buffer);
}
self.buffer = '';
if (self._proveReception(self._headers)) { self._payload(); }
return;
}

View File

@@ -46,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();

View File

@@ -12,13 +12,12 @@ Polling.prototype.getOptions = function(){
return {
timeout: null, // no heartbeats
closeTimeout: 8000,
duration: 50000
duration: 20000
};
};
Polling.prototype._onConnect = function(req, res){
var self = this, body = '';
switch (req.method){
case 'GET':
Client.prototype._onConnect.apply(this, [req, res]);
@@ -49,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();
@@ -68,7 +69,7 @@ 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;
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);

View File

@@ -1,17 +1,21 @@
{ "name" : "socket.io"
, "description" : "The cross-browser WebSocket"
, "version" : "0.6.3"
, "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" }
}

View File

@@ -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
==================

View File

@@ -35,5 +35,27 @@ Install via npm:
$ npm install expresso
## License
(The MIT License)
Copyright (c) 2010 TJ Holowaychuk &lt;tj@vision-media.ca&gt;
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.

View File

@@ -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);
});

View File

@@ -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">'&quot; + key + &quot;'</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>

View File

@@ -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);

View File

@@ -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);

View File

@@ -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": {

View File

@@ -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(){

View File

@@ -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);

View File

@@ -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());
}
};

View File

@@ -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());
}

View File

@@ -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);
});
}
};

View File

@@ -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']);
}
};

View File

@@ -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'

View File

@@ -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

View 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.

View File

@@ -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 &lt;head&gt;
<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 &lt;guillermo@learnboost.com&gt;
Arnout Kazemier &lt;info@3rd-eden.com&gt;
### 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.

View File

@@ -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!');

View File

@@ -1,23 +1,44 @@
/**
* 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.1',
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 + '/';
/**
* @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';
if (typeof WEB_SOCKET_SWF_LOCATION === 'undefined')

View File

@@ -1,161 +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;
this.connectTimeoutTimer = 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(){
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);
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;
};
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();
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;
};
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.emit('connect');
};
Socket.prototype._onMessage = function(data){
this.emit('message', [data]);
};
Socket.prototype._onDisconnect = function(){
var wasConnected = this.connected;
this.connected = false;
this.connecting = false;
this._queueStack = [];
if (wasConnected) this.emit('disconnect');
};
/**
* 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.addListener = Socket.prototype.addEvent = Socket.prototype.addEventListener = Socket.prototype.on;
Socket.prototype.removeListener = Socket.prototype.removeEventListener = Socket.prototype.removeEvent;
})();

View File

@@ -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) : '/');
};
})();

View File

@@ -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;
};
})();

View File

@@ -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;
};
})();

View File

@@ -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;
};
})();

View File

@@ -1,65 +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(); };
this.socket.onerror = function(e){ self._onError(e); };
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._onError = function(e){
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]);
};
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;
};
/**
* 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;
};
})();

View File

@@ -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(null);
};
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;
};
})();

View File

@@ -1,61 +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 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');
this._xhr.onreadystatechange = 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 (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();
self.onData(self.xhr.responseText);
self.get();
} else {
self._onDisconnect();
self.onDisconnect();
}
}
};
this._xhr.send(null);
};
XHRPolling.check = function(){
return io.Transport.XHR.check();
};
XHRPolling.xdomainCheck = function(){
return io.Transport.XHR.xdomainCheck();
};
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();
};
})();

View File

@@ -1,135 +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 = empty;
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 {
this._xhr.abort();
var a = new ActiveXObject('MSXML2.XMLHTTP');
return a;
} catch(e){}
this._xhr = null;
}
if (this._sendXhr){
this._sendXhr.onreadystatechange = empty;
try {
this._sendXhr.abort();
var b = new ActiveXObject('Microsoft.XMLHTTP');
return b;
} catch(e){}
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;
}
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;
})();

View File

@@ -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 (/loaded|complete/.test(document.readyState) || _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];
}
};
/**
* 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;
});
};
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;
});
})();
})();

View File

@@ -0,0 +1,4 @@
.DS_Store
.tmp*~
*.local.*
.pinf-*

View 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 &ndash; 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 &mdash; 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 &ndash; no more comments </a></li>
</ul>
</li>
<li><a href="#sec-1_3">1.3 Compression &ndash; 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 &mdash; 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&mdash;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"] ==&gt; foo.bar
</li>
<li>
remove block brackets <code>{}</code>
</li>
<li>
join consecutive var declarations:
var a = 10; var b = 20; ==&gt; var a=10,b=20;
</li>
<li>
resolve simple constant expressions: 1 +2 * 3 ==&gt; 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(); ==&gt; foo?bar():baz();
</li>
<li>
if (!foo) bar(); else baz(); ==&gt; foo?baz():bar();
</li>
<li>
if (foo) bar(); ==&gt; foo&amp;&amp;bar();
</li>
<li>
if (!foo) bar(); ==&gt; foo||bar();
</li>
<li>
if (foo) return bar(); else return baz(); ==&gt; return foo?bar():baz();
</li>
<li>
if (foo) return bar(); else something(); ==&gt; {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) =&gt; [1,2,3,4]
Array(a, b, c) =&gt; [a,b,c]
<span style="color: #afeeee; font-weight: bold;">new</span> <span style="color: #87ceeb; font-weight: bold;">Array</span>(5) =&gt; Array(5)
<span style="color: #afeeee; font-weight: bold;">new</span> <span style="color: #87ceeb; font-weight: bold;">Array</span>(a) =&gt; 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 &mdash; <code>bin/uglifyjs</code> &mdash; 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> &mdash; output indented code; when passed, additional
options control the beautifier:
<ul>
<li>
<code>-i N</code> or <code>--indent N</code> &mdash; indentation level (number of spaces)
</li>
<li>
<code>-q</code> or <code>--quote-keys</code> &mdash; quote keys in literal objects (by default,
only keys that cannot be identifier names will be quotes).
</li>
</ul>
</li>
<li>
<code>--ascii</code> &mdash; 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> &mdash; don't mangle variable names
</li>
<li>
<code>-ns</code> or <code>--no-squeeze</code> &mdash; 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> &mdash; mangle names in the toplevel scope too
(by default we don't do this).
</li>
<li>
<code>--no-seqs</code> &mdash; 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> &mdash; 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> &mdash; 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> &mdash; 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> &mdash; 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> &mdash; 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> &mdash; output some notes on STDERR (for now just how long
each operation takes).
</li>
<li>
<code>--extra</code> &mdash; 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> &mdash; 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() ==&gt; foo+""
</li>
</ul>
</li>
<li>
<code>--max-line-len</code> (default 32K characters) &mdash; 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 &ndash;max-line-len 0 to disable this
safety feature.
</li>
<li>
<code>--reserved-names</code> &mdash; 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 &ndash;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> &ndash; 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> &ndash; generates a new AST containing mangled
(compressed) variable and function names. It supports the following
options:
<ul>
<li>
<code>toplevel</code> &ndash; mangle toplevel names (by default we don't touch them).
</li>
<li>
<code>except</code> &ndash; an array of names to exclude from compression.
</li>
</ul>
</li>
<li>
<code>pro.ast_squeeze(ast, options)</code> &ndash; 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> &ndash; 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> &ndash; 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>) &ndash; 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> &ndash; 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>) &ndash; wether
to put a space before the colon in object literals
</li>
<li>
<code>ascii_only: false</code> &ndash; 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 &ndash; 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 &mdash; 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 &ndash; 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 &ndash; 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 &ndash; 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> &ndash; 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 &lt;mihai.bazon@gmail.com&gt;
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>

View 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

View 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."); }
};

View 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; }
}

View File

@@ -0,0 +1,2 @@
exports.parser = require("./lib/parse-js");
exports.uglify = require("./lib/process");

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View 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;

View 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" },
}

View 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."); }
};

View 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]);
};
};

View File

@@ -0,0 +1 @@
[],Array(1),[1,2,3]

View File

@@ -0,0 +1 @@
(function(){var a=function(){};return new a(1,2,3,4)})()

View File

@@ -0,0 +1 @@
(function(){function a(){}return new a(1,2,3,4)})()

View File

@@ -0,0 +1 @@
(function(){function a(){}(function(){return new a(1,2,3)})()})()

View 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

View File

@@ -0,0 +1 @@
var a=a+"a"+"b"+1+c,b=a+"c"+"ds"+123+c,c=a+"c"+123+d+"ds"+c

View File

@@ -0,0 +1 @@
var a=13,b=1/3

View File

@@ -0,0 +1 @@
function mak(){for(;;);}function foo(){while(bar());}function bar(){return--x}var x=5

View 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)

View File

@@ -0,0 +1 @@
var a=1;a==1?a=2:a=17

View File

@@ -0,0 +1 @@
function a(a){return a==1?2:17}

View File

@@ -0,0 +1 @@
function f(){var a;return(a="a")?a:a}f()

View File

@@ -0,0 +1 @@
new(A,B),new(A||B),new(X?A:B)

View File

@@ -0,0 +1 @@
var a=/^(?:(\w+):)?(?:\/\/(?:(?:([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#])(?::(\d))?)?(..?$|(?:[^?#\/]\/))([^?#]*)(?:\?([^#]))?(?:#(.))?/

View File

@@ -0,0 +1 @@
var a={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"}

View File

@@ -0,0 +1 @@
var a=3250441966

View File

@@ -0,0 +1 @@
var a=function(b){b(),a()}

View File

@@ -0,0 +1 @@
a:1

View File

@@ -0,0 +1 @@
var a=0;switch(a){case 0:a++}

View File

@@ -0,0 +1 @@
label1:{label2:break label2;console.log(1)}

View File

@@ -0,0 +1 @@
(a?b:c)?d:e

View File

@@ -0,0 +1 @@
o={".5":.5},o={.5:.5},o={.5:.5}

View File

@@ -0,0 +1 @@
result=function(){return 1}()

View File

@@ -0,0 +1 @@
var a=8,b=4,c=4

View File

@@ -0,0 +1 @@
var a={};a["this"]=1,a.that=2

View File

@@ -0,0 +1 @@
var a=2e3,b=.002,c=2e-5

View File

@@ -0,0 +1 @@
var s,i;s="",i=0

View File

@@ -0,0 +1 @@
function bar(a){try{foo()}catch(b){alert("Exception caught (foo not defined)")}alert(a)}bar(10)

View File

@@ -0,0 +1 @@
x=(y,z)

View File

@@ -0,0 +1 @@
foo+"",a.toString(16),b.toString.call(c)

View File

@@ -0,0 +1 @@
function f(){function b(){}a||b()}

View File

@@ -0,0 +1 @@
[(a,b)]

View File

@@ -0,0 +1 @@
var a={a:1,b:2}

View File

@@ -0,0 +1 @@
typeof a=="string",b+""!=c+"",d<e==f<g

View File

@@ -0,0 +1 @@
var a=1,b=2

View File

@@ -0,0 +1,3 @@
new Array();
new Array(1);
new Array(1, 2, 3);

View File

@@ -0,0 +1,4 @@
(function(){
var Array = function(){};
return new Array(1, 2, 3, 4);
})();

View File

@@ -0,0 +1,4 @@
(function(){
return new Array(1, 2, 3, 4);
function Array() {};
})();

View File

@@ -0,0 +1,6 @@
(function(){
(function(){
return new Array(1, 2, 3);
})();
function Array(){};
})();

View 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;

View File

@@ -0,0 +1,3 @@
var a = a + "a" + "b" + 1 + c;
var b = a + "c" + "ds" + 123 + c;
var c = a + "c" + 123 + d + "ds" + c;

View File

@@ -0,0 +1,5 @@
// test that the calculation is fold to 13
var a = 1 + 2 * 6;
// test that it isn't replaced with 0.3333 because that is more characters
var b = 1/3;

View File

@@ -0,0 +1,4 @@
var x = 5;
function bar() { return --x; }
function foo() { while (bar()); }
function mak() { for(;;); }

Some files were not shown because too many files have changed in this diff Show More