Compare commits

...

8 Commits
3.0.5 ... 1

Author SHA1 Message Date
Guillermo Rauch
7926d96739 Added missing options. 2012-02-26 16:11:22 -03:00
Guillermo Rauch
54871bcc53 Corrected engine option. 2012-02-26 16:11:08 -03:00
Guillermo Rauch
bf114ddf2e Refactored Makefile. 2012-02-26 16:03:04 -03:00
Guillermo Rauch
300ee104bf Added superagent dep 2012-02-13 20:43:44 -03:00
Guillermo Rauch
d742345480 Updated server 2012-01-31 10:42:02 -08:00
Guillermo Rauch
d9e60e334d New client 2012-01-31 09:49:51 -08:00
Guillermo Rauch
2cd0dd7080 Removed old tests. 2012-01-31 09:49:43 -08:00
Guillermo Rauch
9e467381f3 Started cleanup 2012-01-25 15:18:37 -08:00
68 changed files with 295 additions and 16691 deletions

View File

@@ -1,31 +1,18 @@
ALL_TESTS = $(shell find test/ -name '*.test.js')
ALL_BENCH = $(shell find benchmarks -name '*.bench.js')
run-tests:
@./node_modules/.bin/expresso \
-t 3000 \
-I support \
--serial \
$(TESTFLAGS) \
$(TESTS)
TESTS = test/*.js
BENCHMARKS = $(shell find bench -type f ! -name 'runner.js')
REPORTER = dot
test:
@$(MAKE) NODE_PATH=lib TESTS="$(ALL_TESTS)" run-tests
test-cov:
@TESTFLAGS=--cov $(MAKE) test
test-leaks:
@ls test/leaks/* | xargs node --expose_debug_as=debug --expose_gc
run-bench:
@node $(PROFILEFLAGS) benchmarks/runner.js
@./node_modules/.bin/mocha \
--require test/common \
--reporter $(REPORTER) \
--slow 500ms \
--bail \
--growl \
$(TESTS)
bench:
@$(MAKE) BENCHMARKS="$(ALL_BENCH)" run-bench
@node $(PROFILEFLAGS) bench/runner.js $(BENCHMARKS)
profile:
@PROFILEFLAGS='--prof --trace-opt --trace-bailout --trace-deopt' $(MAKE) bench
.PHONY: test bench profile
.PHONY: test bench

160
Readme.md
View File

@@ -1,31 +1,28 @@
# Socket.IO
Socket.IO is a Node.JS project that makes WebSockets and realtime possible in
all browsers. It also enhances WebSockets by providing built-in multiplexing,
horizontal scalability, automatic JSON encoding/decoding, and more.
Socket.IO is a Node.JS framework for making realtime applications.
It brings the best of WebSockets and other data transport mechanisms for
blazing fast realtime data exchange in web browsers, mobile devices and
servers.
## How to Install
npm install socket.io
```
npm install socket.io
```
## How to use
First, require `socket.io`:
```js
var io = require('socket.io');
```
Next, attach it to a HTTP/HTTPS server. If you're using the fantastic `express`
web framework:
First, require `socket.io` and attach it to a HTTP/HTTPS server:
```js
var app = express.createServer()
, io = io.listen(app);
, io = require('socket.io')(app);
app.listen(80);
io.sockets.on('connection', function (socket) {
io.on('connection', function (socket) {
socket.emit('news', { hello: 'world' });
socket.on('my other event', function (data) {
console.log(data);
@@ -56,18 +53,18 @@ Socket.IO allows you to emit and receive custom events.
Besides `connect`, `message` and `disconnect`, you can emit custom events:
```js
// note, io.listen(<port>) will create a http server for you
var io = require('socket.io').listen(80);
// note, io(<port>) will create a http server for you
var io = require('socket.io')(80);
io.sockets.on('connection', function (socket) {
io.sockets.emit('this', { will: 'be received by everyone' });
io.on('connection', function (socket) {
io.emit('this', { will: 'be received by everyone' });
socket.on('private message', function (from, msg) {
console.log('I received a private message by ', from, ' saying ', msg);
});
socket.on('disconnect', function () {
io.sockets.emit('user disconnected');
io.emit('user disconnected');
});
});
```
@@ -80,9 +77,9 @@ necessary for the duration of the session.
#### Server side
```js
var io = require('socket.io').listen(80);
var io = require('socket.io')(80);
io.sockets.on('connection', function (socket) {
io.on('connection', function (socket) {
socket.on('set nickname', function (name) {
socket.set('nickname', name, function () { socket.emit('ready'); });
});
@@ -111,86 +108,6 @@ io.sockets.on('connection', function (socket) {
</script>
```
### Restricting yourself to a namespace
If you have control over all the messages and events emitted for a particular
application, using the default `/` namespace works.
If you want to leverage 3rd-party code, or produce code to share with others,
socket.io provides a way of namespacing a `socket`.
This has the benefit of `multiplexing` a single connection. Instead of
socket.io using two `WebSocket` connections, it'll use one.
The following example defines a socket that listens on '/chat' and one for
'/news':
#### Server side
```js
var io = require('socket.io').listen(80);
var chat = io
.of('/chat')
.on('connection', function (socket) {
socket.emit('a message', { that: 'only', '/chat': 'will get' });
chat.emit('a message', { everyone: 'in', '/chat': 'will get' });
});
var news = io
.of('/news');
.on('connection', function (socket) {
socket.emit('item', { news: 'item' });
});
```
#### Client side:
```html
<script>
var chat = io.connect('http://localhost/chat')
, news = io.connect('http://localhost/news');
chat.on('connect', function () {
chat.emit('hi!');
});
news.on('news', function () {
news.emit('woot');
});
</script>
```
### Sending volatile messages.
Sometimes certain messages can be dropped. Let's say you have an app that
shows realtime tweets for the keyword `bieber`.
If a certain client is not ready to receive messages (because of network slowness
or other issues, or because he's connected through long polling and is in the
middle of a request-response cycle), if he doesn't receive ALL the tweets related
to bieber your application won't suffer.
In that case, you might want to send those messages as volatile messages.
#### Server side
```js
var io = require('socket.io').listen(80);
io.sockets.on('connection', function (socket) {
var tweets = setInterval(function () {
getBieberTweet(function (tweet) {
socket.volatile.emit('bieber tweet', tweet);
});
}, 100);
socket.on('disconnect', function () {
clearInterval(tweets);
});
});
```
#### Client side
In the client side, messages are received the same way whether they're volatile
@@ -201,16 +118,16 @@ or not.
Sometimes, you might want to get a callback when the client confirmed the message
reception.
To do this, simply pass a function as the last parameter of `.send` or `.emit`.
To do this, simply pass a function as the last parameter of `.emit`.
What's more, when you use `.emit`, the acknowledgement is done by you, which
means you can also pass data along:
#### Server side
```js
var io = require('socket.io').listen(80);
var io = require('socket.io')(80);
io.sockets.on('connection', function (socket) {
io.on('connection', function (socket) {
socket.on('ferret', function (name, fn) {
fn('woot');
});
@@ -239,11 +156,10 @@ that starts it.
#### Server side
```js
var io = require('socket.io').listen(80);
var io = require('socket.io')(80);
io.sockets.on('connection', function (socket) {
io.on('connection', function (socket) {
socket.broadcast.emit('user connected');
socket.broadcast.json.send({ a: 'message' });
});
```
@@ -258,12 +174,12 @@ rooms in each socket.
#### Server side
```js
var io = require('socket.io').listen(80);
var io = require('socket.io')(80);
io.sockets.on('connection', function (socket) {
io.on('connection', function (socket) {
socket.join('justin bieber fans');
socket.broadcast.to('justin bieber fans').emit('new fan');
io.sockets.in('rammstein fans').emit('new non-fan');
io.to('rammstein fans').emit('new non-fan');
});
```
@@ -275,9 +191,9 @@ Simply leverage `send` and listen on the `message` event:
#### Server side
```js
var io = require('socket.io').listen(80);
var io = require('socket.io')(80);
io.sockets.on('connection', function (socket) {
io.on('connection', function (socket) {
socket.on('message', function () { });
socket.on('disconnect', function () { });
});
@@ -298,6 +214,24 @@ io.sockets.on('connection', function (socket) {
</script>
```
### Messaging between clients
Each client joins its own group, identified by the client id, which means you
can leverage `to` to message a particular client:
```js
var io = require('socket.io')(80);
io.on('connection', function (socket) {
socket.on('salutate', function (id) {
io.to(id)
.emit('hi!')
.on('disconnect', function () {
socket.emit('friend disconnected');
});
});
});
```
### Changing configuration
Configuration in socket.io is TJ-style:
@@ -305,7 +239,7 @@ Configuration in socket.io is TJ-style:
#### Server side
```js
var io = require('socket.io').listen(80);
var io = require('socket.io')(80);
io.configure(function () {
io.set('transports', ['websocket', 'flashsocket', 'xhr-polling']);

View File

@@ -1,64 +0,0 @@
/**
* Module dependencies.
*/
var benchmark = require('benchmark')
, colors = require('colors')
, io = require('../')
, parser = io.parser
, suite = new benchmark.Suite('Decode packet');
suite.add('string', function () {
parser.decodePacket('4:::"2"');
});
suite.add('event', function () {
parser.decodePacket('5:::{"name":"woot"}');
});
suite.add('event+ack', function () {
parser.decodePacket('5:1+::{"name":"tobi"}');
});
suite.add('event+data', function () {
parser.decodePacket('5:::{"name":"edwald","args":[{"a": "b"},2,"3"]}');
});
suite.add('heartbeat', function () {
parser.decodePacket('2:::');
});
suite.add('error', function () {
parser.decodePacket('7:::2+0');
});
var payload = parser.encodePayload([
parser.encodePacket({ type: 'message', data: '5', endpoint: '' })
, parser.encodePacket({ type: 'message', data: '53d', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobar', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobarbaz', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobarbazfoobarbaz', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobarbaz', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobar', endpoint: '' })
]);
suite.add('payload', function () {
parser.decodePayload(payload);
});
suite.on('cycle', function (bench, details) {
console.log('\n' + suite.name.grey, details.name.white.bold);
console.log([
details.hz.toFixed(2).cyan + ' ops/sec'.grey
, details.count.toString().white + ' times executed'.grey
, 'benchmark took '.grey + details.times.elapsed.toString().white + ' sec.'.grey
,
].join(', '.grey));
});
if (!module.parent) {
suite.run();
} else {
module.exports = suite;
}

View File

@@ -1,90 +0,0 @@
/**
* Module dependencies.
*/
var benchmark = require('benchmark')
, colors = require('colors')
, io = require('../')
, parser = io.parser
, suite = new benchmark.Suite('Encode packet');
suite.add('string', function () {
parser.encodePacket({
type: 'json'
, endpoint: ''
, data: '2'
});
});
suite.add('event', function () {
parser.encodePacket({
type: 'event'
, name: 'woot'
, endpoint: ''
, args: []
});
});
suite.add('event+ack', function () {
parser.encodePacket({
type: 'json'
, id: 1
, ack: 'data'
, endpoint: ''
, data: { a: 'b' }
});
});
suite.add('event+data', function () {
parser.encodePacket({
type: 'event'
, name: 'edwald'
, endpoint: ''
, args: [{a: 'b'}, 2, '3']
});
});
suite.add('heartbeat', function () {
parser.encodePacket({
type: 'heartbeat'
, endpoint: ''
})
});
suite.add('error', function () {
parser.encodePacket({
type: 'error'
, reason: 'unauthorized'
, advice: 'reconnect'
, endpoint: ''
})
})
suite.add('payload', function () {
parser.encodePayload([
parser.encodePacket({ type: 'message', data: '5', endpoint: '' })
, parser.encodePacket({ type: 'message', data: '53d', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobar', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobarbaz', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobarbazfoobarbaz', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobarbaz', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobar', endpoint: '' })
]);
});
suite.on('cycle', function (bench, details) {
console.log('\n' + suite.name.grey, details.name.white.bold);
console.log([
details.hz.toFixed(2).cyan + ' ops/sec'.grey
, details.count.toString().white + ' times executed'.grey
, 'benchmark took '.grey + details.times.elapsed.toString().white + ' sec.'.grey
,
].join(', '.grey));
});
if (!module.parent) {
suite.run();
} else {
module.exports = suite;
}

View File

@@ -1,55 +0,0 @@
/**
* Benchmark runner dependencies
*/
var colors = require('colors')
, path = require('path');
/**
* Find all the benchmarks
*/
var benchmarks_files = process.env.BENCHMARKS.split(' ')
, all = [].concat(benchmarks_files)
, first = all.shift()
, benchmarks = {};
// find the benchmarks and load them all in our obj
benchmarks_files.forEach(function (file) {
benchmarks[file] = require(path.join(__dirname, '..', file));
});
// setup the complete listeners
benchmarks_files.forEach(function (file) {
var benchmark = benchmarks[file]
, next_file = all.shift()
, next = benchmarks[next_file];
/**
* Generate a oncomplete function for the tests, either we are done or we
* have more benchmarks to process.
*/
function complete () {
if (!next) {
console.log(
'\n\nBenchmark completed in'.grey
, (Date.now() - start).toString().green + ' ms'.grey
);
} else {
console.log('\nStarting benchmark '.grey + next_file.yellow);
next.run();
}
}
// attach the listener
benchmark.on('complete', complete);
});
/**
* Start the benchmark
*/
var start = Date.now();
console.log('Starting benchmark '.grey + first.yellow);
benchmarks[first].run();

View File

@@ -51,10 +51,10 @@ app.listen(3000, function () {
* Socket.IO server (single process only)
*/
var io = sio.listen(app)
var io = sio(app)
, nicknames = {};
io.sockets.on('connection', function (socket) {
io.on('connection', function (socket) {
socket.on('user message', function (msg) {
socket.broadcast.emit('user message', socket.nickname, msg);
});
@@ -66,7 +66,7 @@ io.sockets.on('connection', function (socket) {
fn(false);
nicknames[nick] = socket.nickname = nick;
socket.broadcast.emit('announcement', nick + ' connected');
io.sockets.emit('nicknames', nicknames);
io.emit('nicknames', nicknames);
}
});

View File

@@ -52,7 +52,7 @@ app.listen(3000, function () {
* Socket.IO server
*/
var io = sio.listen(app)
var io = sio(app)
/**
* Connect to IRC.
@@ -64,11 +64,11 @@ client.on('001', function () {
this.send('JOIN', '#node.js');
});
client.on('PART', function (prefix) {
io.sockets.emit('announcement', irc.user(prefix) + ' left the channel');
io.emit('announcement', irc.user(prefix) + ' left the channel');
});
client.on('JOIN', function (prefix) {
io.sockets.emit('announcement', irc.user(prefix) + ' joined the channel');
io.emit('announcement', irc.user(prefix) + ' joined the channel');
});
client.on('PRIVMSG', function (prefix, channel, text) {
io.sockets.emit('irc message', irc.user(prefix), text);
io.emit('irc message', irc.user(prefix), text);
});

View File

@@ -1,8 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
module.exports = require('./lib/socket.io');

View File

@@ -1,97 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var util = require('./util')
, toArray = util.toArray;
/**
* Log levels.
*/
var levels = [
'error'
, 'warn'
, 'info'
, 'debug'
];
/**
* Colors for log levels.
*/
var colors = [
31
, 33
, 36
, 90
];
/**
* Pads the nice output to the longest log level.
*/
function pad (str) {
var max = 0;
for (var i = 0, l = levels.length; i < l; i++)
max = Math.max(max, levels[i].length);
if (str.length < max)
return str + new Array(max - str.length + 1).join(' ');
return str;
};
/**
* Logger (console).
*
* @api public
*/
var Logger = module.exports = function (opts) {
opts = opts || {}
this.colors = false !== opts.colors;
this.level = 3;
this.enabled = true;
};
/**
* Log method.
*
* @api public
*/
Logger.prototype.log = function (type) {
var index = levels.indexOf(type);
if (index > this.level || !this.enabled)
return this;
console.log.apply(
console
, [this.colors
? ' \033[' + colors[index] + 'm' + pad(type) + ' -\033[39m'
: type + ':'
].concat(toArray(arguments).slice(1))
);
return this;
};
/**
* Generate methods.
*/
levels.forEach(function (name) {
Logger.prototype[name] = function () {
this.log.apply(this, [name].concat(toArray(arguments)));
};
});

View File

@@ -1,968 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var fs = require('fs')
, url = require('url')
, tty = require('tty')
, util = require('./util')
, store = require('./store')
, client = require('socket.io-client')
, transports = require('./transports')
, Logger = require('./logger')
, Socket = require('./socket')
, MemoryStore = require('./stores/memory')
, SocketNamespace = require('./namespace')
, Static = require('./static')
, EventEmitter = process.EventEmitter;
/**
* Export the constructor.
*/
exports = module.exports = Manager;
/**
* Default transports.
*/
var defaultTransports = exports.defaultTransports = [
'websocket'
, 'htmlfile'
, 'xhr-polling'
, 'jsonp-polling'
];
/**
* Inherited defaults.
*/
var parent = module.parent.exports
, protocol = parent.protocol;
/**
* Manager constructor.
*
* @param {HTTPServer} server
* @param {Object} options, optional
* @api public
*/
function Manager (server, options) {
this.server = server;
this.namespaces = {};
this.sockets = this.of('');
this.settings = {
origins: '*:*'
, log: true
, store: new MemoryStore
, logger: new Logger
, static: new Static(this)
, heartbeats: true
, resource: '/socket.io'
, transports: defaultTransports
, authorization: false
, blacklist: ['disconnect']
, 'log level': 3
, 'log colors': tty.isatty(process.stdout.fd)
, 'close timeout': 25
, 'heartbeat timeout': 15
, 'heartbeat interval': 20
, 'polling duration': 20
, 'flash policy server': true
, 'flash policy port': 10843
, 'destroy upgrade': true
, 'destroy buffer size': 10E7
, 'browser client': true
, 'browser client cache': true
, 'browser client minification': false
, 'browser client etag': false
, 'browser client expires': 315360000
, 'browser client gzip': false
, 'browser client handler': false
, 'client store expiration': 15
, 'match origin protocol': false
};
for (var i in options) {
this.settings[i] = options[i];
}
var self = this;
// default error handler
server.on('error', function(err) {
self.log.warn('error raised: ' + err);
});
this.initStore();
this.on('set:store', function() {
self.initStore();
});
// reset listeners
this.oldListeners = server.listeners('request');
server.removeAllListeners('request');
server.on('request', function (req, res) {
self.handleRequest(req, res);
});
server.on('upgrade', function (req, socket, head) {
self.handleUpgrade(req, socket, head);
});
server.on('close', function () {
clearInterval(self.gc);
});
server.once('listening', function () {
self.gc = setInterval(self.garbageCollection.bind(self), 10000);
});
for (var i in transports) {
if (transports[i].init) {
transports[i].init(this);
}
}
this.log.info('socket.io started');
};
Manager.prototype.__proto__ = EventEmitter.prototype
/**
* Store accessor shortcut.
*
* @api public
*/
Manager.prototype.__defineGetter__('store', function () {
var store = this.get('store');
store.manager = this;
return store;
});
/**
* Logger accessor.
*
* @api public
*/
Manager.prototype.__defineGetter__('log', function () {
var logger = this.get('logger');
logger.level = this.get('log level') || -1;
logger.colors = this.get('log colors');
logger.enabled = this.enabled('log');
return logger;
});
/**
* Static accessor.
*
* @api public
*/
Manager.prototype.__defineGetter__('static', function () {
return this.get('static');
});
/**
* Get settings.
*
* @api public
*/
Manager.prototype.get = function (key) {
return this.settings[key];
};
/**
* Set settings
*
* @api public
*/
Manager.prototype.set = function (key, value) {
if (arguments.length == 1) return this.get(key);
this.settings[key] = value;
this.emit('set:' + key, this.settings[key], key);
return this;
};
/**
* Enable a setting
*
* @api public
*/
Manager.prototype.enable = function (key) {
this.settings[key] = true;
this.emit('set:' + key, this.settings[key], key);
return this;
};
/**
* Disable a setting
*
* @api public
*/
Manager.prototype.disable = function (key) {
this.settings[key] = false;
this.emit('set:' + key, this.settings[key], key);
return this;
};
/**
* Checks if a setting is enabled
*
* @api public
*/
Manager.prototype.enabled = function (key) {
return !!this.settings[key];
};
/**
* Checks if a setting is disabled
*
* @api public
*/
Manager.prototype.disabled = function (key) {
return !this.settings[key];
};
/**
* Configure callbacks.
*
* @api public
*/
Manager.prototype.configure = function (env, fn) {
if ('function' == typeof env) {
env.call(this);
} else if (env == process.env.NODE_ENV) {
fn.call(this);
}
return this;
};
/**
* Initializes everything related to the message dispatcher.
*
* @api private
*/
Manager.prototype.initStore = function () {
this.handshaken = {};
this.connected = {};
this.open = {};
this.closed = {};
this.rooms = {};
this.roomClients = {};
var self = this;
this.store.subscribe('handshake', function (id, data) {
self.onHandshake(id, data);
});
this.store.subscribe('connect', function (id) {
self.onConnect(id);
});
this.store.subscribe('open', function (id) {
self.onOpen(id);
});
this.store.subscribe('join', function (id, room) {
self.onJoin(id, room);
});
this.store.subscribe('leave', function (id, room) {
self.onLeave(id, room);
});
this.store.subscribe('close', function (id) {
self.onClose(id);
});
this.store.subscribe('dispatch', function (room, packet, volatile, exceptions) {
self.onDispatch(room, packet, volatile, exceptions);
});
this.store.subscribe('disconnect', function (id) {
self.onDisconnect(id);
});
};
/**
* Called when a client handshakes.
*
* @param text
*/
Manager.prototype.onHandshake = function (id, data) {
this.handshaken[id] = data;
};
/**
* Called when a client connects (ie: transport first opens)
*
* @api private
*/
Manager.prototype.onConnect = function (id) {
this.connected[id] = true;
};
/**
* Called when a client opens a request in a different node.
*
* @api private
*/
Manager.prototype.onOpen = function (id) {
this.open[id] = true;
// if we were buffering messages for the client, clear them
if (this.closed[id]) {
var self = this;
this.store.unsubscribe('dispatch:' + id, function () {
delete self.closed[id];
});
}
// clear the current transport
if (this.transports[id]) {
this.transports[id].discard();
this.transports[id] = null;
}
};
/**
* Called when a message is sent to a namespace and/or room.
*
* @api private
*/
Manager.prototype.onDispatch = function (room, packet, volatile, exceptions) {
if (this.rooms[room]) {
for (var i = 0, l = this.rooms[room].length; i < l; i++) {
var id = this.rooms[room][i];
if (!~exceptions.indexOf(id)) {
if (this.transports[id] && this.transports[id].open) {
this.transports[id].onDispatch(packet, volatile);
} else if (!volatile) {
this.onClientDispatch(id, packet);
}
}
}
}
};
/**
* Called when a client joins a nsp / room.
*
* @api private
*/
Manager.prototype.onJoin = function (id, name) {
if (!this.roomClients[id]) {
this.roomClients[id] = {};
}
if (!this.rooms[name]) {
this.rooms[name] = [];
}
if (!~this.rooms[name].indexOf(id)) {
this.rooms[name].push(id);
this.roomClients[id][name] = true;
}
};
/**
* Called when a client leaves a nsp / room.
*
* @param private
*/
Manager.prototype.onLeave = function (id, room) {
if (this.rooms[room]) {
var index = this.rooms[room].indexOf(id);
if (index >= 0) {
this.rooms[room].splice(index, 1);
}
if (!this.rooms[room].length) {
delete this.rooms[room];
}
delete this.roomClients[id][room];
}
};
/**
* Called when a client closes a request in different node.
*
* @api private
*/
Manager.prototype.onClose = function (id) {
if (this.open[id]) {
delete this.open[id];
}
this.closed[id] = [];
var self = this;
this.store.subscribe('dispatch:' + id, function (packet, volatile) {
if (!volatile) {
self.onClientDispatch(id, packet);
}
});
};
/**
* Dispatches a message for a closed client.
*
* @api private
*/
Manager.prototype.onClientDispatch = function (id, packet) {
if (this.closed[id]) {
this.closed[id].push(packet);
}
};
/**
* Receives a message for a client.
*
* @api private
*/
Manager.prototype.onClientMessage = function (id, packet) {
if (this.namespaces[packet.endpoint]) {
this.namespaces[packet.endpoint].handlePacket(id, packet);
}
};
/**
* Fired when a client disconnects (not triggered).
*
* @api private
*/
Manager.prototype.onClientDisconnect = function (id, reason) {
for (var name in this.namespaces) {
this.namespaces[name].handleDisconnect(id, reason, typeof this.roomClients[id] !== 'undefined' &&
typeof this.roomClients[id][name] !== 'undefined');
}
this.onDisconnect(id);
};
/**
* Called when a client disconnects.
*
* @param text
*/
Manager.prototype.onDisconnect = function (id, local) {
delete this.handshaken[id];
if (this.open[id]) {
delete this.open[id];
}
if (this.connected[id]) {
delete this.connected[id];
}
if (this.transports[id]) {
this.transports[id].discard();
delete this.transports[id];
}
if (this.closed[id]) {
delete this.closed[id];
}
if (this.roomClients[id]) {
for (var room in this.roomClients[id]) {
this.onLeave(id, room);
}
delete this.roomClients[id]
}
this.store.destroyClient(id, this.get('client store expiration'));
this.store.unsubscribe('dispatch:' + id);
if (local) {
this.store.unsubscribe('message:' + id);
this.store.unsubscribe('disconnect:' + id);
}
};
/**
* Handles an HTTP request.
*
* @api private
*/
Manager.prototype.handleRequest = function (req, res) {
var data = this.checkRequest(req);
if (!data) {
for (var i = 0, l = this.oldListeners.length; i < l; i++) {
this.oldListeners[i].call(this.server, req, res);
}
return;
}
if (data.static || !data.transport && !data.protocol) {
if (data.static && this.enabled('browser client')) {
this.static.write(data.path, req, res);
} else {
res.writeHead(200);
res.end('Welcome to socket.io.');
this.log.info('unhandled socket.io url');
}
return;
}
if (data.protocol != protocol) {
res.writeHead(500);
res.end('Protocol version not supported.');
this.log.info('client protocol version unsupported');
} else {
if (data.id) {
this.handleHTTPRequest(data, req, res);
} else {
this.handleHandshake(data, req, res);
}
}
};
/**
* Handles an HTTP Upgrade.
*
* @api private
*/
Manager.prototype.handleUpgrade = function (req, socket, head) {
var data = this.checkRequest(req)
, self = this;
if (!data) {
if (this.enabled('destroy upgrade')) {
socket.end();
this.log.debug('destroying non-socket.io upgrade');
}
return;
}
req.head = head;
this.handleClient(data, req);
};
/**
* Handles a normal handshaken HTTP request (eg: long-polling)
*
* @api private
*/
Manager.prototype.handleHTTPRequest = function (data, req, res) {
req.res = res;
this.handleClient(data, req);
};
/**
* Intantiantes a new client.
*
* @api private
*/
Manager.prototype.handleClient = function (data, req) {
var socket = req.socket
, store = this.store
, self = this;
if (undefined != data.query.disconnect) {
if (this.transports[data.id] && this.transports[data.id].open) {
this.transports[data.id].onForcedDisconnect();
} else {
this.store.publish('disconnect-force:' + data.id);
}
return;
}
if (!~this.get('transports').indexOf(data.transport)) {
this.log.warn('unknown transport: "' + data.transport + '"');
req.connection.end();
return;
}
var transport = new transports[data.transport](this, data, req)
, handshaken = this.handshaken[data.id];
if (transport.disconnected) {
// failed during transport setup
req.connection.end();
return;
}
if (handshaken) {
if (transport.open) {
if (this.closed[data.id] && this.closed[data.id].length) {
transport.payload(this.closed[data.id]);
this.closed[data.id] = [];
}
this.onOpen(data.id);
this.store.publish('open', data.id);
this.transports[data.id] = transport;
}
if (!this.connected[data.id]) {
this.onConnect(data.id);
this.store.publish('connect', data.id);
// flag as used
delete handshaken.issued;
this.onHandshake(data.id, handshaken);
this.store.publish('handshake', data.id, handshaken);
// initialize the socket for all namespaces
for (var i in this.namespaces) {
var socket = this.namespaces[i].socket(data.id, true);
// echo back connect packet and fire connection event
if (i === '') {
this.namespaces[i].handlePacket(data.id, { type: 'connect' });
}
}
this.store.subscribe('message:' + data.id, function (packet) {
self.onClientMessage(data.id, packet);
});
this.store.subscribe('disconnect:' + data.id, function (reason) {
self.onClientDisconnect(data.id, reason);
});
}
} else {
if (transport.open) {
transport.error('client not handshaken', 'reconnect');
}
transport.discard();
}
};
/**
* Generates a session id.
*
* @api private
*/
Manager.prototype.generateId = function () {
return Math.abs(Math.random() * Math.random() * Date.now() | 0).toString()
+ Math.abs(Math.random() * Math.random() * Date.now() | 0).toString();
};
/**
* Handles a handshake request.
*
* @api private
*/
Manager.prototype.handleHandshake = function (data, req, res) {
var self = this
, origin = req.headers.origin
, headers = {
'Content-Type': 'text/plain'
};
function writeErr (status, message) {
if (data.query.jsonp) {
res.writeHead(200, { 'Content-Type': 'application/javascript' });
res.end('io.j[' + data.query.jsonp + '](new Error("' + message + '"));');
} else {
res.writeHead(status, headers);
res.end(message);
}
};
function error (err) {
writeErr(500, 'handshake error');
self.log.warn('handshake error ' + err);
};
if (!this.verifyOrigin(req)) {
writeErr(403, 'handshake bad origin');
return;
}
var handshakeData = this.handshakeData(data);
if (origin) {
// https://developer.mozilla.org/En/HTTP_Access_Control
headers['Access-Control-Allow-Origin'] = origin;
headers['Access-Control-Allow-Credentials'] = 'true';
}
this.authorize(handshakeData, function (err, authorized, newData) {
if (err) return error(err);
if (authorized) {
var id = self.generateId()
, hs = [
id
, self.enabled('heartbeats') ? self.get('heartbeat timeout') || '' : ''
, self.get('close timeout') || ''
, self.transports(data).join(',')
].join(':');
if (data.query.jsonp) {
hs = 'io.j[' + data.query.jsonp + '](' + JSON.stringify(hs) + ');';
res.writeHead(200, { 'Content-Type': 'application/javascript' });
} else {
res.writeHead(200, headers);
}
res.end(hs);
self.onHandshake(id, newData || handshakeData);
self.store.publish('handshake', id, newData || handshakeData);
self.log.info('handshake authorized', id);
} else {
writeErr(403, 'handshake unauthorized');
self.log.info('handshake unauthorized');
}
})
};
/**
* Gets normalized handshake data
*
* @api private
*/
Manager.prototype.handshakeData = function (data) {
var connection = data.request.connection
, connectionAddress
, date = new Date;
if (connection.remoteAddress) {
connectionAddress = {
address: connection.remoteAddress
, port: connection.remotePort
};
} else if (connection.socket && connection.socket.remoteAddress) {
connectionAddress = {
address: connection.socket.remoteAddress
, port: connection.socket.remotePort
};
}
return {
headers: data.headers
, address: connectionAddress
, time: date.toString()
, query: data.query
, url: data.request.url
, xdomain: !!data.request.headers.origin
, secure: data.request.connection.secure
, issued: +date
};
};
/**
* Verifies the origin of a request.
*
* @api private
*/
Manager.prototype.verifyOrigin = function (request) {
var origin = request.headers.origin || request.headers.referer
, origins = this.get('origins');
if (origin === 'null') origin = '*';
if (origins.indexOf('*:*') !== -1) {
return true;
}
if (origin) {
try {
var parts = url.parse(origin);
parts.port = parts.port || 80;
var ok =
~origins.indexOf(parts.hostname + ':' + parts.port) ||
~origins.indexOf(parts.hostname + ':*') ||
~origins.indexOf('*:' + parts.port);
if (!ok) this.log.warn('illegal origin: ' + origin);
return ok;
} catch (ex) {
this.log.warn('error parsing origin');
}
}
else {
this.log.warn('origin missing from handshake, yet required by config');
}
return false;
};
/**
* Handles an incoming packet.
*
* @api private
*/
Manager.prototype.handlePacket = function (sessid, packet) {
this.of(packet.endpoint || '').handlePacket(sessid, packet);
};
/**
* Performs authentication.
*
* @param Object client request data
* @api private
*/
Manager.prototype.authorize = function (data, fn) {
if (this.get('authorization')) {
var self = this;
this.get('authorization').call(this, data, function (err, authorized) {
self.log.debug('client ' + authorized ? 'authorized' : 'unauthorized');
fn(err, authorized);
});
} else {
this.log.debug('client authorized');
fn(null, true);
}
return this;
};
/**
* Retrieves the transports adviced to the user.
*
* @api private
*/
Manager.prototype.transports = function (data) {
var transp = this.get('transports')
, ret = [];
for (var i = 0, l = transp.length; i < l; i++) {
var transport = transp[i];
if (transport) {
if (!transport.checkClient || transport.checkClient(data)) {
ret.push(transport);
}
}
}
return ret;
};
/**
* Checks whether a request is a socket.io one.
*
* @return {Object} a client request data object or `false`
* @api private
*/
var regexp = /^\/([^\/]+)\/?([^\/]+)?\/?([^\/]+)?\/?$/
Manager.prototype.checkRequest = function (req) {
var resource = this.get('resource');
if (req.url.substr(0, resource.length) == resource) {
var uri = url.parse(req.url.substr(resource.length), true)
, path = uri.pathname || ''
, pieces = path.match(regexp);
// client request data
var data = {
query: uri.query || {}
, headers: req.headers
, request: req
, path: path
};
if (pieces) {
data.protocol = Number(pieces[1]);
data.transport = pieces[2];
data.id = pieces[3];
data.static = !!this.static.has(path);
};
return data;
}
return false;
};
/**
* Declares a socket namespace
*
* @api public
*/
Manager.prototype.of = function (nsp) {
if (this.namespaces[nsp]) {
return this.namespaces[nsp];
}
return this.namespaces[nsp] = new SocketNamespace(this, nsp);
};
/**
* Perform garbage collection on long living objects and properties that cannot
* be removed automatically.
*
* @api private
*/
Manager.prototype.garbageCollection = function () {
// clean up unused handshakes
var ids = Object.keys(this.handshaken)
, i = ids.length
, now = Date.now()
, handshake;
while (i--) {
handshake = this.handshaken[ids[i]];
if ('issued' in handshake && (now - handshake.issued) >= 3E4) {
this.onDisconnect(ids[i]);
}
}
};

View File

@@ -1,355 +0,0 @@
/**
* Module dependencies.
*/
var Socket = require('./socket')
, EventEmitter = process.EventEmitter
, parser = require('./parser')
, util = require('./util');
/**
* Exports the constructor.
*/
exports = module.exports = SocketNamespace;
/**
* Constructor.
*
* @api public.
*/
function SocketNamespace (mgr, name) {
this.manager = mgr;
this.name = name || '';
this.sockets = {};
this.auth = false;
this.setFlags();
};
/**
* Inherits from EventEmitter.
*/
SocketNamespace.prototype.__proto__ = EventEmitter.prototype;
/**
* Copies emit since we override it.
*
* @api private
*/
SocketNamespace.prototype.$emit = EventEmitter.prototype.emit;
/**
* Retrieves all clients as Socket instances as an array.
*
* @api public
*/
SocketNamespace.prototype.clients = function (room) {
var room = this.name + (room !== undefined ?
'/' + room : '');
if (!this.manager.rooms[room]) {
return [];
}
return this.manager.rooms[room].map(function (id) {
return this.socket(id);
}, this);
};
/**
* Access logger interface.
*
* @api public
*/
SocketNamespace.prototype.__defineGetter__('log', function () {
return this.manager.log;
});
/**
* Access store.
*
* @api public
*/
SocketNamespace.prototype.__defineGetter__('store', function () {
return this.manager.store;
});
/**
* JSON message flag.
*
* @api public
*/
SocketNamespace.prototype.__defineGetter__('json', function () {
this.flags.json = true;
return this;
});
/**
* Volatile message flag.
*
* @api public
*/
SocketNamespace.prototype.__defineGetter__('volatile', function () {
this.flags.volatile = true;
return this;
});
/**
* Overrides the room to relay messages to (flag).
*
* @api public
*/
SocketNamespace.prototype.in = SocketNamespace.prototype.to = function (room) {
this.flags.endpoint = this.name + (room ? '/' + room : '');
return this;
};
/**
* Adds a session id we should prevent relaying messages to (flag).
*
* @api public
*/
SocketNamespace.prototype.except = function (id) {
this.flags.exceptions.push(id);
return this;
};
/**
* Sets the default flags.
*
* @api private
*/
SocketNamespace.prototype.setFlags = function () {
this.flags = {
endpoint: this.name
, exceptions: []
};
return this;
};
/**
* Sends out a packet.
*
* @api private
*/
SocketNamespace.prototype.packet = function (packet) {
packet.endpoint = this.name;
var store = this.store
, log = this.log
, volatile = this.flags.volatile
, exceptions = this.flags.exceptions
, packet = parser.encodePacket(packet);
this.manager.onDispatch(this.flags.endpoint, packet, volatile, exceptions);
this.store.publish('dispatch', this.flags.endpoint, packet, volatile, exceptions);
this.setFlags();
return this;
};
/**
* Sends to everyone.
*
* @api public
*/
SocketNamespace.prototype.send = function (data) {
return this.packet({
type: this.flags.json ? 'json' : 'message'
, data: data
});
};
/**
* Emits to everyone (override).
*
* @api public
*/
SocketNamespace.prototype.emit = function (name) {
if (name == 'newListener') {
return this.$emit.apply(this, arguments);
}
return this.packet({
type: 'event'
, name: name
, args: util.toArray(arguments).slice(1)
});
};
/**
* Retrieves or creates a write-only socket for a client, unless specified.
*
* @param {Boolean} whether the socket will be readable when initialized
* @api public
*/
SocketNamespace.prototype.socket = function (sid, readable) {
if (!this.sockets[sid]) {
this.sockets[sid] = new Socket(this.manager, sid, this, readable);
}
return this.sockets[sid];
};
/**
* Sets authorization for this namespace.
*
* @api public
*/
SocketNamespace.prototype.authorization = function (fn) {
this.auth = fn;
return this;
};
/**
* Called when a socket disconnects entirely.
*
* @api private
*/
SocketNamespace.prototype.handleDisconnect = function (sid, reason, raiseOnDisconnect) {
if (this.sockets[sid] && this.sockets[sid].readable) {
if (raiseOnDisconnect) this.sockets[sid].onDisconnect(reason);
delete this.sockets[sid];
}
};
/**
* Performs authentication.
*
* @param Object client request data
* @api private
*/
SocketNamespace.prototype.authorize = function (data, fn) {
if (this.auth) {
var self = this;
this.auth.call(this, data, function (err, authorized) {
self.log.debug('client ' +
(authorized ? '' : 'un') + 'authorized for ' + self.name);
fn(err, authorized);
});
} else {
this.log.debug('client authorized for ' + this.name);
fn(null, true);
}
return this;
};
/**
* Handles a packet.
*
* @api private
*/
SocketNamespace.prototype.handlePacket = function (sessid, packet) {
var socket = this.socket(sessid)
, dataAck = packet.ack == 'data'
, manager = this.manager
, self = this;
function ack () {
self.log.debug('sending data ack packet');
socket.packet({
type: 'ack'
, args: util.toArray(arguments)
, ackId: packet.id
});
};
function error (err) {
self.log.warn('handshake error ' + err + ' for ' + self.name);
socket.packet({ type: 'error', reason: err });
};
function connect () {
self.manager.onJoin(sessid, self.name);
self.store.publish('join', sessid, self.name);
// packet echo
socket.packet({ type: 'connect' });
// emit connection event
self.$emit('connection', socket);
};
switch (packet.type) {
case 'connect':
if (packet.endpoint == '') {
connect();
} else {
var handshakeData = manager.handshaken[sessid];
this.authorize(handshakeData, function (err, authorized, newData) {
if (err) return error(err);
if (authorized) {
manager.onHandshake(sessid, newData || handshakeData);
self.store.publish('handshake', sessid, newData || handshakeData);
connect();
} else {
error('unauthorized');
}
});
}
break;
case 'ack':
if (socket.acks[packet.ackId]) {
socket.acks[packet.ackId].apply(socket, packet.args);
} else {
this.log.info('unknown ack packet');
}
break;
case 'event':
// check if the emitted event is not blacklisted
if (-~manager.get('blacklist').indexOf(packet.name)) {
this.log.debug('ignoring blacklisted event `' + packet.name + '`');
} else {
var params = [packet.name].concat(packet.args);
if (dataAck) {
params.push(ack);
}
socket.$emit.apply(socket, params);
}
break;
case 'disconnect':
this.manager.onLeave(sessid, this.name);
this.store.publish('leave', sessid, this.name);
socket.$emit('disconnect', packet.reason || 'packet');
break;
case 'json':
case 'message':
var params = ['message', packet.data];
if (dataAck)
params.push(ack);
socket.$emit.apply(socket, params);
};
};

View File

@@ -1,249 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module dependencies.
*/
/**
* Packet types.
*/
var packets = exports.packets = {
'disconnect': 0
, 'connect': 1
, 'heartbeat': 2
, 'message': 3
, 'json': 4
, 'event': 5
, 'ack': 6
, 'error': 7
, 'noop': 8
}
, packetslist = Object.keys(packets);
/**
* Errors reasons.
*/
var reasons = exports.reasons = {
'transport not supported': 0
, 'client not handshaken': 1
, 'unauthorized': 2
}
, reasonslist = Object.keys(reasons);
/**
* Errors advice.
*/
var advice = exports.advice = {
'reconnect': 0
}
, advicelist = Object.keys(advice);
/**
* Encodes a packet.
*
* @api private
*/
exports.encodePacket = function (packet) {
var type = packets[packet.type]
, id = packet.id || ''
, endpoint = packet.endpoint || ''
, ack = packet.ack
, data = null;
switch (packet.type) {
case 'message':
if (packet.data !== '')
data = packet.data;
break;
case 'event':
var ev = { name: packet.name };
if (packet.args && packet.args.length) {
ev.args = packet.args;
}
data = JSON.stringify(ev);
break;
case 'json':
data = JSON.stringify(packet.data);
break;
case 'ack':
data = packet.ackId
+ (packet.args && packet.args.length
? '+' + JSON.stringify(packet.args) : '');
break;
case 'connect':
if (packet.qs)
data = packet.qs;
break;
case 'error':
var reason = packet.reason ? reasons[packet.reason] : ''
, adv = packet.advice ? advice[packet.advice] : ''
if (reason !== '' || adv !== '')
data = reason + (adv !== '' ? ('+' + adv) : '')
break;
}
// construct packet with required fragments
var encoded = type + ':' + id + (ack == 'data' ? '+' : '') + ':' + endpoint;
// data fragment is optional
if (data !== null && data !== undefined)
encoded += ':' + data;
return encoded;
};
/**
* Encodes multiple messages (payload).
*
* @param {Array} messages
* @api private
*/
exports.encodePayload = function (packets) {
var decoded = '';
if (packets.length == 1)
return packets[0];
for (var i = 0, l = packets.length; i < l; i++) {
var packet = packets[i];
decoded += '\ufffd' + packet.length + '\ufffd' + packets[i]
}
return decoded;
};
/**
* Decodes a packet
*
* @api private
*/
var regexp = /([^:]+):([0-9]+)?(\+)?:([^:]+)?:?([\s\S]*)?/;
/**
* Wrap the JSON.parse in a seperate function the crankshaft optimizer will
* only punish this function for the usage for try catch
*
* @api private
*/
function parse (data) {
try { return JSON.parse(data) }
catch (e) { return false }
}
exports.decodePacket = function (data) {
var pieces = data.match(regexp);
if (!pieces) return {};
var id = pieces[2] || ''
, data = pieces[5] || ''
, packet = {
type: packetslist[pieces[1]]
, endpoint: pieces[4] || ''
};
// whether we need to acknowledge the packet
if (id) {
packet.id = id;
if (pieces[3])
packet.ack = 'data';
else
packet.ack = true;
}
// handle different packet types
switch (packet.type) {
case 'message':
packet.data = data || '';
break;
case 'event':
pieces = parse(data);
if (pieces) {
packet.name = pieces.name;
packet.args = pieces.args;
}
packet.args = packet.args || [];
break;
case 'json':
packet.data = parse(data);
break;
case 'connect':
packet.qs = data || '';
break;
case 'ack':
pieces = data.match(/^([0-9]+)(\+)?(.*)/);
if (pieces) {
packet.ackId = pieces[1];
packet.args = [];
if (pieces[3]) {
packet.args = parse(pieces[3]) || [];
}
}
break;
case 'error':
pieces = data.split('+');
packet.reason = reasonslist[pieces[0]] || '';
packet.advice = advicelist[pieces[1]] || '';
}
return packet;
};
/**
* Decodes data payload. Detects multiple messages
*
* @return {Array} messages
* @api public
*/
exports.decodePayload = function (data) {
if (undefined == data || null == data) {
return [];
}
if (data[0] == '\ufffd') {
var ret = [];
for (var i = 1, length = ''; i < data.length; i++) {
if (data[i] == '\ufffd') {
ret.push(exports.decodePacket(data.substr(i + 1, length)));
i += Number(length) + 1;
length = '';
} else {
length += data[i];
}
}
return ret;
} else {
return [exports.decodePacket(data)];
}
};

76
lib/server.js Normal file
View File

@@ -0,0 +1,76 @@
/**
* Socket.IO server.
*
* @param {Object} options
* @api public
*/
function Server (opts) {
this.clients = {};
this.clientsCount = 0;
this.flags = {};
// legacy
this.sockets = this;
}
/**
* Broadcast flag.
*
* @api public
*/
Server.prototype.__defineGetter__('broadcast', function () {
this.flags.broadcast = true;
});
/**
* Called with a websocket.io-compatible connection.
*
* @param {engine.Socket|wsio.Socket} connection
* @api public
*/
Server.prototype.onConnection = function (conn) {
var socket = new Socket(conn, this)
, self = this
socket.once('ready', function () {
self.clients[socket.id] = socket;
self.emit('connection', socket):
});
};
/**
* Gets a client.
*
* @api public
*/
Server.prototype.socket =
Server.prototype.client = function (id) {
return this.clients[id];
};
/**
* Emits to all clients.
*
* @api private
*/
Server.prototype.emit = function () {
};
/**
* Sets the room to broadcast to.
*
* @param {String} room name
* @api public
*/
Server.prototype.to =
Server.prototype.in = function () {
};

View File

@@ -1,136 +1,116 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var client = require('socket.io-client');
var engine = require('engine.io')
, Server = require('http').Server
/**
* Version.
* Module exports.
*/
exports.version = '0.8.7';
module.exports = exports = create;
/**
* Supported protocol version.
*/
exports.protocol = 1;
/**
* Client that we serve.
*/
exports.clientVersion = client.version;
/**
* Attaches a manager
* Creates a Socket.IO server.
*
* @param {HTTPServer/Number} a HTTP/S server or a port number to listen on.
* @param {Object} opts to be passed to Manager and/or http server
* @param {Function} callback if a port is supplied
* @api public
*/
exports.listen = function (server, options, fn) {
if ('function' == typeof options) {
fn = options;
options = {};
function create (arg, options, fn) {
if ('number' == typeof arg) {
return exports.listen(arg, options, fn);
} else {
return exports.attach(arg, options, fn);
}
if ('undefined' == typeof server) {
// create a server that listens on port 80
server = 80;
}
if ('number' == typeof server) {
// if a port number is passed
var port = server;
if (options && options.key)
server = require('https').createServer(options);
else
server = require('http').createServer();
// default response
server.on('request', function (req, res) {
res.writeHead(200);
res.end('Welcome to socket.io.');
});
server.listen(port, fn);
}
// otherwise assume a http/s server
return new exports.Manager(server, options);
};
/**
* Manager constructor.
* Version
*
* @api public
*/
exports.Manager = require('./manager');
exports.version = '1.0.0-alpha1';
/**
* Transport constructor.
* Server constructor.
*
* @api public
* @api private
*/
exports.Transport = require('./transport');
exports.Server = Server;
/**
* Socket constructor.
* Listen shortcut.
*
* @api public
*/
exports.Socket = require('./socket');
exports.create = create;
/**
* Static constructor.
* Makes socket.io listen on a port.
*
* @param {Number} port
* @param {Object|Function} (optional) options or callback
* @param {Function} (optional) callback
* @return {Server} io
* @api public
*/
exports.Static = require('./static');
exports.listen = function (port, fn, options) {
// legacy
if (port instanceof Server) return attach(port, fn, opts);
if ('object' == typeof fn) {
options = fn;
fn = null;
}
var server = http.createServer(function (req, res) {
res.writeHead(501);
res.end('Not Implemented');
});
server.listen(port, fn);
// create socket.io server
var io = exports.attach(server, options);
// keep ref to http server
io.httpServer = server;
return io;
};
/**
* Store constructor.
* Attaches socket.io to a http server.
*
* @param {http.Server} server
* @param {Object} (optional) options
* @return {Server} io server
* @api public
*/
exports.Store = require('./store');
exports.attach = function (server, options) {
var opts = options || {}
, engineOpts = opts.engine || {}
/**
* Memory Store constructor.
*
* @api public
*/
// use default socket.io base path
engineOpts.path = engineOpts.path || '/socket.io';
exports.MemoryStore = require('./stores/memory');
// spawn engine server
var server = engine.attach(server, engineOpts);
/**
* Redis Store constructor.
*
* @api public
*/
// spawn socket.io
var io = new exports.Server(options);
exports.RedisStore = require('./stores/redis');
// capture connections
server.on('connection', function (conn) {
io.onConnection(conn);
});
/**
* Parser.
*
* @api public
*/
exports.parser = require('./parser');
return io;
};

View File

@@ -1,52 +1,31 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var parser = require('./parser')
, util = require('./util')
, EventEmitter = process.EventEmitter
var EventEmitter = require('events').EventEmitter;
/**
* Export the constructor.
*/
exports = module.exports = Socket;
/**
* Default error event listener to prevent uncaught exceptions.
*/
var defaultError = function () {};
/**
* Socket constructor.
* Socket.
*
* @param {Manager} manager instance
* @param {String} session id
* @param {Namespace} namespace the socket belongs to
* @param {Boolean} whether the
* @api public
* @api private
*/
function Socket (manager, id, nsp, readable) {
this.id = id;
this.namespace = nsp;
this.manager = manager;
this.disconnected = false;
this.ackPackets = 0;
this.acks = {};
this.setFlags();
this.readable = readable;
this.store = this.manager.store.client(this.id);
this.on('error', defaultError);
};
function Socket (connection, server) {
this.connection = connection;
this.id = this.sid = connection.id;
this.server = server;
this.store = this.server.store;
// group subscriptions
this.subscriptions = [];
// join to group for itself
var self = this;
this.join(sid, function () {
self.emit('ready');
});
}
/**
* Inherits from EventEmitter.
@@ -55,308 +34,58 @@ function Socket (manager, id, nsp, readable) {
Socket.prototype.__proto__ = EventEmitter.prototype;
/**
* Accessor shortcut for the handshake data
* Save reference to original `emit`.
*
* @api private
*/
Socket.prototype.__defineGetter__('handshake', function () {
return this.manager.handshaken[this.id];
});
Socket.prototype._emit = Socket.prototype.emit;
/**
* Accessor shortcut for the transport type
*
* @api private
*/
Socket.prototype.__defineGetter__('transport', function () {
return this.manager.transports[this.id].name;
});
/**
* Accessor shortcut for the logger.
*
* @api private
*/
Socket.prototype.__defineGetter__('log', function () {
return this.manager.log;
});
/**
* JSON message flag.
* Joins a group.
*
* @param {String} group
* @return {Socket} for chaining
* @api public
*/
Socket.prototype.__defineGetter__('json', function () {
this.flags.json = true;
return this;
});
/**
* Volatile message flag.
*
* @api public
*/
Socket.prototype.__defineGetter__('volatile', function () {
this.flags.volatile = true;
return this;
});
/**
* Broadcast message flag.
*
* @api public
*/
Socket.prototype.__defineGetter__('broadcast', function () {
this.flags.broadcast = true;
return this;
});
/**
* Overrides the room to broadcast messages to (flag)
*
* @api public
*/
Socket.prototype.to = Socket.prototype.in = function (room) {
this.flags.room = room;
return this;
};
/**
* Resets flags
*
* @api private
*/
Socket.prototype.setFlags = function () {
this.flags = {
endpoint: this.namespace.name
, room: ''
};
return this;
};
/**
* Triggered on disconnect
*
* @api private
*/
Socket.prototype.onDisconnect = function (reason) {
if (!this.disconnected) {
this.$emit('disconnect', reason);
this.disconnected = true;
}
};
/**
* Joins a user to a room.
*
* @api public
*/
Socket.prototype.join = function (name, fn) {
var nsp = this.namespace.name
, name = (nsp + '/') + name;
this.manager.onJoin(this.id, name);
this.manager.store.publish('join', this.id, name);
if (fn) {
this.log.warn('Client#join callback is deprecated');
fn();
}
return this;
};
/**
* Un-joins a user from a room.
*
* @api public
*/
Socket.prototype.leave = function (name, fn) {
var nsp = this.namespace.name
, name = (nsp + '/') + name;
this.manager.onLeave(this.id, name);
this.manager.store.publish('leave', this.id, name);
if (fn) {
this.log.warn('Client#leave callback is deprecated');
fn();
}
return this;
};
/**
* Transmits a packet.
*
* @api private
*/
Socket.prototype.packet = function (packet) {
if (this.flags.broadcast) {
this.log.debug('broadcasting packet');
this.namespace.in(this.flags.room).except(this.id).packet(packet);
Socket.prototype.join = function (group, fn) {
if (!~this.subscriptions.indexOf(group)) {
var self = this;
this.subscriptions.push(group);
this.store.addToGroup(group, this.sid, function (ev, args) {
self.onGroupEvent(ev, args);
}, fn);
} else {
packet.endpoint = this.flags.endpoint;
packet = parser.encodePacket(packet);
this.dispatch(packet, this.flags.volatile);
fn && fn();
}
this.setFlags();
return this;
};
/**
* Dispatches a packet
* Leaves a group.
*
* @return {Socket} for chaining
* @api public
*/
Socket.prototype.leave = function (group) {
var index = this.subscriptions.indexOf(group);
if (~index) {
this.subscriptions.splice(index, 1);
}
return this;
};
/**
* Called upon disconnect.
*
* @api private
*/
Socket.prototype.dispatch = function (packet, volatile) {
if (this.manager.transports[this.id] && this.manager.transports[this.id].open) {
this.manager.transports[this.id].onDispatch(packet, volatile);
} else {
if (!volatile) {
this.manager.onClientDispatch(this.id, packet, volatile);
}
this.manager.store.publish('dispatch:' + this.id, packet, volatile);
Socket.prototype.onDisconnect = function () {
for (var i = 0, l = this.subscriptions; i < l; i++) {
this.store.removeFromGroup(id, group, fn);
}
};
/**
* Stores data for the client.
*
* @api public
*/
Socket.prototype.set = function (key, value, fn) {
this.store.set(key, value, fn);
return this;
};
/**
* Retrieves data for the client
*
* @api public
*/
Socket.prototype.get = function (key, fn) {
this.store.get(key, fn);
return this;
};
/**
* Checks data for the client
*
* @api public
*/
Socket.prototype.has = function (key, fn) {
this.store.has(key, fn);
return this;
};
/**
* Deletes data for the client
*
* @api public
*/
Socket.prototype.del = function (key, fn) {
this.store.del(key, fn);
return this;
};
/**
* Kicks client
*
* @api public
*/
Socket.prototype.disconnect = function () {
if (!this.disconnected) {
this.log.info('booting client');
if (this.manager.transports[this.id] && this.manager.transports[this.id].open) {
this.manager.transports[this.id].onForcedDisconnect();
} else {
this.manager.onClientDisconnect(this.id);
this.manager.store.publish('disconnect:' + this.id);
}
}
return this;
};
/**
* Send a message.
*
* @api public
*/
Socket.prototype.send = function (data, fn) {
var packet = {
type: this.flags.json ? 'json' : 'message'
, data: data
};
if (fn) {
packet.id = ++this.ackPackets;
packet.ack = true;
this.acks[packet.id] = fn;
}
return this.packet(packet);
};
/**
* Original emit function.
*
* @api private
*/
Socket.prototype.$emit = EventEmitter.prototype.emit;
/**
* Emit override for custom events.
*
* @api public
*/
Socket.prototype.emit = function (ev) {
if (ev == 'newListener') {
return this.$emit.apply(this, arguments);
}
var args = util.toArray(arguments).slice(1)
, lastArg = args[args.length - 1]
, packet = {
type: 'event'
, name: ev
};
if ('function' == typeof lastArg) {
packet.id = ++this.ackPackets;
packet.ack = lastArg.length ? 'data' : true;
this.acks[packet.id] = lastArg;
args = args.slice(0, args.length - 1);
}
packet.args = args;
return this.packet(packet);
};

View File

@@ -1,395 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var client = require('socket.io-client')
, cp = require('child_process')
, fs = require('fs')
, util = require('./util');
/**
* File type details.
*
* @api private
*/
var mime = {
js: {
type: 'application/javascript'
, encoding: 'utf8'
, gzip: true
}
, swf: {
type: 'application/x-shockwave-flash'
, encoding: 'binary'
, gzip: false
}
};
/**
* Regexp for matching custom transport patterns. Users can configure their own
* socket.io bundle based on the url structure. Different transport names are
* concatinated using the `+` char. /socket.io/socket.io+websocket.js should
* create a bundle that only contains support for the websocket.
*
* @api private
*/
var bundle = /\+((?:\+)?[\w\-]+)*(?:\.v\d+\.\d+\.\d+)?(?:\.js)$/
, versioning = /\.v\d+\.\d+\.\d+(?:\.js)$/;
/**
* Export the constructor
*/
exports = module.exports = Static;
/**
* Static constructor
*
* @api public
*/
function Static (manager) {
this.manager = manager;
this.cache = {};
this.paths = {};
this.init();
}
/**
* Initialize the Static by adding default file paths.
*
* @api public
*/
Static.prototype.init = function () {
/**
* Generates a unique id based the supplied transports array
*
* @param {Array} transports The array with transport types
* @api private
*/
function id (transports) {
var id = transports.join('').split('').map(function (char) {
return ('' + char.charCodeAt(0)).split('').pop();
}).reduce(function (char, id) {
return char +id;
});
return client.version + ':' + id;
}
/**
* Generates a socket.io-client file based on the supplied transports.
*
* @param {Array} transports The array with transport types
* @param {Function} callback Callback for the static.write
* @api private
*/
function build (transports, callback) {
client.builder(transports, {
minify: self.manager.enabled('browser client minification')
}, function (err, content) {
callback(err, content ? new Buffer(content) : null, id(transports));
}
);
}
var self = this;
// add our default static files
this.add('/static/flashsocket/WebSocketMain.swf', {
file: client.dist + '/WebSocketMain.swf'
});
this.add('/static/flashsocket/WebSocketMainInsecure.swf', {
file: client.dist + '/WebSocketMainInsecure.swf'
});
// generates dedicated build based on the available transports
this.add('/socket.io.js', function (path, callback) {
build(self.manager.get('transports'), callback);
});
this.add('/socket.io.v', { mime: mime.js }, function (path, callback) {
build(self.manager.get('transports'), callback);
});
// allow custom builds based on url paths
this.add('/socket.io+', { mime: mime.js }, function (path, callback) {
var available = self.manager.get('transports')
, matches = path.match(bundle)
, transports = [];
if (!matches) return callback('No valid transports');
// make sure they valid transports
matches[0].split('.')[0].split('+').slice(1).forEach(function (transport) {
if (!!~available.indexOf(transport)) {
transports.push(transport);
}
});
if (!transports.length) return callback('No valid transports');
build(transports, callback);
});
// clear cache when transports change
this.manager.on('set:transports', function (key, value) {
delete self.cache['/socket.io.js'];
Object.keys(self.cache).forEach(function (key) {
if (bundle.test(key)) {
delete self.cache[key];
}
});
});
};
/**
* Gzip compress buffers.
*
* @param {Buffer} data The buffer that needs gzip compression
* @param {Function} callback
* @api public
*/
Static.prototype.gzip = function (data, callback) {
var gzip = cp.spawn('gzip', ['-9', '-c', '-f', '-n'])
, encoding = Buffer.isBuffer(data) ? 'binary' : 'utf8'
, buffer = []
, err;
gzip.stdout.on('data', function (data) {
buffer.push(data);
});
gzip.stderr.on('data', function (data) {
err = data +'';
buffer.length = 0;
});
gzip.on('exit', function () {
if (err) return callback(err);
var size = 0
, index = 0
, i = buffer.length
, content;
while (i--) {
size += buffer[i].length;
}
content = new Buffer(size);
i = buffer.length;
buffer.forEach(function (buffer) {
var length = buffer.length;
buffer.copy(content, index, 0, length);
index += length;
});
buffer.length = 0;
callback(null, content);
});
gzip.stdin.end(data, encoding);
};
/**
* Is the path a static file?
*
* @param {String} path The path that needs to be checked
* @api public
*/
Static.prototype.has = function (path) {
// fast case
if (this.paths[path]) return this.paths[path];
var keys = Object.keys(this.paths)
, i = keys.length;
while (i--) {
if (-~path.indexOf(keys[i])) return this.paths[keys[i]];
}
return false;
};
/**
* Add new paths new paths that can be served using the static provider.
*
* @param {String} path The path to respond to
* @param {Options} options Options for writing out the response
* @param {Function} [callback] Optional callback if no options.file is
* supplied this would be called instead.
* @api public
*/
Static.prototype.add = function (path, options, callback) {
var extension = /(?:\.(\w{1,4}))$/.exec(path);
if (!callback && typeof options == 'function') {
callback = options;
options = {};
}
options.mime = options.mime || (extension ? mime[extension[1]] : false);
if (callback) options.callback = callback;
if (!(options.file || options.callback) || !options.mime) return false;
this.paths[path] = options;
return true;
};
/**
* Writes a static response.
*
* @param {String} path The path for the static content
* @param {HTTPRequest} req The request object
* @param {HTTPResponse} res The response object
* @api public
*/
Static.prototype.write = function (path, req, res) {
/**
* Write a response without throwing errors because can throw error if the
* response is no longer writable etc.
*
* @api private
*/
function write (status, headers, content, encoding) {
try {
res.writeHead(status, headers || undefined);
// only write content if it's not a HEAD request and we actually have
// some content to write (304's doesn't have content).
res.end(
req.method !== 'HEAD' && content ? content : ''
, encoding || undefined
);
} catch (e) {}
}
/**
* Answers requests depending on the request properties and the reply object.
*
* @param {Object} reply The details and content to reply the response with
* @api private
*/
function answer (reply) {
var cached = req.headers['if-none-match'] === reply.etag;
if (cached && self.manager.enabled('browser client etag')) {
return write(304);
}
var accept = req.headers['accept-encoding'] || ''
, gzip = !!~accept.toLowerCase().indexOf('gzip')
, mime = reply.mime
, versioned = reply.versioned
, headers = {
'Content-Type': mime.type
};
// check if we can add a etag
if (self.manager.enabled('browser client etag') && reply.etag && !versioned) {
headers['Etag'] = reply.etag;
}
// see if we need to set Expire headers because the path is versioned
if (versioned) {
var expires = self.manager.get('browser client expires');
headers['Cache-Control'] = 'private, x-gzip-ok="", max-age=' + expires;
headers['Date'] = new Date().toUTCString();
headers['Expires'] = new Date(Date.now() + (expires * 1000)).toUTCString();
}
if (gzip && reply.gzip) {
headers['Content-Length'] = reply.gzip.length;
headers['Content-Encoding'] = 'gzip';
headers['Vary'] = 'Accept-Encoding';
write(200, headers, reply.gzip.content, mime.encoding);
} else {
headers['Content-Length'] = reply.length;
write(200, headers, reply.content, mime.encoding);
}
self.manager.log.debug('served static content ' + path);
}
var self = this
, details;
// most common case first
if (this.manager.enabled('browser client cache') && this.cache[path]) {
return answer(this.cache[path]);
} else if (this.manager.get('browser client handler')) {
return this.manager.get('browser client handler').call(this, req, res);
} else if ((details = this.has(path))) {
/**
* A small helper function that will let us deal with fs and dynamic files
*
* @param {Object} err Optional error
* @param {Buffer} content The data
* @api private
*/
function ready (err, content, etag) {
if (err) {
self.manager.log.warn('Unable to serve file. ' + (err.message || err));
return write(500, null, 'Error serving static ' + path);
}
// store the result in the cache
var reply = self.cache[path] = {
content: content
, length: content.length
, mime: details.mime
, etag: etag || client.version
, versioned: versioning.test(path)
};
// check if gzip is enabled
if (details.mime.gzip && self.manager.enabled('browser client gzip')) {
self.gzip(content, function (err, content) {
if (!err) {
reply.gzip = {
content: content
, length: content.length
}
}
answer(reply);
});
} else {
answer(reply);
}
}
if (details.file) {
fs.readFile(details.file, ready);
} else if(details.callback) {
details.callback.call(this, path, ready);
} else {
write(404, null, 'File handle not found');
}
} else {
write(404, null, 'File not found');
}
};

View File

@@ -1,98 +1,53 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Expose the constructor.
*/
exports = module.exports = Store;
/**
* Module dependencies.
*/
var EventEmitter = process.EventEmitter;
/**
* Store interface
* Store. In memory by default.
*
* @api public
*/
function Store (options) {
this.options = options;
this.clients = {};
};
function Store () {
this.groups = {};
}
/**
* Inherit from EventEmitter.
*/
Store.prototype.__proto__ = EventEmitter.prototype;
/**
* Initializes a client store
* Adds id to group.
*
* @param {String} id
* @api public
*/
Store.prototype.client = function (id) {
if (!this.clients[id]) {
this.clients[id] = new (this.constructor.Client)(this, id);
}
return this.clients[id];
};
/**
* Destroys a client
*
* @api {String} sid
* @param {Number} number of seconds to expire client data
* @param {String} client id
* @param {String} group name
* @param {Function} event listener
* @param {Function} callback
* @api private
*/
Store.prototype.destroyClient = function (id, expiration) {
if (this.clients[id]) {
this.clients[id].destroy(expiration);
delete this.clients[id];
Store.prototype.addToGroup = function (id, group, listener, fn) {
if (!this.groups[group]) {
this.groups[group] = [];
this.listeners[group] = {};
}
return this;
if (!this.listeners[group][id]) {
this.on('group:' + group, listener);
this.listeners[group][id] = listener;
this.groups[group].push(id);
}
fn && fn();
};
/**
* Destroys the store
* Removes id from group.
*
* @param {Number} number of seconds to expire client data
* @api private
*/
Store.prototype.destroy = function (clientExpiration) {
var keys = Object.keys(this.clients)
, count = keys.length;
for (var i = 0, l = count; i < l; i++) {
this.destroyClient(keys[i], clientExpiration);
Store.prototype.removeFromGroup = function (id, group, fn) {
if (this.groups[group]) {
var i = this.groups[group].indexOf(id);
if (~i) {
this.groups[group].splice(i, 1);
this.removeListener('group:' + group, this.listeners[group][id]);
}
}
this.clients = {};
return this;
};
/**
* Client.
*
* @api public
*/
Store.Client = function (store, id) {
this.store = store;
this.id = id;
fn && fn();
};

View File

@@ -1,143 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var crypto = require('crypto')
, Store = require('../store');
/**
* Exports the constructor.
*/
exports = module.exports = Memory;
Memory.Client = Client;
/**
* Memory store
*
* @api public
*/
function Memory (opts) {
Store.call(this, opts);
};
/**
* Inherits from Store.
*/
Memory.prototype.__proto__ = Store.prototype;
/**
* Publishes a message.
*
* @api private
*/
Memory.prototype.publish = function () { };
/**
* Subscribes to a channel
*
* @api private
*/
Memory.prototype.subscribe = function () { };
/**
* Unsubscribes
*
* @api private
*/
Memory.prototype.unsubscribe = function () { };
/**
* Client constructor
*
* @api private
*/
function Client () {
Store.Client.apply(this, arguments);
this.data = {};
};
/**
* Inherits from Store.Client
*/
Client.prototype.__proto__ = Store.Client;
/**
* Gets a key
*
* @api public
*/
Client.prototype.get = function (key, fn) {
fn(null, this.data[key] === undefined ? null : this.data[key]);
return this;
};
/**
* Sets a key
*
* @api public
*/
Client.prototype.set = function (key, value, fn) {
this.data[key] = value;
fn && fn(null);
return this;
};
/**
* Has a key
*
* @api public
*/
Client.prototype.has = function (key, fn) {
fn(null, key in this.data);
};
/**
* Deletes a key
*
* @api public
*/
Client.prototype.del = function (key, fn) {
delete this.data[key];
fn && fn(null);
return this;
};
/**
* Destroys the client.
*
* @param {Number} number of seconds to expire data
* @api private
*/
Client.prototype.destroy = function (expiration) {
if ('number' != typeof expiration) {
this.data = {};
} else {
var self = this;
setTimeout(function () {
self.data = {};
}, expiration * 1000);
}
return this;
};

View File

@@ -1,269 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var crypto = require('crypto')
, Store = require('../store')
, assert = require('assert');
/**
* Exports the constructor.
*/
exports = module.exports = Redis;
Redis.Client = Client;
/**
* Redis store.
* Options:
* - nodeId (fn) gets an id that uniquely identifies this node
* - redis (fn) redis constructor, defaults to redis
* - redisPub (object) options to pass to the pub redis client
* - redisSub (object) options to pass to the sub redis client
* - redisClient (object) options to pass to the general redis client
* - pack (fn) custom packing, defaults to JSON or msgpack if installed
* - unpack (fn) custom packing, defaults to JSON or msgpack if installed
*
* @api public
*/
function Redis (opts) {
opts = opts || {};
// node id to uniquely identify this node
var nodeId = opts.nodeId || function () {
// by default, we generate a random id
return Math.abs(Math.random() * Math.random() * Date.now() | 0);
};
this.nodeId = nodeId();
// packing / unpacking mechanism
if (opts.pack) {
this.pack = opts.pack;
this.unpack = opts.unpack;
} else {
try {
var msgpack = require('msgpack');
this.pack = msgpack.pack;
this.unpack = msgpack.unpack;
} catch (e) {
this.pack = JSON.stringify;
this.unpack = JSON.parse;
}
}
var redis = opts.redis || require('redis')
, RedisClient = redis.RedisClient;
// initialize a pubsub client and a regular client
if (opts.redisPub instanceof RedisClient) {
this.pub = opts.redisPub;
} else {
opts.redisPub || (opts.redisPub = {});
this.pub = redis.createClient(opts.redisPub.port, opts.redisPub.host, opts.redisPub);
}
if (opts.redisSub instanceof RedisClient) {
this.sub = opts.redisSub;
} else {
opts.redisSub || (opts.redisSub = {});
this.sub = redis.createClient(opts.redisSub.port, opts.redisSub.host, opts.redisSub);
}
if (opts.redisClient instanceof RedisClient) {
this.cmd = opts.redisClient;
} else {
opts.redisClient || (opts.redisClient = {});
this.cmd = redis.createClient(opts.redisClient.port, opts.redisClient.host, opts.redisClient);
}
Store.call(this, opts);
this.sub.setMaxListeners(0);
this.setMaxListeners(0);
};
/**
* Inherits from Store.
*/
Redis.prototype.__proto__ = Store.prototype;
/**
* Publishes a message.
*
* @api private
*/
Redis.prototype.publish = function (name) {
var args = Array.prototype.slice.call(arguments, 1);
this.pub.publish(name, this.pack({ nodeId: this.nodeId, args: args }));
this.emit.apply(this, ['publish', name].concat(args));
};
/**
* Subscribes to a channel
*
* @api private
*/
Redis.prototype.subscribe = function (name, consumer, fn) {
this.sub.subscribe(name);
if (consumer || fn) {
var self = this;
self.sub.on('subscribe', function subscribe (ch) {
if (name == ch) {
function message (ch, msg) {
if (name == ch) {
msg = self.unpack(msg);
// we check that the message consumed wasnt emitted by this node
if (self.nodeId != msg.nodeId) {
consumer.apply(null, msg.args);
}
}
};
self.sub.on('message', message);
self.on('unsubscribe', function unsubscribe (ch) {
if (name == ch) {
self.sub.removeListener('message', message);
self.removeListener('unsubscribe', unsubscribe);
}
});
self.sub.removeListener('subscribe', subscribe);
fn && fn();
}
});
}
this.emit('subscribe', name, consumer, fn);
};
/**
* Unsubscribes
*
* @api private
*/
Redis.prototype.unsubscribe = function (name, fn) {
this.sub.unsubscribe(name);
if (fn) {
var client = this.sub;
client.on('unsubscribe', function unsubscribe (ch) {
if (name == ch) {
fn();
client.removeListener('unsubscribe', unsubscribe);
}
});
}
this.emit('unsubscribe', name, fn);
};
/**
* Destroys the store
*
* @api public
*/
Redis.prototype.destroy = function () {
Store.prototype.destroy.call(this);
this.pub.end();
this.sub.end();
this.cmd.end();
};
/**
* Client constructor
*
* @api private
*/
function Client (store, id) {
Store.Client.call(this, store, id);
};
/**
* Inherits from Store.Client
*/
Client.prototype.__proto__ = Store.Client;
/**
* Redis hash get
*
* @api private
*/
Client.prototype.get = function (key, fn) {
this.store.cmd.hget(this.id, key, fn);
return this;
};
/**
* Redis hash set
*
* @api private
*/
Client.prototype.set = function (key, value, fn) {
this.store.cmd.hset(this.id, key, value, fn);
return this;
};
/**
* Redis hash del
*
* @api private
*/
Client.prototype.del = function (key, fn) {
this.store.cmd.hdel(this.id, key, fn);
return this;
};
/**
* Redis hash has
*
* @api private
*/
Client.prototype.has = function (key, fn) {
this.store.cmd.hexists(this.id, key, function (err, has) {
if (err) return fn(err);
fn(null, !!has);
});
return this;
};
/**
* Destroys client
*
* @param {Number} number of seconds to expire data
* @api private
*/
Client.prototype.destroy = function (expiration) {
if ('number' != typeof expiration) {
this.store.cmd.del(this.id);
} else {
this.store.cmd.expire(this.id, expiration);
}
return this;
};

View File

@@ -1,534 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var parser = require('./parser');
/**
* Expose the constructor.
*/
exports = module.exports = Transport;
/**
* Transport constructor.
*
* @api public
*/
function Transport (mng, data, req) {
this.manager = mng;
this.id = data.id;
this.disconnected = false;
this.drained = true;
this.handleRequest(req);
};
/**
* Access the logger.
*
* @api public
*/
Transport.prototype.__defineGetter__('log', function () {
return this.manager.log;
});
/**
* Access the store.
*
* @api public
*/
Transport.prototype.__defineGetter__('store', function () {
return this.manager.store;
});
/**
* Handles a request when it's set.
*
* @api private
*/
Transport.prototype.handleRequest = function (req) {
this.log.debug('setting request', req.method, req.url);
this.req = req;
if (req.method == 'GET') {
this.socket = req.socket;
this.open = true;
this.drained = true;
this.setHeartbeatInterval();
this.setHandlers();
this.onSocketConnect();
}
};
/**
* Called when a connection is first set.
*
* @api private
*/
Transport.prototype.onSocketConnect = function () { };
/**
* Sets transport handlers
*
* @api private
*/
Transport.prototype.setHandlers = function () {
var self = this;
// we need to do this in a pub/sub way since the client can POST the message
// over a different socket (ie: different Transport instance)
this.store.subscribe('heartbeat-clear:' + this.id, function () {
self.onHeartbeatClear();
});
this.store.subscribe('disconnect-force:' + this.id, function () {
self.onForcedDisconnect();
});
this.store.subscribe('dispatch:' + this.id, function (packet, volatile) {
self.onDispatch(packet, volatile);
});
this.bound = {
end: this.onSocketEnd.bind(this)
, close: this.onSocketClose.bind(this)
, error: this.onSocketError.bind(this)
, drain: this.onSocketDrain.bind(this)
};
this.socket.on('end', this.bound.end);
this.socket.on('close', this.bound.close);
this.socket.on('error', this.bound.error);
this.socket.on('drain', this.bound.drain);
this.handlersSet = true;
};
/**
* Removes transport handlers
*
* @api private
*/
Transport.prototype.clearHandlers = function () {
if (this.handlersSet) {
this.store.unsubscribe('disconnect-force:' + this.id);
this.store.unsubscribe('heartbeat-clear:' + this.id);
this.store.unsubscribe('dispatch:' + this.id);
this.socket.removeListener('end', this.bound.end);
this.socket.removeListener('close', this.bound.close);
this.socket.removeListener('error', this.bound.error);
this.socket.removeListener('drain', this.bound.drain);
}
};
/**
* Called when the connection dies
*
* @api private
*/
Transport.prototype.onSocketEnd = function () {
this.end('socket end');
};
/**
* Called when the connection dies
*
* @api private
*/
Transport.prototype.onSocketClose = function (error) {
this.end(error ? 'socket error' : 'socket close');
};
/**
* Called when the connection has an error.
*
* @api private
*/
Transport.prototype.onSocketError = function (err) {
if (this.open) {
this.socket.destroy();
this.onClose();
}
this.log.info('socket error ' + err.stack);
};
/**
* Called when the connection is drained.
*
* @api private
*/
Transport.prototype.onSocketDrain = function () {
this.drained = true;
};
/**
* Called upon receiving a heartbeat packet.
*
* @api private
*/
Transport.prototype.onHeartbeatClear = function () {
this.clearHeartbeatTimeout();
this.setHeartbeatInterval();
};
/**
* Called upon a forced disconnection.
*
* @api private
*/
Transport.prototype.onForcedDisconnect = function () {
if (!this.disconnected) {
this.log.info('transport end by forced client disconnection');
if (this.open) {
this.packet({ type: 'disconnect' });
}
this.end('booted');
}
};
/**
* Dispatches a packet.
*
* @api private
*/
Transport.prototype.onDispatch = function (packet, volatile) {
if (volatile) {
this.writeVolatile(packet);
} else {
this.write(packet);
}
};
/**
* Sets the close timeout.
*/
Transport.prototype.setCloseTimeout = function () {
if (!this.closeTimeout) {
var self = this;
this.closeTimeout = setTimeout(function () {
self.log.debug('fired close timeout for client', self.id);
self.closeTimeout = null;
self.end('close timeout');
}, this.manager.get('close timeout') * 1000);
this.log.debug('set close timeout for client', this.id);
}
};
/**
* Clears the close timeout.
*/
Transport.prototype.clearCloseTimeout = function () {
if (this.closeTimeout) {
clearTimeout(this.closeTimeout);
this.closeTimeout = null;
this.log.debug('cleared close timeout for client', this.id);
}
};
/**
* Sets the heartbeat timeout
*/
Transport.prototype.setHeartbeatTimeout = function () {
if (!this.heartbeatTimeout && this.manager.enabled('heartbeats')) {
var self = this;
this.heartbeatTimeout = setTimeout(function () {
self.log.debug('fired heartbeat timeout for client', self.id);
self.heartbeatTimeout = null;
self.end('heartbeat timeout');
}, this.manager.get('heartbeat timeout') * 1000);
this.log.debug('set heartbeat timeout for client', this.id);
}
};
/**
* Clears the heartbeat timeout
*
* @param text
*/
Transport.prototype.clearHeartbeatTimeout = function () {
if (this.heartbeatTimeout && this.manager.enabled('heartbeats')) {
clearTimeout(this.heartbeatTimeout);
this.heartbeatTimeout = null;
this.log.debug('cleared heartbeat timeout for client', this.id);
}
};
/**
* Sets the heartbeat interval. To be called when a connection opens and when
* a heartbeat is received.
*
* @api private
*/
Transport.prototype.setHeartbeatInterval = function () {
if (!this.heartbeatInterval && this.manager.enabled('heartbeats')) {
var self = this;
this.heartbeatInterval = setTimeout(function () {
self.heartbeat();
self.heartbeatInterval = null;
}, this.manager.get('heartbeat interval') * 1000);
this.log.debug('set heartbeat interval for client', this.id);
}
};
/**
* Clears all timeouts.
*
* @api private
*/
Transport.prototype.clearTimeouts = function () {
this.clearCloseTimeout();
this.clearHeartbeatTimeout();
this.clearHeartbeatInterval();
};
/**
* Sends a heartbeat
*
* @api private
*/
Transport.prototype.heartbeat = function () {
if (this.open) {
this.log.debug('emitting heartbeat for client', this.id);
this.packet({ type: 'heartbeat' });
this.setHeartbeatTimeout();
}
return this;
};
/**
* Handles a message.
*
* @param {Object} packet object
* @api private
*/
Transport.prototype.onMessage = function (packet) {
var current = this.manager.transports[this.id];
if ('heartbeat' == packet.type) {
this.log.debug('got heartbeat packet');
if (current && current.open) {
current.onHeartbeatClear();
} else {
this.store.publish('heartbeat-clear:' + this.id);
}
} else {
if ('disconnect' == packet.type && packet.endpoint == '') {
this.log.debug('got disconnection packet');
if (current) {
current.onForcedDisconnect();
} else {
this.store.publish('disconnect-force:' + this.id);
}
return;
}
if (packet.id && packet.ack != 'data') {
this.log.debug('acknowledging packet automatically');
var ack = parser.encodePacket({
type: 'ack'
, ackId: packet.id
, endpoint: packet.endpoint || ''
});
if (current && current.open) {
current.onDispatch(ack);
} else {
this.manager.onClientDispatch(this.id, ack);
this.store.publish('dispatch:' + this.id, ack);
}
}
// handle packet locally or publish it
if (current) {
this.manager.onClientMessage(this.id, packet);
} else {
this.store.publish('message:' + this.id, packet);
}
}
};
/**
* Clears the heartbeat interval
*
* @api private
*/
Transport.prototype.clearHeartbeatInterval = function () {
if (this.heartbeatInterval && this.manager.enabled('heartbeats')) {
clearTimeout(this.heartbeatInterval);
this.heartbeatInterval = null;
this.log.debug('cleared heartbeat interval for client', this.id);
}
};
/**
* Finishes the connection and makes sure client doesn't reopen
*
* @api private
*/
Transport.prototype.disconnect = function (reason) {
this.packet({ type: 'disconnect' });
this.end(reason);
return this;
};
/**
* Closes the connection.
*
* @api private
*/
Transport.prototype.close = function () {
if (this.open) {
this.doClose();
this.onClose();
}
};
/**
* Called upon a connection close.
*
* @api private
*/
Transport.prototype.onClose = function () {
if (this.open) {
this.setCloseTimeout();
this.clearHandlers();
this.open = false;
this.manager.onClose(this.id);
this.store.publish('close', this.id);
}
};
/**
* Cleans up the connection, considers the client disconnected.
*
* @api private
*/
Transport.prototype.end = function (reason) {
if (!this.disconnected) {
this.log.info('transport end');
var local = this.manager.transports[this.id];
this.close();
this.clearTimeouts();
this.disconnected = true;
if (local) {
this.manager.onClientDisconnect(this.id, reason, true);
} else {
this.store.publish('disconnect:' + this.id, reason);
}
}
};
/**
* Signals that the transport should pause and buffer data.
*
* @api public
*/
Transport.prototype.discard = function () {
this.log.debug('discarding transport');
this.discarded = true;
this.clearTimeouts();
this.clearHandlers();
return this;
};
/**
* Writes an error packet with the specified reason and advice.
*
* @param {Number} advice
* @param {Number} reason
* @api public
*/
Transport.prototype.error = function (reason, advice) {
this.packet({
type: 'error'
, reason: reason
, advice: advice
});
this.log.warn(reason, advice ? ('client should ' + advice) : '');
this.end('error');
};
/**
* Write a packet.
*
* @api public
*/
Transport.prototype.packet = function (obj) {
return this.write(parser.encodePacket(obj));
};
/**
* Writes a volatile message.
*
* @api private
*/
Transport.prototype.writeVolatile = function (msg) {
if (this.open) {
if (this.drained) {
this.write(msg);
} else {
this.log.debug('ignoring volatile packet, buffer not drained');
}
} else {
this.log.debug('ignoring volatile packet, transport not open');
}
};

View File

@@ -1,106 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module requirements.
*/
var WebSocket = require('./websocket');
/**
* Export the constructor.
*/
exports = module.exports = FlashSocket;
/**
* The FlashSocket transport is just a proxy
* for WebSocket connections.
*
* @api public
*/
function FlashSocket (mng, data, req) {
return WebSocket.call(this, mng, data, req);
}
/**
* Inherits from WebSocket.
*/
FlashSocket.prototype.__proto__ = WebSocket.prototype;
/**
* Transport name
*
* @api public
*/
FlashSocket.prototype.name = 'flashsocket';
/**
* Listens for new configuration changes of the Manager
* this way we can enable and disable the flash server.
*
* @param {Manager} Manager instance.
* @api private
*/
FlashSocket.init = function (manager) {
var server;
function create () {
server = require('policyfile').createServer({
log: function(msg){
manager.log.info(msg.toLowerCase());
}
}, manager.get('origins'));
server.on('close', function (e) {
server = null;
});
server.listen(manager.get('flash policy port'), manager.server);
manager.flashPolicyServer = server;
}
// listen for origin changes, so we can update the server
manager.on('set:origins', function (value, key) {
if (!server) return;
// update the origins and compile a new response buffer
server.origins = Array.isArray(value) ? value : [value];
server.compile();
});
// destory the server and create a new server
manager.on('set:flash policy port', function (value, key) {
var transports = manager.get('transports');
if (~transports.indexOf('flashsocket')) {
if (server) {
if (server.port === value) return;
// destroy the server and rebuild it on a new port
try {
server.close();
}
catch (e) { /* ignore exception. could e.g. be that the server isn't started yet */ }
}
create();
}
});
// only start the server
manager.on('set:transports', function (value, key){
if (!server && ~manager.get('transports').indexOf('flashsocket')) {
create();
}
});
// check if we need to initialize at start
if (~manager.get('transports').indexOf('flashsocket')){
create();
}
};

View File

@@ -1,82 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module requirements.
*/
var HTTPTransport = require('./http');
/**
* Export the constructor.
*/
exports = module.exports = HTMLFile;
/**
* HTMLFile transport constructor.
*
* @api public
*/
function HTMLFile (mng, data, req) {
HTTPTransport.call(this, mng, data, req);
};
/**
* Inherits from Transport.
*/
HTMLFile.prototype.__proto__ = HTTPTransport.prototype;
/**
* Transport name
*
* @api public
*/
HTMLFile.prototype.name = 'htmlfile';
/**
* Handles the request.
*
* @api private
*/
HTMLFile.prototype.handleRequest = function (req) {
HTTPTransport.prototype.handleRequest.call(this, req);
if (req.method == 'GET') {
req.res.writeHead(200, {
'Content-Type': 'text/html; charset=UTF-8'
, 'Connection': 'keep-alive'
, 'Transfer-Encoding': 'chunked'
});
req.res.write(
'<html><body>'
+ '<script>var _ = function (msg) { parent.s._(msg, document); };</script>'
+ new Array(174).join(' ')
);
}
};
/**
* Performs the write.
*
* @api private
*/
HTMLFile.prototype.write = function (data) {
data = '<script>_(' + JSON.stringify(data) + ');</script>';
if (this.response.write(data)) {
this.drained = true;
}
this.log.debug(this.name + ' writing', data);
};

View File

@@ -1,135 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module requirements.
*/
var HTTPTransport = require('./http');
/**
* Exports the constructor.
*/
exports = module.exports = HTTPPolling;
/**
* HTTP polling constructor.
*
* @api public.
*/
function HTTPPolling (mng, data, req) {
HTTPTransport.call(this, mng, data, req);
};
/**
* Inherits from HTTPTransport.
*
* @api public.
*/
HTTPPolling.prototype.__proto__ = HTTPTransport.prototype;
/**
* Transport name
*
* @api public
*/
HTTPPolling.prototype.name = 'httppolling';
/**
* Removes heartbeat timeouts for polling.
*/
HTTPPolling.prototype.setHeartbeatInterval = function () {
return this;
};
/**
* Handles a request
*
* @api private
*/
HTTPPolling.prototype.handleRequest = function (req) {
HTTPTransport.prototype.handleRequest.call(this, req);
if (req.method == 'GET') {
var self = this;
this.pollTimeout = setTimeout(function () {
self.packet({ type: 'noop' });
self.log.debug(self.name + ' closed due to exceeded duration');
}, this.manager.get('polling duration') * 1000);
this.log.debug('setting poll timeout');
}
};
/**
* Clears polling timeout
*
* @api private
*/
HTTPPolling.prototype.clearPollTimeout = function () {
if (this.pollTimeout) {
clearTimeout(this.pollTimeout);
this.pollTimeout = null;
this.log.debug('clearing poll timeout');
}
return this;
};
/**
* Override clear timeouts to clear the poll timeout
*
* @api private
*/
HTTPPolling.prototype.clearTimeouts = function () {
HTTPTransport.prototype.clearTimeouts.call(this);
this.clearPollTimeout();
};
/**
* doWrite to clear poll timeout
*
* @api private
*/
HTTPPolling.prototype.doWrite = function () {
this.clearPollTimeout();
};
/**
* Performs a write.
*
* @api private.
*/
HTTPPolling.prototype.write = function (data, close) {
this.doWrite(data);
this.response.end();
this.onClose();
};
/**
* Override end.
*
* @api private
*/
HTTPPolling.prototype.end = function () {
this.clearPollTimeout();
return HTTPTransport.prototype.end.call(this);
};

View File

@@ -1,121 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module requirements.
*/
var Transport = require('../transport')
, parser = require('../parser')
, qs = require('querystring');
/**
* Export the constructor.
*/
exports = module.exports = HTTPTransport;
/**
* HTTP interface constructor. For all non-websocket transports.
*
* @api public
*/
function HTTPTransport (mng, data, req) {
Transport.call(this, mng, data, req);
};
/**
* Inherits from Transport.
*/
HTTPTransport.prototype.__proto__ = Transport.prototype;
/**
* Handles a request.
*
* @api private
*/
HTTPTransport.prototype.handleRequest = function (req) {
if (req.method == 'POST') {
var buffer = ''
, res = req.res
, origin = req.headers.origin
, headers = { 'Content-Length': 1 }
, self = this;
req.on('data', function (data) {
buffer += data;
if (Buffer.byteLength(buffer) >= self.manager.get('destroy buffer size')) {
buffer = '';
req.connection.destroy();
}
});
req.on('end', function () {
res.writeHead(200, headers);
res.end('1');
self.onData(self.postEncoded ? qs.parse(buffer).d : buffer);
});
// prevent memory leaks for uncompleted requests
req.on('close', function () {
buffer = '';
});
if (origin) {
// https://developer.mozilla.org/En/HTTP_Access_Control
headers['Access-Control-Allow-Origin'] = '*';
if (req.headers.cookie) {
headers['Access-Control-Allow-Credentials'] = 'true';
}
}
} else {
this.response = req.res;
Transport.prototype.handleRequest.call(this, req);
}
};
/**
* Handles data payload.
*
* @api private
*/
HTTPTransport.prototype.onData = function (data) {
var messages = parser.decodePayload(data);
this.log.debug(this.name + ' received data packet', data);
for (var i = 0, l = messages.length; i < l; i++) {
this.onMessage(messages[i]);
}
};
/**
* Closes the request-response cycle
*
* @api private
*/
HTTPTransport.prototype.doClose = function () {
this.response.end();
};
/**
* Writes a payload of messages
*
* @api private
*/
HTTPTransport.prototype.payload = function (msgs) {
this.write(parser.encodePayload(msgs));
};

View File

@@ -1,12 +0,0 @@
/**
* Export transports.
*/
module.exports = {
websocket: require('./websocket')
, flashsocket: require('./flashsocket')
, htmlfile: require('./htmlfile')
, 'xhr-polling': require('./xhr-polling')
, 'jsonp-polling': require('./jsonp-polling')
};

View File

@@ -1,96 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module requirements.
*/
var HTTPPolling = require('./http-polling');
/**
* Export the constructor.
*/
exports = module.exports = JSONPPolling;
/**
* JSON-P polling transport.
*
* @api public
*/
function JSONPPolling (mng, data, req) {
HTTPPolling.call(this, mng, data, req);
this.head = 'io.j[0](';
this.foot = ');';
if (data.query.i) {
this.head = 'io.j[' + data.query.i + '](';
}
};
/**
* Inherits from Transport.
*/
JSONPPolling.prototype.__proto__ = HTTPPolling.prototype;
/**
* Transport name
*
* @api public
*/
JSONPPolling.prototype.name = 'jsonppolling';
/**
* Make sure POST are decoded.
*/
JSONPPolling.prototype.postEncoded = true;
/**
* Handles incoming data.
* Due to a bug in \n handling by browsers, we expect a JSONified string.
*
* @api private
*/
JSONPPolling.prototype.onData = function (data) {
try {
data = JSON.parse(data);
} catch (e) {
this.error('parse', 'reconnect');
return;
}
HTTPPolling.prototype.onData.call(this, data);
};
/**
* Performs the write.
*
* @api private
*/
JSONPPolling.prototype.doWrite = function (data) {
HTTPPolling.prototype.doWrite.call(this);
var data = data === undefined
? '' : this.head + JSON.stringify(data) + this.foot;
this.response.writeHead(200, {
'Content-Type': 'text/javascript; charset=UTF-8'
, 'Content-Length': Buffer.byteLength(data)
, 'Connection': 'Keep-Alive'
, 'X-XSS-Protection': '0'
});
this.response.write(data);
this.log.debug(this.name + ' writing', data);
};

View File

@@ -1,36 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module requirements.
*/
var protocolVersions = require('./websocket/');
/**
* Export the constructor.
*/
exports = module.exports = WebSocket;
/**
* HTTP interface constructor. Interface compatible with all transports that
* depend on request-response cycles.
*
* @api public
*/
function WebSocket (mng, data, req) {
var transport
, version = req.headers['sec-websocket-version'];
if (typeof version !== 'undefined' && typeof protocolVersions[version] !== 'undefined') {
transport = new protocolVersions[version](mng, data, req);
}
else transport = new protocolVersions['default'](mng, data, req);
if (typeof this.name !== 'undefined') transport.name = this.name;
return transport;
};

View File

@@ -1,360 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module requirements.
*/
var Transport = require('../../transport')
, EventEmitter = process.EventEmitter
, crypto = require('crypto')
, parser = require('../../parser');
/**
* Export the constructor.
*/
exports = module.exports = WebSocket;
/**
* HTTP interface constructor. Interface compatible with all transports that
* depend on request-response cycles.
*
* @api public
*/
function WebSocket (mng, data, req) {
// parser
var self = this;
this.parser = new Parser();
this.parser.on('data', function (packet) {
self.log.debug(self.name + ' received data packet', packet);
self.onMessage(parser.decodePacket(packet));
});
this.parser.on('close', function () {
self.end();
});
this.parser.on('error', function () {
self.end();
});
Transport.call(this, mng, data, req);
};
/**
* Inherits from Transport.
*/
WebSocket.prototype.__proto__ = Transport.prototype;
/**
* Transport name
*
* @api public
*/
WebSocket.prototype.name = 'websocket';
/**
* Websocket draft version
*
* @api public
*/
WebSocket.prototype.protocolVersion = 'hixie-76';
/**
* Called when the socket connects.
*
* @api private
*/
WebSocket.prototype.onSocketConnect = function () {
var self = this;
this.socket.setNoDelay(true);
this.buffer = true;
this.buffered = [];
if (this.req.headers.upgrade !== 'WebSocket') {
this.log.warn(this.name + ' connection invalid');
this.end();
return;
}
var origin = this.req.headers['origin']
, location = ((this.manager.settings['match origin protocol'] ?
origin.match(/^https/) : this.socket.encrypted) ?
'wss' : 'ws')
+ '://' + this.req.headers.host + this.req.url
, waitingForNonce = false;
if (this.req.headers['sec-websocket-key1']) {
// If we don't have the nonce yet, wait for it (HAProxy compatibility).
if (! (this.req.head && this.req.head.length >= 8)) {
waitingForNonce = true;
}
var headers = [
'HTTP/1.1 101 WebSocket Protocol Handshake'
, 'Upgrade: WebSocket'
, 'Connection: Upgrade'
, 'Sec-WebSocket-Origin: ' + origin
, 'Sec-WebSocket-Location: ' + location
];
if (this.req.headers['sec-websocket-protocol']){
headers.push('Sec-WebSocket-Protocol: '
+ this.req.headers['sec-websocket-protocol']);
}
} else {
var headers = [
'HTTP/1.1 101 Web Socket Protocol Handshake'
, 'Upgrade: WebSocket'
, 'Connection: Upgrade'
, 'WebSocket-Origin: ' + origin
, 'WebSocket-Location: ' + location
];
}
try {
this.socket.write(headers.concat('', '').join('\r\n'));
this.socket.setTimeout(0);
this.socket.setNoDelay(true);
this.socket.setEncoding('utf8');
} catch (e) {
this.end();
return;
}
if (waitingForNonce) {
this.socket.setEncoding('binary');
} else if (this.proveReception(headers)) {
self.flush();
}
var headBuffer = '';
this.socket.on('data', function (data) {
if (waitingForNonce) {
headBuffer += data;
if (headBuffer.length < 8) {
return;
}
// Restore the connection to utf8 encoding after receiving the nonce
self.socket.setEncoding('utf8');
waitingForNonce = false;
// Stuff the nonce into the location where it's expected to be
self.req.head = headBuffer.substr(0, 8);
headBuffer = '';
if (self.proveReception(headers)) {
self.flush();
}
return;
}
self.parser.add(data);
});
};
/**
* Writes to the socket.
*
* @api private
*/
WebSocket.prototype.write = function (data) {
if (this.open) {
this.drained = false;
if (this.buffer) {
this.buffered.push(data);
return this;
}
var length = Buffer.byteLength(data)
, buffer = new Buffer(2 + length);
buffer.write('\x00', 'binary');
buffer.write(data, 1, 'utf8');
buffer.write('\xff', 1 + length, 'binary');
try {
if (this.socket.write(buffer)) {
this.drained = true;
}
} catch (e) {
this.end();
}
this.log.debug(this.name + ' writing', data);
}
};
/**
* Flushes the internal buffer
*
* @api private
*/
WebSocket.prototype.flush = function () {
this.buffer = false;
for (var i = 0, l = this.buffered.length; i < l; i++) {
this.write(this.buffered.splice(0, 1)[0]);
}
};
/**
* Finishes the handshake.
*
* @api private
*/
WebSocket.prototype.proveReception = function (headers) {
var self = this
, k1 = this.req.headers['sec-websocket-key1']
, k2 = this.req.headers['sec-websocket-key2'];
if (k1 && k2){
var md5 = crypto.createHash('md5');
[k1, k2].forEach(function (k) {
var n = parseInt(k.replace(/[^\d]/g, ''))
, spaces = k.replace(/[^ ]/g, '').length;
if (spaces === 0 || n % spaces !== 0){
self.log.warn('Invalid ' + self.name + ' key: "' + k + '".');
self.end();
return false;
}
n /= spaces;
md5.update(String.fromCharCode(
n >> 24 & 0xFF,
n >> 16 & 0xFF,
n >> 8 & 0xFF,
n & 0xFF));
});
md5.update(this.req.head.toString('binary'));
try {
this.socket.write(md5.digest('binary'), 'binary');
} catch (e) {
this.end();
}
}
return true;
};
/**
* Writes a payload.
*
* @api private
*/
WebSocket.prototype.payload = function (msgs) {
for (var i = 0, l = msgs.length; i < l; i++) {
this.write(msgs[i]);
}
return this;
};
/**
* Closes the connection.
*
* @api private
*/
WebSocket.prototype.doClose = function () {
this.socket.end();
};
/**
* WebSocket parser
*
* @api public
*/
function Parser () {
this.buffer = '';
this.i = 0;
};
/**
* Inherits from EventEmitter.
*/
Parser.prototype.__proto__ = EventEmitter.prototype;
/**
* Adds data to the buffer.
*
* @api public
*/
Parser.prototype.add = function (data) {
this.buffer += data;
this.parse();
};
/**
* Parses the buffer.
*
* @api private
*/
Parser.prototype.parse = function () {
for (var i = this.i, chr, l = this.buffer.length; i < l; i++){
chr = this.buffer[i];
if (this.buffer.length == 2 && this.buffer[1] == '\u0000') {
this.emit('close');
this.buffer = '';
this.i = 0;
return;
}
if (i === 0){
if (chr != '\u0000')
this.error('Bad framing. Expected null byte as first frame');
else
continue;
}
if (chr == '\ufffd'){
this.emit('data', this.buffer.substr(1, i - 1));
this.buffer = this.buffer.substr(i + 1);
this.i = 0;
return this.parse();
}
}
};
/**
* Handles an error
*
* @api private
*/
Parser.prototype.error = function (reason) {
this.buffer = '';
this.i = 0;
this.emit('error', reason);
return this;
};

View File

@@ -1,622 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module requirements.
*/
var Transport = require('../../transport')
, EventEmitter = process.EventEmitter
, crypto = require('crypto')
, url = require('url')
, parser = require('../../parser')
, util = require('../../util');
/**
* Export the constructor.
*/
exports = module.exports = WebSocket;
exports.Parser = Parser;
/**
* HTTP interface constructor. Interface compatible with all transports that
* depend on request-response cycles.
*
* @api public
*/
function WebSocket (mng, data, req) {
// parser
var self = this;
this.manager = mng;
this.parser = new Parser();
this.parser.on('data', function (packet) {
self.onMessage(parser.decodePacket(packet));
});
this.parser.on('ping', function () {
// version 8 ping => pong
try {
self.socket.write('\u008a\u0000');
}
catch (e) {
self.end();
return;
}
});
this.parser.on('close', function () {
self.end();
});
this.parser.on('error', function (reason) {
self.log.warn(self.name + ' parser error: ' + reason);
self.end();
});
Transport.call(this, mng, data, req);
};
/**
* Inherits from Transport.
*/
WebSocket.prototype.__proto__ = Transport.prototype;
/**
* Transport name
*
* @api public
*/
WebSocket.prototype.name = 'websocket';
/**
* Websocket draft version
*
* @api public
*/
WebSocket.prototype.protocolVersion = '07-12';
/**
* Called when the socket connects.
*
* @api private
*/
WebSocket.prototype.onSocketConnect = function () {
var self = this;
if (typeof this.req.headers.upgrade === 'undefined' ||
this.req.headers.upgrade.toLowerCase() !== 'websocket') {
this.log.warn(this.name + ' connection invalid');
this.end();
return;
}
var origin = this.req.headers['sec-websocket-origin']
, location = ((this.manager.settings['match origin protocol'] ?
origin.match(/^https/) : this.socket.encrypted) ?
'wss' : 'ws')
+ '://' + this.req.headers.host + this.req.url;
if (!this.verifyOrigin(origin)) {
this.log.warn(this.name + ' connection invalid: origin mismatch');
this.end();
return;
}
if (!this.req.headers['sec-websocket-key']) {
this.log.warn(this.name + ' connection invalid: received no key');
this.end();
return;
}
// calc key
var key = this.req.headers['sec-websocket-key'];
var shasum = crypto.createHash('sha1');
shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
key = shasum.digest('base64');
var headers = [
'HTTP/1.1 101 Switching Protocols'
, 'Upgrade: websocket'
, 'Connection: Upgrade'
, 'Sec-WebSocket-Accept: ' + key
];
try {
this.socket.write(headers.concat('', '').join('\r\n'));
this.socket.setTimeout(0);
this.socket.setNoDelay(true);
} catch (e) {
this.end();
return;
}
this.socket.on('data', function (data) {
self.parser.add(data);
});
};
/**
* Verifies the origin of a request.
*
* @api private
*/
WebSocket.prototype.verifyOrigin = function (origin) {
var origins = this.manager.get('origins');
if (origin === 'null') origin = '*';
if (origins.indexOf('*:*') !== -1) {
return true;
}
if (origin) {
try {
var parts = url.parse(origin);
parts.port = parts.port || 80;
var ok =
~origins.indexOf(parts.hostname + ':' + parts.port) ||
~origins.indexOf(parts.hostname + ':*') ||
~origins.indexOf('*:' + parts.port);
if (!ok) this.log.warn('illegal origin: ' + origin);
return ok;
} catch (ex) {
this.log.warn('error parsing origin');
}
}
else {
this.log.warn('origin missing from websocket call, yet required by config');
}
return false;
};
/**
* Writes to the socket.
*
* @api private
*/
WebSocket.prototype.write = function (data) {
if (this.open) {
var buf = this.frame(0x81, data);
try {
this.socket.write(buf, 'binary');
}
catch (e) {
this.end();
return;
}
this.log.debug(this.name + ' writing', data);
}
};
/**
* Writes a payload.
*
* @api private
*/
WebSocket.prototype.payload = function (msgs) {
for (var i = 0, l = msgs.length; i < l; i++) {
this.write(msgs[i]);
}
return this;
};
/**
* Frame server-to-client output as a text packet.
*
* @api private
*/
WebSocket.prototype.frame = function (opcode, str) {
var dataBuffer = new Buffer(str)
, dataLength = dataBuffer.length
, startOffset = 2
, secondByte = dataLength;
if (dataLength > 65536) {
startOffset = 10;
secondByte = 127;
}
else if (dataLength > 125) {
startOffset = 4;
secondByte = 126;
}
var outputBuffer = new Buffer(dataLength + startOffset);
outputBuffer[0] = opcode;
outputBuffer[1] = secondByte;
dataBuffer.copy(outputBuffer, startOffset);
switch (secondByte) {
case 126:
outputBuffer[2] = dataLength >>> 8;
outputBuffer[3] = dataLength % 256;
break;
case 127:
var l = dataLength;
for (var i = 1; i <= 8; ++i) {
outputBuffer[startOffset - i] = l & 0xff;
l >>>= 8;
}
}
return outputBuffer;
};
/**
* Closes the connection.
*
* @api private
*/
WebSocket.prototype.doClose = function () {
this.socket.end();
};
/**
* WebSocket parser
*
* @api public
*/
function Parser () {
this.state = {
activeFragmentedOperation: null,
lastFragment: false,
masked: false,
opcode: 0
};
this.overflow = null;
this.expectOffset = 0;
this.expectBuffer = null;
this.expectHandler = null;
this.currentMessage = '';
var self = this;
this.opcodeHandlers = {
// text
'1': function(data) {
var finish = function(mask, data) {
self.currentMessage += self.unmask(mask, data);
if (self.state.lastFragment) {
self.emit('data', self.currentMessage);
self.currentMessage = '';
}
self.endPacket();
}
var expectData = function(length) {
if (self.state.masked) {
self.expect('Mask', 4, function(data) {
var mask = data;
self.expect('Data', length, function(data) {
finish(mask, data);
});
});
}
else {
self.expect('Data', length, function(data) {
finish(null, data);
});
}
}
// decode length
var firstLength = data[1] & 0x7f;
if (firstLength < 126) {
expectData(firstLength);
}
else if (firstLength == 126) {
self.expect('Length', 2, function(data) {
expectData(util.unpack(data));
});
}
else if (firstLength == 127) {
self.expect('Length', 8, function(data) {
if (util.unpack(data.slice(0, 4)) != 0) {
self.error('packets with length spanning more than 32 bit is currently not supported');
return;
}
var lengthBytes = data.slice(4); // note: cap to 32 bit length
expectData(util.unpack(data));
});
}
},
// binary
'2': function(data) {
var finish = function(mask, data) {
if (typeof self.currentMessage == 'string') self.currentMessage = []; // build a buffer list
self.currentMessage.push(self.unmask(mask, data, true));
if (self.state.lastFragment) {
self.emit('binary', self.concatBuffers(self.currentMessage));
self.currentMessage = '';
}
self.endPacket();
}
var expectData = function(length) {
if (self.state.masked) {
self.expect('Mask', 4, function(data) {
var mask = data;
self.expect('Data', length, function(data) {
finish(mask, data);
});
});
}
else {
self.expect('Data', length, function(data) {
finish(null, data);
});
}
}
// decode length
var firstLength = data[1] & 0x7f;
if (firstLength < 126) {
expectData(firstLength);
}
else if (firstLength == 126) {
self.expect('Length', 2, function(data) {
expectData(util.unpack(data));
});
}
else if (firstLength == 127) {
self.expect('Length', 8, function(data) {
if (util.unpack(data.slice(0, 4)) != 0) {
self.error('packets with length spanning more than 32 bit is currently not supported');
return;
}
var lengthBytes = data.slice(4); // note: cap to 32 bit length
expectData(util.unpack(data));
});
}
},
// close
'8': function(data) {
self.emit('close');
self.reset();
},
// ping
'9': function(data) {
if (self.state.lastFragment == false) {
self.error('fragmented ping is not supported');
return;
}
var finish = function(mask, data) {
self.emit('ping', self.unmask(mask, data));
self.endPacket();
}
var expectData = function(length) {
if (self.state.masked) {
self.expect('Mask', 4, function(data) {
var mask = data;
self.expect('Data', length, function(data) {
finish(mask, data);
});
});
}
else {
self.expect('Data', length, function(data) {
finish(null, data);
});
}
}
// decode length
var firstLength = data[1] & 0x7f;
if (firstLength == 0) {
finish(null, null);
}
else if (firstLength < 126) {
expectData(firstLength);
}
else if (firstLength == 126) {
self.expect('Length', 2, function(data) {
expectData(util.unpack(data));
});
}
else if (firstLength == 127) {
self.expect('Length', 8, function(data) {
expectData(util.unpack(data));
});
}
}
}
this.expect('Opcode', 2, this.processPacket);
};
/**
* Inherits from EventEmitter.
*/
Parser.prototype.__proto__ = EventEmitter.prototype;
/**
* Add new data to the parser.
*
* @api public
*/
Parser.prototype.add = function(data) {
if (this.expectBuffer == null) {
this.addToOverflow(data);
return;
}
var toRead = Math.min(data.length, this.expectBuffer.length - this.expectOffset);
data.copy(this.expectBuffer, this.expectOffset, 0, toRead);
this.expectOffset += toRead;
if (toRead < data.length) {
// at this point the overflow buffer shouldn't at all exist
this.overflow = new Buffer(data.length - toRead);
data.copy(this.overflow, 0, toRead, toRead + this.overflow.length);
}
if (this.expectOffset == this.expectBuffer.length) {
var bufferForHandler = this.expectBuffer;
this.expectBuffer = null;
this.expectOffset = 0;
this.expectHandler.call(this, bufferForHandler);
}
}
/**
* Adds a piece of data to the overflow.
*
* @api private
*/
Parser.prototype.addToOverflow = function(data) {
if (this.overflow == null) this.overflow = data;
else {
var prevOverflow = this.overflow;
this.overflow = new Buffer(this.overflow.length + data.length);
prevOverflow.copy(this.overflow, 0);
data.copy(this.overflow, prevOverflow.length);
}
}
/**
* Waits for a certain amount of bytes to be available, then fires a callback.
*
* @api private
*/
Parser.prototype.expect = function(what, length, handler) {
this.expectBuffer = new Buffer(length);
this.expectOffset = 0;
this.expectHandler = handler;
if (this.overflow != null) {
var toOverflow = this.overflow;
this.overflow = null;
this.add(toOverflow);
}
}
/**
* Start processing a new packet.
*
* @api private
*/
Parser.prototype.processPacket = function (data) {
if ((data[0] & 0x70) != 0) {
this.error('reserved fields must be empty');
}
this.state.lastFragment = (data[0] & 0x80) == 0x80;
this.state.masked = (data[1] & 0x80) == 0x80;
var opcode = data[0] & 0xf;
if (opcode == 0) {
// continuation frame
this.state.opcode = this.state.activeFragmentedOperation;
if (!(this.state.opcode == 1 || this.state.opcode == 2)) {
this.error('continuation frame cannot follow current opcode')
return;
}
}
else {
this.state.opcode = opcode;
if (this.state.lastFragment === false) {
this.state.activeFragmentedOperation = opcode;
}
}
var handler = this.opcodeHandlers[this.state.opcode];
if (typeof handler == 'undefined') this.error('no handler for opcode ' + this.state.opcode);
else handler(data);
}
/**
* Endprocessing a packet.
*
* @api private
*/
Parser.prototype.endPacket = function() {
this.expectOffset = 0;
this.expectBuffer = null;
this.expectHandler = null;
if (this.state.lastFragment && this.state.opcode == this.state.activeFragmentedOperation) {
// end current fragmented operation
this.state.activeFragmentedOperation = null;
}
this.state.lastFragment = false;
this.state.opcode = this.state.activeFragmentedOperation != null ? this.state.activeFragmentedOperation : 0;
this.state.masked = false;
this.expect('Opcode', 2, this.processPacket);
}
/**
* Reset the parser state.
*
* @api private
*/
Parser.prototype.reset = function() {
this.state = {
activeFragmentedOperation: null,
lastFragment: false,
masked: false,
opcode: 0
};
this.expectOffset = 0;
this.expectBuffer = null;
this.expectHandler = null;
this.overflow = null;
this.currentMessage = '';
}
/**
* Unmask received data.
*
* @api private
*/
Parser.prototype.unmask = function (mask, buf, binary) {
if (mask != null) {
for (var i = 0, ll = buf.length; i < ll; i++) {
buf[i] ^= mask[i % 4];
}
}
if (binary) return buf;
return buf != null ? buf.toString('utf8') : '';
}
/**
* Concatenates a list of buffers.
*
* @api private
*/
Parser.prototype.concatBuffers = function(buffers) {
var length = 0;
for (var i = 0, l = buffers.length; i < l; ++i) {
length += buffers[i].length;
}
var mergedBuffer = new Buffer(length);
var offset = 0;
for (var i = 0, l = buffers.length; i < l; ++i) {
buffers[i].copy(mergedBuffer, offset);
offset += buffers[i].length;
}
return mergedBuffer;
}
/**
* Handles an error
*
* @api private
*/
Parser.prototype.error = function (reason) {
this.reset();
this.emit('error', reason);
return this;
};

View File

@@ -1,623 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module requirements.
*/
var Transport = require('../../transport')
, EventEmitter = process.EventEmitter
, crypto = require('crypto')
, url = require('url')
, parser = require('../../parser')
, util = require('../../util');
/**
* Export the constructor.
*/
exports = module.exports = WebSocket;
exports.Parser = Parser;
/**
* HTTP interface constructor. Interface compatible with all transports that
* depend on request-response cycles.
*
* @api public
*/
function WebSocket (mng, data, req) {
// parser
var self = this;
this.manager = mng;
this.parser = new Parser();
this.parser.on('data', function (packet) {
self.onMessage(parser.decodePacket(packet));
});
this.parser.on('ping', function () {
// version 8 ping => pong
try {
self.socket.write('\u008a\u0000');
}
catch (e) {
self.end();
return;
}
});
this.parser.on('close', function () {
self.end();
});
this.parser.on('error', function (reason) {
self.log.warn(self.name + ' parser error: ' + reason);
self.end();
});
Transport.call(this, mng, data, req);
};
/**
* Inherits from Transport.
*/
WebSocket.prototype.__proto__ = Transport.prototype;
/**
* Transport name
*
* @api public
*/
WebSocket.prototype.name = 'websocket';
/**
* Websocket draft version
*
* @api public
*/
WebSocket.prototype.protocolVersion = '16';
/**
* Called when the socket connects.
*
* @api private
*/
WebSocket.prototype.onSocketConnect = function () {
var self = this;
if (typeof this.req.headers.upgrade === 'undefined' ||
this.req.headers.upgrade.toLowerCase() !== 'websocket') {
this.log.warn(this.name + ' connection invalid');
this.end();
return;
}
var origin = this.req.headers['origin']
, location = ((this.manager.settings['match origin protocol'] ?
origin.match(/^https/) : this.socket.encrypted) ?
'wss' : 'ws')
+ '://' + this.req.headers.host + this.req.url;
if (!this.verifyOrigin(origin)) {
this.log.warn(this.name + ' connection invalid: origin mismatch');
this.end();
return;
}
if (!this.req.headers['sec-websocket-key']) {
this.log.warn(this.name + ' connection invalid: received no key');
this.end();
return;
}
// calc key
var key = this.req.headers['sec-websocket-key'];
var shasum = crypto.createHash('sha1');
shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
key = shasum.digest('base64');
var headers = [
'HTTP/1.1 101 Switching Protocols'
, 'Upgrade: websocket'
, 'Connection: Upgrade'
, 'Sec-WebSocket-Accept: ' + key
];
try {
this.socket.write(headers.concat('', '').join('\r\n'));
this.socket.setTimeout(0);
this.socket.setNoDelay(true);
} catch (e) {
this.end();
return;
}
this.socket.on('data', function (data) {
self.parser.add(data);
});
};
/**
* Verifies the origin of a request.
*
* @api private
*/
WebSocket.prototype.verifyOrigin = function (origin) {
var origins = this.manager.get('origins');
if (origin === 'null') origin = '*';
if (origins.indexOf('*:*') !== -1) {
return true;
}
if (origin) {
try {
var parts = url.parse(origin);
parts.port = parts.port || 80;
var ok =
~origins.indexOf(parts.hostname + ':' + parts.port) ||
~origins.indexOf(parts.hostname + ':*') ||
~origins.indexOf('*:' + parts.port);
if (!ok) this.log.warn('illegal origin: ' + origin);
return ok;
} catch (ex) {
this.log.warn('error parsing origin');
}
}
else {
this.log.warn('origin missing from websocket call, yet required by config');
}
return false;
};
/**
* Writes to the socket.
*
* @api private
*/
WebSocket.prototype.write = function (data) {
if (this.open) {
var buf = this.frame(0x81, data);
try {
this.socket.write(buf, 'binary');
}
catch (e) {
this.end();
return;
}
this.log.debug(this.name + ' writing', data);
}
};
/**
* Writes a payload.
*
* @api private
*/
WebSocket.prototype.payload = function (msgs) {
for (var i = 0, l = msgs.length; i < l; i++) {
this.write(msgs[i]);
}
return this;
};
/**
* Frame server-to-client output as a text packet.
*
* @api private
*/
WebSocket.prototype.frame = function (opcode, str) {
var dataBuffer = new Buffer(str)
, dataLength = dataBuffer.length
, startOffset = 2
, secondByte = dataLength;
if (dataLength > 65536) {
startOffset = 10;
secondByte = 127;
}
else if (dataLength > 125) {
startOffset = 4;
secondByte = 126;
}
var outputBuffer = new Buffer(dataLength + startOffset);
outputBuffer[0] = opcode;
outputBuffer[1] = secondByte;
dataBuffer.copy(outputBuffer, startOffset);
switch (secondByte) {
case 126:
outputBuffer[2] = dataLength >>> 8;
outputBuffer[3] = dataLength % 256;
break;
case 127:
var l = dataLength;
for (var i = 1; i <= 8; ++i) {
outputBuffer[startOffset - i] = l & 0xff;
l >>>= 8;
}
}
return outputBuffer;
};
/**
* Closes the connection.
*
* @api private
*/
WebSocket.prototype.doClose = function () {
this.socket.end();
};
/**
* WebSocket parser
*
* @api public
*/
function Parser () {
this.state = {
activeFragmentedOperation: null,
lastFragment: false,
masked: false,
opcode: 0
};
this.overflow = null;
this.expectOffset = 0;
this.expectBuffer = null;
this.expectHandler = null;
this.currentMessage = '';
var self = this;
this.opcodeHandlers = {
// text
'1': function(data) {
var finish = function(mask, data) {
self.currentMessage += self.unmask(mask, data);
if (self.state.lastFragment) {
self.emit('data', self.currentMessage);
self.currentMessage = '';
}
self.endPacket();
}
var expectData = function(length) {
if (self.state.masked) {
self.expect('Mask', 4, function(data) {
var mask = data;
self.expect('Data', length, function(data) {
finish(mask, data);
});
});
}
else {
self.expect('Data', length, function(data) {
finish(null, data);
});
}
}
// decode length
var firstLength = data[1] & 0x7f;
if (firstLength < 126) {
expectData(firstLength);
}
else if (firstLength == 126) {
self.expect('Length', 2, function(data) {
expectData(util.unpack(data));
});
}
else if (firstLength == 127) {
self.expect('Length', 8, function(data) {
if (util.unpack(data.slice(0, 4)) != 0) {
self.error('packets with length spanning more than 32 bit is currently not supported');
return;
}
var lengthBytes = data.slice(4); // note: cap to 32 bit length
expectData(util.unpack(data));
});
}
},
// binary
'2': function(data) {
var finish = function(mask, data) {
if (typeof self.currentMessage == 'string') self.currentMessage = []; // build a buffer list
self.currentMessage.push(self.unmask(mask, data, true));
if (self.state.lastFragment) {
self.emit('binary', self.concatBuffers(self.currentMessage));
self.currentMessage = '';
}
self.endPacket();
}
var expectData = function(length) {
if (self.state.masked) {
self.expect('Mask', 4, function(data) {
var mask = data;
self.expect('Data', length, function(data) {
finish(mask, data);
});
});
}
else {
self.expect('Data', length, function(data) {
finish(null, data);
});
}
}
// decode length
var firstLength = data[1] & 0x7f;
if (firstLength < 126) {
expectData(firstLength);
}
else if (firstLength == 126) {
self.expect('Length', 2, function(data) {
expectData(util.unpack(data));
});
}
else if (firstLength == 127) {
self.expect('Length', 8, function(data) {
if (util.unpack(data.slice(0, 4)) != 0) {
self.error('packets with length spanning more than 32 bit is currently not supported');
return;
}
var lengthBytes = data.slice(4); // note: cap to 32 bit length
expectData(util.unpack(data));
});
}
},
// close
'8': function(data) {
self.emit('close');
self.reset();
},
// ping
'9': function(data) {
if (self.state.lastFragment == false) {
self.error('fragmented ping is not supported');
return;
}
var finish = function(mask, data) {
self.emit('ping', self.unmask(mask, data));
self.endPacket();
}
var expectData = function(length) {
if (self.state.masked) {
self.expect('Mask', 4, function(data) {
var mask = data;
self.expect('Data', length, function(data) {
finish(mask, data);
});
});
}
else {
self.expect('Data', length, function(data) {
finish(null, data);
});
}
}
// decode length
var firstLength = data[1] & 0x7f;
if (firstLength == 0) {
finish(null, null);
}
else if (firstLength < 126) {
expectData(firstLength);
}
else if (firstLength == 126) {
self.expect('Length', 2, function(data) {
expectData(util.unpack(data));
});
}
else if (firstLength == 127) {
self.expect('Length', 8, function(data) {
expectData(util.unpack(data));
});
}
}
}
this.expect('Opcode', 2, this.processPacket);
};
/**
* Inherits from EventEmitter.
*/
Parser.prototype.__proto__ = EventEmitter.prototype;
/**
* Add new data to the parser.
*
* @api public
*/
Parser.prototype.add = function(data) {
if (this.expectBuffer == null) {
this.addToOverflow(data);
return;
}
var toRead = Math.min(data.length, this.expectBuffer.length - this.expectOffset);
data.copy(this.expectBuffer, this.expectOffset, 0, toRead);
this.expectOffset += toRead;
if (toRead < data.length) {
// at this point the overflow buffer shouldn't at all exist
this.overflow = new Buffer(data.length - toRead);
data.copy(this.overflow, 0, toRead, toRead + this.overflow.length);
}
if (this.expectOffset == this.expectBuffer.length) {
var bufferForHandler = this.expectBuffer;
this.expectBuffer = null;
this.expectOffset = 0;
this.expectHandler.call(this, bufferForHandler);
}
}
/**
* Adds a piece of data to the overflow.
*
* @api private
*/
Parser.prototype.addToOverflow = function(data) {
if (this.overflow == null) this.overflow = data;
else {
var prevOverflow = this.overflow;
this.overflow = new Buffer(this.overflow.length + data.length);
prevOverflow.copy(this.overflow, 0);
data.copy(this.overflow, prevOverflow.length);
}
}
/**
* Waits for a certain amount of bytes to be available, then fires a callback.
*
* @api private
*/
Parser.prototype.expect = function(what, length, handler) {
this.expectBuffer = new Buffer(length);
this.expectOffset = 0;
this.expectHandler = handler;
if (this.overflow != null) {
var toOverflow = this.overflow;
this.overflow = null;
this.add(toOverflow);
}
}
/**
* Start processing a new packet.
*
* @api private
*/
Parser.prototype.processPacket = function (data) {
if ((data[0] & 0x70) != 0) {
this.error('reserved fields must be empty');
return;
}
this.state.lastFragment = (data[0] & 0x80) == 0x80;
this.state.masked = (data[1] & 0x80) == 0x80;
var opcode = data[0] & 0xf;
if (opcode == 0) {
// continuation frame
this.state.opcode = this.state.activeFragmentedOperation;
if (!(this.state.opcode == 1 || this.state.opcode == 2)) {
this.error('continuation frame cannot follow current opcode')
return;
}
}
else {
this.state.opcode = opcode;
if (this.state.lastFragment === false) {
this.state.activeFragmentedOperation = opcode;
}
}
var handler = this.opcodeHandlers[this.state.opcode];
if (typeof handler == 'undefined') this.error('no handler for opcode ' + this.state.opcode);
else handler(data);
}
/**
* Endprocessing a packet.
*
* @api private
*/
Parser.prototype.endPacket = function() {
this.expectOffset = 0;
this.expectBuffer = null;
this.expectHandler = null;
if (this.state.lastFragment && this.state.opcode == this.state.activeFragmentedOperation) {
// end current fragmented operation
this.state.activeFragmentedOperation = null;
}
this.state.lastFragment = false;
this.state.opcode = this.state.activeFragmentedOperation != null ? this.state.activeFragmentedOperation : 0;
this.state.masked = false;
this.expect('Opcode', 2, this.processPacket);
}
/**
* Reset the parser state.
*
* @api private
*/
Parser.prototype.reset = function() {
this.state = {
activeFragmentedOperation: null,
lastFragment: false,
masked: false,
opcode: 0
};
this.expectOffset = 0;
this.expectBuffer = null;
this.expectHandler = null;
this.overflow = null;
this.currentMessage = '';
}
/**
* Unmask received data.
*
* @api private
*/
Parser.prototype.unmask = function (mask, buf, binary) {
if (mask != null) {
for (var i = 0, ll = buf.length; i < ll; i++) {
buf[i] ^= mask[i % 4];
}
}
if (binary) return buf;
return buf != null ? buf.toString('utf8') : '';
}
/**
* Concatenates a list of buffers.
*
* @api private
*/
Parser.prototype.concatBuffers = function(buffers) {
var length = 0;
for (var i = 0, l = buffers.length; i < l; ++i) {
length += buffers[i].length;
}
var mergedBuffer = new Buffer(length);
var offset = 0;
for (var i = 0, l = buffers.length; i < l; ++i) {
buffers[i].copy(mergedBuffer, offset);
offset += buffers[i].length;
}
return mergedBuffer;
}
/**
* Handles an error
*
* @api private
*/
Parser.prototype.error = function (reason) {
this.reset();
this.emit('error', reason);
return this;
};

View File

@@ -1,11 +0,0 @@
/**
* Export websocket versions.
*/
module.exports = {
7: require('./hybi-07-12'),
8: require('./hybi-07-12'),
13: require('./hybi-16'),
default: require('./default')
};

View File

@@ -1,72 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module requirements.
*/
var HTTPPolling = require('./http-polling');
/**
* Export the constructor.
*/
exports = module.exports = XHRPolling;
/**
* Ajax polling transport.
*
* @api public
*/
function XHRPolling (mng, data, req) {
HTTPPolling.call(this, mng, data, req);
};
/**
* Inherits from Transport.
*/
XHRPolling.prototype.__proto__ = HTTPPolling.prototype;
/**
* Transport name
*
* @api public
*/
XHRPolling.prototype.name = 'xhr-polling';
/**
* Frames data prior to write.
*
* @api private
*/
XHRPolling.prototype.doWrite = function (data) {
HTTPPolling.prototype.doWrite.call(this);
var origin = this.req.headers.origin
, headers = {
'Content-Type': 'text/plain; charset=UTF-8'
, 'Content-Length': data === undefined ? 0 : Buffer.byteLength(data)
, 'Connection': 'Keep-Alive'
};
if (origin) {
// https://developer.mozilla.org/En/HTTP_Access_Control
headers['Access-Control-Allow-Origin'] = '*';
if (this.req.headers.cookie) {
headers['Access-Control-Allow-Credentials'] = 'true';
}
}
this.response.writeHead(200, headers);
this.response.write(data);
this.log.debug(this.name + ' writing', data);
};

View File

@@ -1,50 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module dependencies.
*/
/**
* Converts an enumerable to an array.
*
* @api public
*/
exports.toArray = function (enu) {
var arr = [];
for (var i = 0, l = enu.length; i < l; i++)
arr.push(enu[i]);
return arr;
};
/**
* Unpacks a buffer to a number.
*
* @api public
*/
exports.unpack = function (buffer) {
var n = 0;
for (var i = 0; i < buffer.length; ++i) {
n = (i == 0) ? buffer[i] : (n * 256) + buffer[i];
}
return n;
}
/**
* Left pads a string.
*
* @api public
*/
exports.padl = function (s,n,c) {
return new Array(1 + n - s.length).join(c) + s;
}

View File

@@ -1,6 +1,6 @@
{
"name": "socket.io"
, "version": "0.8.7"
, "version": "1.0.0-alpha1"
, "description": "Real-time apps made cross-browser & easy with a WebSocket-like API"
, "homepage": "http://socket.io"
, "keywords": ["websocket", "socket", "realtime", "socket.io", "comet", "ajax"]
@@ -11,24 +11,20 @@
, { "name": "Vladimir Dronnikov", "email": "dronnikov@gmail.com" }
, { "name": "Einar Otto Stangvik", "email": "einaros@gmail.com" }
]
, "repository":{
, "repository": {
"type": "git"
, "url": "https://github.com/LearnBoost/socket.io.git"
, "url": "https://github.com/learnboost/socket.io.git"
}
, "main": "./lib/socket.io"
, "dependencies": {
"socket.io-client": "0.8.7"
, "policyfile": "0.0.4"
, "redis": "0.6.7"
"engine.io": "0.1.0"
, "socket.io-client": "1.0.0-alpha1"
}
, "devDependencies": {
"expresso": "0.9.2"
, "should": "0.0.4"
, "benchmark": "0.2.2"
, "microtime": "0.1.3-1"
, "colors": "0.5.1"
"mocha": "*"
, "expect.js": "*"
, "superagent": "*"
}
, "main": "index"
, "engines": { "node": ">= 0.4.0" }
, "scripts": {
"test": "make test"
}

View File

@@ -1,27 +0,0 @@
Copyright (c) 2010, Peter Griess <pg@std.in>
All rights reserved.
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.
* Neither the name of node-websocket-client nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 OR CONTRIBUTORS 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.

View File

@@ -1,22 +0,0 @@
# This makefile exists to help run tests.
#
# If TEST_UNIX is a non-empty value, runs tests for UNIX sockets. This
# functionality is not in node-websocket-server at the moment.
.PHONY: test
all: test test-unix
test:
for f in `ls -1 test/test-*.js | grep -v unix` ; do \
echo $$f ; \
node $$f ; \
done
test-unix:
if [[ -n "$$TEST_UNIX" ]] ; then \
for f in `ls -1 test/test-*.js | grep unix` ; do \
echo $$f ; \
node $$f ; \
done \
fi

View File

@@ -1,41 +0,0 @@
A prototype [Web Socket](http://www.whatwg.org/specs/web-socket-protocol/)
client implementation for [node.js](http://nodejs.org).
Tested with
[miksago/node-websocket-server](http://github.com/miksago/node-websocket-server)
v1.2.00.
Requires [nodejs](http://nodejs.org) 0.1.98 or later.
## Installation
Install this using `npm` as follows
npm install websocket-client
... or just dump `lib/websocket.js` in your `$NODE_PATH`.
## Usage
var sys = require('sys');
var WebSocket = require('websocket').WebSocket;
var ws = new WebSocket('ws://localhost:8000/biff', 'borf');
ws.addListener('data', function(buf) {
sys.debug('Got data: ' + sys.inspect(buf));
});
ws.onmessage = function(m) {
sys.debug('Got message: ' + m);
}
## API
This supports the `send()` and `onmessage()` APIs. The `WebSocket` object will
also emit `data` events that are node `Buffer` objects, in case you want to
work with something lower-level than strings.
## Transports
Multiple transports are supported, indicated by the scheme provided to the
`WebSocket` constructor. `ws://` is a standard TCP-based Web Socket;
`ws+unix://` allows connection to a UNIX socket at the given path.

View File

@@ -1,12 +0,0 @@
var sys = require('sys');
var WebSocket = require('../lib/websocket').WebSocket;
var ws = new WebSocket('ws+unix://' + process.argv[2], 'boffo');
ws.addListener('message', function(d) {
sys.debug('Received message: ' + d.toString('utf8'));
});
ws.addListener('open', function() {
ws.send('This is a message', 1);
});

View File

@@ -1,10 +0,0 @@
var sys = require('sys');
var WebSocket = require('../lib/websocket').WebSocket;
var ws = new WebSocket('ws://localhost:8000/biff', 'borf');
ws.addListener('data', function(buf) {
sys.debug('Got data: ' + sys.inspect(buf));
});
ws.onmessage = function(m) {
sys.debug('Got message: ' + m);
}

View File

@@ -1,13 +0,0 @@
var sys = require('sys');
var ws = require('websocket-server/ws');
var srv = ws.createServer({ debug : true});
srv.addListener('connection', function(s) {
sys.debug('Got a connection!');
s._req.socket.addListener('fd', function(fd) {
sys.debug('Got an fd: ' + fd);
});
});
srv.listen(process.argv[2]);

View File

@@ -1,617 +0,0 @@
var assert = require('assert');
var buffer = require('buffer');
var crypto = require('crypto');
var events = require('events');
var http = require('http');
var net = require('net');
var urllib = require('url');
var sys = require('util');
var FRAME_NO = 0;
var FRAME_LO = 1;
var FRAME_HI = 2;
// Values for readyState as per the W3C spec
var CONNECTING = 0;
var OPEN = 1;
var CLOSING = 2;
var CLOSED = 3;
var debugLevel = parseInt(process.env.NODE_DEBUG, 16);
var debug = (debugLevel & 0x4) ?
function() { sys.error.apply(this, arguments); } :
function() { };
// Generate a Sec-WebSocket-* value
var createSecretKey = function() {
// How many spaces will we be inserting?
var numSpaces = 1 + Math.floor(Math.random() * 12);
assert.ok(1 <= numSpaces && numSpaces <= 12);
// What is the numerical value of our key?
var keyVal = (Math.floor(
Math.random() * (4294967295 / numSpaces)
) * numSpaces);
// Our string starts with a string representation of our key
var s = keyVal.toString();
// Insert 'numChars' worth of noise in the character ranges
// [0x21, 0x2f] (14 characters) and [0x3a, 0x7e] (68 characters)
var numChars = 1 + Math.floor(Math.random() * 12);
assert.ok(1 <= numChars && numChars <= 12);
for (var i = 0; i < numChars; i++) {
var pos = Math.floor(Math.random() * s.length + 1);
var c = Math.floor(Math.random() * (14 + 68));
c = (c <= 14) ?
String.fromCharCode(c + 0x21) :
String.fromCharCode((c - 14) + 0x3a);
s = s.substring(0, pos) + c + s.substring(pos, s.length);
}
// We shoudln't have any spaces in our value until we insert them
assert.equal(s.indexOf(' '), -1);
// Insert 'numSpaces' worth of spaces
for (var i = 0; i < numSpaces; i++) {
var pos = Math.floor(Math.random() * (s.length - 1)) + 1;
s = s.substring(0, pos) + ' ' + s.substring(pos, s.length);
}
assert.notEqual(s.charAt(0), ' ');
assert.notEqual(s.charAt(s.length), ' ');
return s;
};
// Generate a challenge sequence
var createChallenge = function() {
var c = '';
for (var i = 0; i < 8; i++) {
c += String.fromCharCode(Math.floor(Math.random() * 255));
}
return c;
};
// Get the value of a secret key string
//
// This strips non-digit values and divides the result by the number of
// spaces found.
var secretKeyValue = function(sk) {
var ns = 0;
var v = 0;
for (var i = 0; i < sk.length; i++) {
var cc = sk.charCodeAt(i);
if (cc == 0x20) {
ns++;
} else if (0x30 <= cc && cc <= 0x39) {
v = v * 10 + cc - 0x30;
}
}
return Math.floor(v / ns);
}
// Get the to-be-hashed value of a secret key string
//
// This takes the result of secretKeyValue() and encodes it in a big-endian
// byte string
var secretKeyHashValue = function(sk) {
var skv = secretKeyValue(sk);
var hv = '';
hv += String.fromCharCode((skv >> 24) & 0xff);
hv += String.fromCharCode((skv >> 16) & 0xff);
hv += String.fromCharCode((skv >> 8) & 0xff);
hv += String.fromCharCode((skv >> 0) & 0xff);
return hv;
};
// Compute the secret key signature based on two secret key strings and some
// handshaking data.
var computeSecretKeySignature = function(s1, s2, hs) {
assert.equal(hs.length, 8);
var hash = crypto.createHash('md5');
hash.update(secretKeyHashValue(s1));
hash.update(secretKeyHashValue(s2));
hash.update(hs);
return hash.digest('binary');
};
// Return a hex representation of the given binary string; used for debugging
var str2hex = function(str) {
var hexChars = [
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f'
];
var out = '';
for (var i = 0; i < str.length; i++) {
var c = str.charCodeAt(i);
out += hexChars[(c & 0xf0) >>> 4];
out += hexChars[c & 0x0f];
out += ' ';
}
return out.trim();
};
// Get the scheme for a URL, undefined if none is found
var getUrlScheme = function(url) {
var i = url.indexOf(':');
if (i == -1) {
return undefined;
}
return url.substring(0, i);
};
// Set a constant on the given object
var setConstant = function(obj, name, value) {
Object.defineProperty(obj, name, {
get : function() {
return value;
}
});
};
// WebSocket object
//
// This is intended to conform (mostly) to http://dev.w3.org/html5/websockets/
//
// N.B. Arguments are parsed in the anonymous function at the bottom of the
// constructor.
var WebSocket = function(url, proto, opts) {
events.EventEmitter.call(this);
// Retain a reference to our object
var self = this;
// State of our end of the connection
var readyState = CONNECTING;
// Whether or not the server has sent a close handshake
var serverClosed = false;
// Our underlying net.Stream instance
var stream = undefined;
opts = opts || {
origin : 'http://www.example.com'
};
// Frame parsing functions
//
// These read data from the given buffer starting at the given offset,
// looking for the end of the current frame. If found, the current frame is
// emitted and the function returns. Only a single frame is processed at a
// time.
//
// The number of bytes read to complete a frame is returned, which the
// caller is to use to advance along its buffer. If 0 is returned, no
// completed frame bytes were found, and the caller should probably enqueue
// the buffer as a continuation of the current message. If a complete frame
// is read, the function is responsible for resting 'frameType'.
// Framing data
var frameType = FRAME_NO;
var bufs = [];
var bufsBytes = 0;
// Frame-parsing functions
var frameFuncs = [
// FRAME_NO
function(buf, off) {
if (buf[off] & 0x80) {
frameType = FRAME_HI;
} else {
frameType = FRAME_LO;
}
return 1;
},
// FRAME_LO
function(buf, off) {
debug('frame_lo(' + sys.inspect(buf) + ', ' + off + ')');
// Find the first instance of 0xff, our terminating byte
for (var i = off; i < buf.length && buf[i] != 0xff; i++)
;
// We didn't find a terminating byte
if (i >= buf.length) {
return 0;
}
// We found a terminating byte; collect all bytes into a single buffer
// and emit it
var mb = null;
if (bufs.length == 0) {
mb = buf.slice(off, i);
} else {
mb = new buffer.Buffer(bufsBytes + i);
var mbOff = 0;
bufs.forEach(function(b) {
b.copy(mb, mbOff, 0, b.length);
mbOff += b.length;
});
assert.equal(mbOff, bufsBytes);
// Don't call Buffer.copy() if we're coping 0 bytes. Rather
// than being a no-op, this will trigger a range violation on
// the destination.
if (i > 0) {
buf.copy(mb, mbOff, off, i);
}
// We consumed all of the buffers that we'd been saving; clear
// things out
bufs = [];
bufsBytes = 0;
}
process.nextTick(function() {
var b = mb;
return function() {
var m = b.toString('utf8');
self.emit('data', b);
self.emit('message', m); // wss compat
if (self.onmessage) {
self.onmessage({data: m});
}
};
}());
frameType = FRAME_NO;
return i - off + 1;
},
// FRAME_HI
function(buf, off) {
debug('frame_hi(' + sys.inspect(buf) + ', ' + off + ')');
if (buf[off] !== 0) {
throw new Error('High-byte framing not supported.');
}
serverClosed = true;
return 1;
}
];
// Handle data coming from our socket
var dataListener = function(buf) {
if (buf.length <= 0 || serverClosed) {
return;
}
debug('dataListener(' + sys.inspect(buf) + ')');
var off = 0;
var consumed = 0;
do {
if (frameType < 0 || frameFuncs.length <= frameType) {
throw new Error('Unexpected frame type: ' + frameType);
}
assert.equal(bufs.length === 0, bufsBytes === 0);
assert.ok(off < buf.length);
consumed = frameFuncs[frameType](buf, off);
off += consumed;
} while (!serverClosed && consumed > 0 && off < buf.length);
if (serverClosed) {
serverCloseHandler();
}
if (consumed == 0) {
bufs.push(buf.slice(off, buf.length));
bufsBytes += buf.length - off;
}
};
// Handle incoming file descriptors
var fdListener = function(fd) {
self.emit('fd', fd);
};
// Handle errors from any source (HTTP client, stream, etc)
var errorListener = function(e) {
process.nextTick(function() {
self.emit('wserror', e);
if (self.onerror) {
self.onerror(e);
}
});
};
// Finish the closing process; destroy the socket and tell the application
// that we've closed.
var finishClose = self.finishClose = function() {
readyState = CLOSED;
if (stream) {
stream.end();
stream.destroy();
stream = undefined;
}
process.nextTick(function() {
self.emit('close');
if (self.onclose) {
self.onclose();
}
});
};
// Send a close frame to the server
var sendClose = function() {
assert.equal(OPEN, readyState);
readyState = CLOSING;
stream.write('\xff\x00', 'binary');
};
// Handle a close packet sent from the server
var serverCloseHandler = function() {
assert.ok(serverClosed);
assert.ok(readyState === OPEN || readyState === CLOSING);
bufs = [];
bufsBytes = 0;
// Handle state transitions asynchronously so that we don't change
// readyState before the application has had a chance to process data
// events which are already in the delivery pipeline. For example, a
// 'data' event could be delivered with a readyState of CLOSING if we
// received both frames in the same packet.
process.nextTick(function() {
if (readyState === OPEN) {
sendClose();
}
finishClose();
});
};
// External API
self.close = function(timeout) {
if (readyState === CONNECTING) {
// If we're still in the process of connecting, the server is not
// in a position to understand our close frame. Just nuke the
// connection and call it a day.
finishClose();
} else if (readyState === OPEN) {
sendClose();
if (timeout) {
setTimeout(finishClose, timeout * 1000);
}
}
};
self.send = function(str, fd) {
if (readyState != OPEN) {
return;
}
stream.write('\x00', 'binary');
stream.write(str, 'utf8', fd);
stream.write('\xff', 'binary');
};
// wss compat
self.write = self.send;
setConstant(self, 'url', url);
Object.defineProperty(self, 'readyState', {
get : function() {
return readyState;
}
});
// Connect and perform handshaking with the server
(function() {
// Parse constructor arguments
if (!url) {
throw new Error('Url and must be specified.');
}
// Secrets used for handshaking
var key1 = createSecretKey();
var key2 = createSecretKey();
var challenge = createChallenge();
debug(
'key1=\'' + str2hex(key1) + '\'; ' +
'key2=\'' + str2hex(key2) + '\'; ' +
'challenge=\'' + str2hex(challenge) + '\''
);
var httpHeaders = {
'Connection' : 'Upgrade',
'Upgrade' : 'WebSocket',
'Sec-WebSocket-Key1' : key1,
'Sec-WebSocket-Key2' : key2
};
if (opts.origin) {
httpHeaders['Origin'] = opts.origin;
}
if (proto) {
httpHeaders['Sec-WebSocket-Protocol'] = proto;
}
var httpPath = '/';
// Create the HTTP client that we'll use for handshaking. We'll cannabalize
// its socket via the 'upgrade' event and leave it to rot.
//
// N.B. The ws+unix:// scheme makes use of the implementation detail
// that http.Client passes its constructor arguments through,
// un-inspected to net.Stream.connect(). The latter accepts a
// string as its first argument to connect to a UNIX socket.
var opt = {};
var agent = null;
switch (getUrlScheme(url)) {
case 'ws':
var u = urllib.parse(url);
agent = new http.Agent({
host: u.hostname,
port: u.port || 80
});
opt.agent = agent;
opt.host = u.hostname;
opt.port = u.port || 80;
opt.path = (u.pathname || '/') + (u.search || '');
opt.headers = httpHeaders;
break;
case 'ws+unix':
var sockPath = url.substring('ws+unix://'.length, url.length);
var u = urllib.parse(url);
agent = new http.Agent({
host: 'localhost',
port: sockPath
});
opt.agent = agent;
opt.host = 'localhost';
opt.path = sockPath;
opt.headers = httpHeaders;
break;
default:
throw new Error('Invalid URL scheme \'' + urlScheme + '\' specified.');
}
var httpReq = http.request(opt, function() { });
var upgradeHandler = (function() {
var data = undefined;
return function(req, s, head) {
req.socket.setNoDelay(true);
stream = s;
if (readyState == CLOSED) {
stream.end();
stream.destroy();
stream = undefined;
return;
}
stream.on('data', function(d) {
if (d.length <= 0) {
return;
}
if (!data) {
data = d;
} else {
var data2 = new buffer.Buffer(data.length + d.length);
data.copy(data2, 0, 0, data.length);
d.copy(data2, data.length, 0, d.length);
data = data2;
}
if (data.length >= 16) {
var expected = computeSecretKeySignature(key1, key2, challenge);
var actual = data.slice(0, 16).toString('binary');
// Handshaking fails; we're donezo
if (actual != expected) {
debug(
'expected=\'' + str2hex(expected) + '\'; ' +
'actual=\'' + str2hex(actual) + '\''
);
process.nextTick(function() {
// N.B. Emit 'wserror' here, as 'error' is a reserved word in the
// EventEmitter world, and gets thrown.
self.emit(
'wserror',
new Error('Invalid handshake from server:' +
'expected \'' + str2hex(expected) + '\', ' +
'actual \'' + str2hex(actual) + '\''
)
);
if (self.onerror) {
self.onerror();
}
finishClose();
});
}
// Un-register our data handler and add the one to be used
// for the normal, non-handshaking case. If we have extra
// data left over, manually fire off the handler on
// whatever remains.
//
// XXX: This is lame. We should only remove the listeners
// that we added.
httpReq.removeAllListeners('upgrade');
stream.removeAllListeners('data');
stream.on('data', dataListener);
readyState = OPEN;
process.nextTick(function() {
self.emit('open');
if (self.onopen) {
self.onopen();
}
});
// Consume any leftover data
if (data.length > 16) {
stream.emit('data', data.slice(16, data.length));
}
}
});
stream.on('fd', fdListener);
stream.on('error', errorListener);
stream.on('close', function() {
errorListener(new Error('Stream closed unexpectedly.'));
});
stream.emit('data', head);
};
})();
agent.on('upgrade', upgradeHandler); // node v0.4
httpReq.on('upgrade', upgradeHandler); // node v0.5+
httpReq.write(challenge, 'binary');
httpReq.end();
})();
};
sys.inherits(WebSocket, events.EventEmitter);
exports.WebSocket = WebSocket;
// Add some constants to the WebSocket object
setConstant(WebSocket.prototype, 'CONNECTING', CONNECTING);
setConstant(WebSocket.prototype, 'OPEN', OPEN);
setConstant(WebSocket.prototype, 'CLOSING', CLOSING);
setConstant(WebSocket.prototype, 'CLOSED', CLOSED);
// vim:ts=4 sw=4 et

View File

@@ -1,22 +0,0 @@
{
"name" : "websocket-client",
"version" : "1.0.0",
"description" : "An HTML5 Web Sockets client",
"author" : "Peter Griess <pg@std.in>",
"engines" : {
"node" : ">=0.1.98"
},
"repositories" : [
{
"type" : "git",
"url" : "http://github.com/pgriess/node-websocket-client.git"
}
],
"licenses" : [
{
"type" : "BSD",
"url" : "http://github.com/pgriess/node-websocket-client/blob/master/LICENSE"
}
],
"main" : "./lib/websocket"
}

View File

@@ -1,68 +0,0 @@
// Verify that we can connect to a WebSocket server, exchange messages, and
// shut down cleanly.
var assert = require('assert');
var WebSocket = require('../lib/websocket').WebSocket;
var WebSocketServer = require('websocket-server/ws/server').Server;
var PORT = 1024 + Math.floor(Math.random() * 4096);
var C_MSG = 'Client test: ' + (Math.random() * 100);
var S_MSG = 'Server test: ' + (Math.random() * 100);
var serverGotConnection = false;
var clientGotOpen = false;
var clientGotData = false;
var clientGotMessage = false;
var serverGotMessage = false;
var serverGotClose = false;
var clientGotClose = false;
var wss = new WebSocketServer();
wss.listen(PORT, 'localhost');
wss.on('connection', function(c) {
serverGotConnection = true;
c.on('message', function(m) {
assert.equal(m, C_MSG);
serverGotMessage = true;
c.close();
});
c.on('close', function() {
serverGotClose = true;
wss.close();
});
c.write(S_MSG);
});
var ws = new WebSocket('ws://localhost:' + PORT + '/', 'biff');
ws.on('open', function() {
clientGotOpen = true;
});
ws.on('data', function(buf) {
assert.equal(typeof buf, 'object');
assert.equal(buf.toString('utf8'), S_MSG);
clientGotData = true;
ws.send(C_MSG);
});
ws.onmessage = function(m) {
assert.deepEqual(m, {data : S_MSG});
clientGotMessage = true;
};
ws.onclose = function() {
clientGotClose = true;
};
process.on('exit', function() {
assert.ok(serverGotConnection);
assert.ok(clientGotOpen);
assert.ok(clientGotData);
assert.ok(clientGotMessage);
assert.ok(serverGotMessage);
assert.ok(serverGotClose);
assert.ok(clientGotClose);
});

View File

@@ -1,43 +0,0 @@
// Verify that a connection can be closed gracefully from the client.
var assert = require('assert');
var WebSocket = require('../lib/websocket').WebSocket;
var WebSocketServer = require('websocket-server/ws/server').Server;
var PORT = 1024 + Math.floor(Math.random() * 4096);
var C_MSG = 'Client test: ' + (Math.random() * 100);
var serverGotClientMessage = false;
var clientGotServerClose = false;
var serverGotClientClose = false;
var wss = new WebSocketServer();
wss.listen(PORT, 'localhost');
wss.on('connection', function(c) {
c.on('message', function(m) {
assert.equal(m, C_MSG);
serverGotClientMessage = true;
});
c.on('close', function() {
serverGotClientClose = true;
wss.close();
});
});
var ws = new WebSocket('ws://localhost:' + PORT);
ws.onopen = function() {
ws.send(C_MSG);
// XXX: Add a timeout here
ws.close(5);
};
ws.onclose = function() {
assert.equal(ws.CLOSED, ws.readyState);
clientGotServerClose = true;
};
process.on('exit', function() {
assert.ok(serverGotClientMessage);
assert.ok(clientGotServerClose);
assert.ok(serverGotClientClose);
});

View File

@@ -1,43 +0,0 @@
// Verify that some attributes of a WebSocket object are read-only.
var assert = require('assert');
var sys = require('sys');
var WebSocket = require('../lib/websocket').WebSocket;
var WebSocketServer = require('websocket-server/ws/server').Server;
var PORT = 1024 + Math.floor(Math.random() * 4096);
var wss = new WebSocketServer();
wss.listen(PORT, 'localhost');
wss.on('connection', function(c) {
c.close();
wss.close();
});
var ws = new WebSocket('ws://localhost:' + PORT + '/', 'biff');
ws.on('open', function() {
assert.equal(ws.CONNECTING, 0);
try {
ws.CONNECTING = 13;
assert.equal(
ws.CONNECTING, 0,
'Should not have been able to set read-only CONNECTING attribute'
);
} catch (e) {
assert.equal(e.type, 'no_setter_in_callback');
}
assert.equal(ws.OPEN, 1);
assert.equal(ws.CLOSING, 2);
assert.equal(ws.CLOSED, 3);
assert.equal(ws.url, 'ws://localhost:' + PORT + '/');
try {
ws.url = 'foobar';
assert.equal(
ws.url, 'ws://localhost:' + PORT + '/',
'Should not have been able to set read-only url attribute'
);
} catch (e) {
assert.equal(e.type, 'no_setter_in_callback');
}
});

View File

@@ -1,26 +0,0 @@
// Verify that readyState transitions are implemented correctly
var assert = require('assert');
var WebSocket = require('../lib/websocket').WebSocket;
var WebSocketServer = require('websocket-server/ws/server').Server;
var PORT = 1024 + Math.floor(Math.random() * 4096);
var wss = new WebSocketServer();
wss.listen(PORT, 'localhost');
wss.on('connection', function(c) {
c.close();
});
var ws = new WebSocket('ws://localhost:' + PORT);
assert.equal(ws.readyState, ws.CONNECTING);
ws.onopen = function() {
assert.equal(ws.readyState, ws.OPEN);
ws.close();
assert.ok(ws.readyState == ws.CLOSING);
};
ws.onclose = function() {
assert.equal(ws.readyState, ws.CLOSED);
wss.close();
};

View File

@@ -1,41 +0,0 @@
// Verify that a connection can be closed gracefully from the server.
var assert = require('assert');
var WebSocket = require('../lib/websocket').WebSocket;
var WebSocketServer = require('websocket-server/ws/server').Server;
var PORT = 1024 + Math.floor(Math.random() * 4096);
var S_MSG = 'Server test: ' + (Math.random() * 100);
var clientGotServerMessage = false;
var clientGotServerClose = false;
var serverGotClientClose = false;
var wss = new WebSocketServer();
wss.listen(PORT, 'localhost');
wss.on('connection', function(c) {
c.on('close', function() {
serverGotClientClose = true;
wss.close();
});
c.write(S_MSG);
c.close();
});
var ws = new WebSocket('ws://localhost:' + PORT);
ws.onmessage = function(m) {
assert.deepEqual(m, {data: S_MSG});
clientGotServerMessage = true;
};
ws.onclose = function() {
assert.equal(ws.CLOSED, ws.readyState);
clientGotServerClose = true;
};
process.on('exit', function() {
assert.ok(clientGotServerMessage);
assert.ok(clientGotServerClose);
assert.ok(serverGotClientClose);
});

View File

@@ -1,63 +0,0 @@
// Verify that both sides of the WS connection can both send and receive file
// descriptors.
var assert = require('assert');
var fs = require('fs');
var path = require('path');
var sys = require('sys');
var WebSocket = require('../lib/websocket').WebSocket;
var WebSocketServer = require('websocket-server/ws/server').Server;
var PATH = path.join(__dirname, 'sock.' + process.pid);
var C_MSG = 'Client test: ' + (Math.random() * 100);
var S_MSG = 'Server test: ' + (Math.random() * 100);
var clientReceivedData = false;
var clientReceivedFD = false;
var serverReceivedData = false;
var serverReceivedFD = false;
var wss = new WebSocketServer();
wss.on('listening', function() {
var ws = new WebSocket('ws+unix://' + PATH);
ws.on('data', function(d) {
assert.equal(d.toString('utf8'), S_MSG);
clientReceivedData = true;
ws.send(C_MSG, 1);
ws.close();
});
ws.on('fd', function(fd) {
assert.ok(fd >= 0);
clientReceivedFD = true;
});
});
wss.on('connection', function(c) {
c.write(S_MSG, 0);
c._req.socket.on('fd', function(fd) {
assert.ok(fd >= 0);
serverReceivedFD = true;
});
c.on('message', function(d) {
assert.equal(d, C_MSG);
serverReceivedData = true;
wss.close();
});
});
wss.listen(PATH);
process.on('exit', function() {
assert.ok(clientReceivedFD);
assert.ok(clientReceivedData);
assert.ok(serverReceivedFD);
assert.ok(serverReceivedData);
try {
fs.unlinkSync(PATH);
} catch (e) { }
});

View File

@@ -1,46 +0,0 @@
// Verify that we can connect to a server over UNIX domain sockets.
var assert = require('assert');
var fs = require('fs');
var path = require('path');
var sys = require('sys');
var WebSocket = require('../lib/websocket').WebSocket;
var WebSocketServer = require('websocket-server/ws/server').Server;
var PATH = path.join(__dirname, 'sock.' + process.pid);
var S_MSG = 'Server test: ' + (Math.random() * 100);
var serverGotConnection = false;
var clientGotOpen = false;
var clientGotData = false;
var wss = new WebSocketServer();
wss.on('listening', function() {
var ws = new WebSocket('ws+unix://' + PATH);
ws.on('open', function() {
clientGotOpen = true;
ws.close();
});
ws.on('data', function(d) {
assert.equal(d.toString('utf8'), S_MSG);
clientGotData = true;
});
});
wss.on('connection', function(c) {
serverGotConnection = true;
c.write(S_MSG);
wss.close();
});
wss.listen(PATH);
process.on('exit', function() {
assert.ok(serverGotConnection);
assert.ok(clientGotOpen);
assert.ok(clientGotData);
try {
fs.unlinkSync(PATH);
} catch(e) { }
});

View File

@@ -1,274 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Test dependencies.
*/
var io = require('socket.io')
, parser = io.parser
, http = require('http')
, https = require('https')
, WebSocket = require('../support/node-websocket-client/lib/websocket').WebSocket;
/**
* Exports.
*/
var should = module.exports = require('should');
should.HTTPClient = HTTPClient;
/**
* Client utility.
*
* @api publiC
*/
function HTTPClient (port) {
this.port = port;
this.agent = new http.Agent({
host: 'localhost'
, port: port
});
};
/**
* Issue a request
*
* @api private
*/
HTTPClient.prototype.request = function (path, opts, fn) {
if ('function' == typeof opts) {
fn = opts;
opts = {};
}
opts = opts || {};
opts.agent = this.agent;
opts.host = 'localhost';
opts.port = this.port;
opts.path = path.replace(/{protocol}/g, io.protocol);
opts.headers = opts.headers || {};
opts.headers.Host = 'localhost';
opts.headers.Connection = 'keep-alive';
var req = http.request(opts, function (res) {
if (false === opts.buffer)
return fn && fn(res);
var buf = '';
res.on('data', function (chunk) {
buf += chunk;
});
res.on('end', function () {
fn && fn(res, opts.parse ? opts.parse(buf) : buf);
});
});
req.on('error', function (err) { });
if (undefined !== opts.data)
req.write(opts.data);
req.end();
return req;
};
/**
* Terminates the client and associated connections.
*
* @api public
*/
HTTPClient.prototype.end = function () {
// node <v0.5 compat
if (this.agent.sockets.forEach) {
this.agent.sockets.forEach(function (socket) {
if (socket.end) socket.end();
});
return;
}
// node >=v0.5 compat
var self = this;
Object.keys(this.agent.sockets).forEach(function (socket) {
for (var i = 0, l = self.agent.sockets[socket].length; i < l; ++i) {
if (self.agent.sockets[socket][i]._handle) {
self.agent.sockets[socket][i]._handle.socket.end();
}
}
});
};
/**
* Issue a GET request
*
* @api public
*/
HTTPClient.prototype.get = function (path, opts, fn) {
if ('function' == typeof opts) {
fn = opts;
opts = {};
}
opts = opts || {};
opts.method = 'GET';
// override the parser for transport requests
if (/\/(xhr-polling|htmlfile|jsonp-polling)\//.test(path)) {
// parser that might be necessary for transport-specific framing
var transportParse = opts.parse;
opts.parse = function (data) {
if (data === '') return data;
data = transportParse ? transportParse(data) : data;
return parser.decodePayload(data);
};
} else {
opts.parse = undefined;
}
return this.request(path, opts, fn);
};
/**
* Issue a POST request
*
* @api private
*/
HTTPClient.prototype.post = function (path, data, opts, fn) {
if ('function' == typeof opts) {
fn = opts;
opts = {};
}
opts = opts || {};
opts.method = 'POST';
opts.data = data;
return this.request(path, opts, fn);
};
/**
* Issue a HEAD request
*
* @api private
*/
HTTPClient.prototype.head = function (path, opts, fn) {
if ('function' == typeof opts) {
fn = opts;
opts = {};
}
opts = opts || {};
opts.method = 'HEAD';
return this.request(path, opts, fn);
};
/**
* Performs a handshake (GET) request
*
* @api private
*/
HTTPClient.prototype.handshake = function (opts, fn) {
if ('function' == typeof opts) {
fn = opts;
opts = {};
}
return this.get('/socket.io/{protocol}', opts, function (res, data) {
fn && fn.apply(null, data.split(':'));
});
};
/**
* Generates a new client for the given port.
*
* @api private
*/
client = function (port) {
return new HTTPClient(port);
};
/**
* Create a socket.io server.
*/
create = function (cl) {
var manager = io.listen(cl.port);
manager.set('client store expiration', 0);
return manager;
};
/**
* WebSocket socket.io client.
*
* @api private
*/
function WSClient (port, sid, transport) {
this.sid = sid;
this.port = port;
this.transportName = transport || 'websocket';
WebSocket.call(
this
, 'ws://localhost:' + port + '/socket.io/'
+ io.protocol + '/' + this.transportName + '/' + sid
);
};
/**
* Inherits from WebSocket.
*/
WSClient.prototype.__proto__ = WebSocket.prototype;
/**
* Overrides message event emission.
*
* @api private
*/
WSClient.prototype.emit = function (name) {
var args = arguments;
if (name == 'message' || name == 'data') {
args[1] = parser.decodePacket(args[1].toString());
}
return WebSocket.prototype.emit.apply(this, arguments);
};
/**
* Writes a packet
*/
WSClient.prototype.packet = function (pack) {
this.write(parser.encodePacket(pack));
return this;
};
/**
* Creates a websocket client.
*
* @api public
*/
websocket = function (cl, sid, transport) {
return new WSClient(cl.port, sid, transport);
};

View File

@@ -1,21 +0,0 @@
-----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-----

27
test/fixtures/key.key vendored
View File

@@ -1,27 +0,0 @@
-----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-----

View File

@@ -1,99 +0,0 @@
/**
* Returns a Buffer from a "ff 00 ff"-type hex string.
*/
getBufferFromHexString = function(byteStr) {
var bytes = byteStr.split(' ');
var buf = new Buffer(bytes.length);
for (var i = 0; i < bytes.length; ++i) {
buf[i] = parseInt(bytes[i], 16);
}
return buf;
}
/**
* Returns a hex string from a Buffer.
*/
getHexStringFromBuffer = function(data) {
var s = '';
for (var i = 0; i < data.length; ++i) {
s += padl(data[i].toString(16), 2, '0') + ' ';
}
return s.trim();
}
/**
* Splits a buffer in two parts.
*/
splitBuffer = function(buffer) {
var b1 = new Buffer(Math.ceil(buffer.length / 2));
buffer.copy(b1, 0, 0, b1.length);
var b2 = new Buffer(Math.floor(buffer.length / 2));
buffer.copy(b2, 0, b1.length, b1.length + b2.length);
return [b1, b2];
}
/**
* Performs hybi07+ type masking on a hex string or buffer.
*/
mask = function(buf, maskString) {
if (typeof buf == 'string') buf = new Buffer(buf);
var mask = getBufferFromHexString(maskString || '34 83 a8 68');
for (var i = 0; i < buf.length; ++i) {
buf[i] ^= mask[i % 4];
}
return buf;
}
/**
* Returns a hex string representing the length of a message
*/
getHybiLengthAsHexString = function(len, masked) {
if (len < 126) {
var buf = new Buffer(1);
buf[0] = (masked ? 0x80 : 0) | len;
}
else if (len < 65536) {
var buf = new Buffer(3);
buf[0] = (masked ? 0x80 : 0) | 126;
getBufferFromHexString(pack(4, len)).copy(buf, 1);
}
else {
var buf = new Buffer(9);
buf[0] = (masked ? 0x80 : 0) | 127;
getBufferFromHexString(pack(16, len)).copy(buf, 1);
}
return getHexStringFromBuffer(buf);
}
/**
* Unpacks a Buffer into a number.
*/
unpack = function(buffer) {
var n = 0;
for (var i = 0; i < buffer.length; ++i) {
n = (i == 0) ? buffer[i] : (n * 256) + buffer[i];
}
return n;
}
/**
* Returns a hex string, representing a specific byte count 'length', from a number.
*/
pack = function(length, number) {
return padl(number.toString(16), length, '0').replace(/(\d\d)/g, '$1 ').trim();
}
/**
* Left pads the string 's' to a total length of 'n' with char 'c'.
*/
padl = function(s, n, c) {
return new Array(1 + n - s.length).join(c) + s;
}

View File

@@ -1,125 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Test dependencies.
*/
var sio = require('socket.io')
, fs = require('fs')
, http = require('http')
, https = require('https')
, should = require('./common')
, ports = 15000;
/**
* Test.
*/
module.exports = {
'test that protocol version is present': function (done) {
sio.protocol.should.be.a('number');
done();
},
'test that default transports are present': function (done) {
sio.Manager.defaultTransports.should.be.an.instanceof(Array);
done();
},
'test that version is present': function (done) {
sio.version.should.match(/([0-9]+)\.([0-9]+)\.([0-9]+)/);
done();
},
'test listening with a port': function (done) {
var cl = client(++ports)
, io = create(cl);
io.server.should.be.an.instanceof(http.Server);
cl.get('/', function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('Welcome to socket.io.');
cl.end();
io.server.close();
done();
});
},
'test listening with a server': function (done) {
var server = http.createServer()
, io = sio.listen(server)
, port = ++ports
, cl = client(port);
server.listen(port);
cl.get('/socket.io', function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('Welcome to socket.io.');
cl.end();
server.close();
done();
});
},
'test listening with a https server': function (done) {
var server = https.createServer({
key: fs.readFileSync(__dirname + '/fixtures/key.key')
, cert: fs.readFileSync(__dirname + '/fixtures/cert.crt')
}, function () { })
, io = sio.listen(server)
, port = ++ports;
server.listen(port);
var req = require('https').get({
host: 'localhost'
, port: port
, path: '/socket.io'
}, function (res) {
res.statusCode.should.eql(200);
var buf = '';
res.on('data', function (data) {
buf += data;
});
res.on('end', function () {
buf.should.eql('Welcome to socket.io.');
res.socket.end();
server.close();
done();
});
});
},
'test listening with no arguments listens on 80': function (done) {
try {
var io = sio.listen()
, cl = client(80);
cl.get('/socket.io', function (res) {
res.statusCode.should.eql(200);
cl.end();
io.server.close();
done();
});
done();
} catch (e) {
e.should.match(/EACCES/);
done();
}
}
};

View File

@@ -1,54 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Test dependencies.
*/
require.paths.unshift(__dirname + '/../../lib');
var assertvanish = require('assertvanish')
, common = require('../common')
, ports = 15800;
function resultCallback (leaks, leakedSocket) {
if (leaks) {
console.error('Leak detected');
process.exit(1);
} else {
console.error('No leaks');
process.exit(0);
}
};
/**
* Test.
*/
var cl = client(++ports);
var io = create(cl);
io.sockets.on('connection', function (socket) {
console.log('connected');
socket.on('disconnect', function() {
console.log("client gone");
setTimeout(gc, 1000);
assertvanish(socket, 2000, {silent: true, callback: resultCallback});
});
});
setTimeout(function() {
cl.handshake(function (sid) {
var ws = websocket(cl, sid);
ws.on('open', function () {
console.log('open!');
setTimeout(function() {
ws.close();
}, 500);
});
});
}, 100);

View File

@@ -1,589 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Test dependencies.
*/
var sio = require('socket.io')
, http = require('http')
, should = require('./common')
, ports = 15100;
/**
* Test.
*/
module.exports = {
'test setting and getting a configuration flag': function (done) {
var port = ++ports
, io = sio.listen(http.createServer());
io.set('a', 'b');
io.get('a').should.eql('b');
var port = ++ports
, io = sio.listen(http.createServer());
io.configure(function () {
io.set('a', 'b');
io.enable('tobi');
});
io.get('a').should.eql('b');
done();
},
'test enabling and disabling a configuration flag': function (done) {
var port = ++ports
, io = sio.listen(http.createServer());
io.enable('flag');
io.enabled('flag').should.be.true;
io.disabled('flag').should.be.false;
io.disable('flag');
var port = ++ports
, io = sio.listen(http.createServer());
io.configure(function () {
io.enable('tobi');
});
io.enabled('tobi').should.be.true;
done();
},
'test configuration callbacks with envs': function (done) {
var port = ++ports
, io = sio.listen(http.createServer());
process.env.NODE_ENV = 'development';
io.configure('production', function () {
io.set('ferret', 'tobi');
});
io.configure('development', function () {
io.set('ferret', 'jane');
});
io.get('ferret').should.eql('jane');
done();
},
'test configuration callbacks conserve scope': function (done) {
var port = ++ports
, io = sio.listen(http.createServer())
, calls = 0;
process.env.NODE_ENV = 'development';
io.configure(function () {
this.should.eql(io);
calls++;
});
io.configure('development', function () {
this.should.eql(io);
calls++;
});
calls.should.eql(2);
done();
},
'test configuration update notifications': function (done) {
var port = ++ports
, io = sio.listen(http.createServer())
, calls = 0;
io.on('set:foo', function () {
calls++;
});
io.set('foo', 'bar');
io.set('baz', 'bar');
calls.should.eql(1);
io.enable('foo');
io.disable('foo');
calls.should.eql(3);
done();
},
'test that normal requests are still served': function (done) {
var server = http.createServer(function (req, res) {
res.writeHead(200);
res.end('woot');
});
var io = sio.listen(server)
, port = ++ports
, cl = client(port);
server.listen(ports);
cl.get('/socket.io', function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('Welcome to socket.io.');
cl.get('/woot', function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('woot');
cl.end();
server.close();
done();
});
});
},
'test that you can disable clients': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.configure(function () {
io.disable('browser client');
});
cl.get('/socket.io/socket.io.js', function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('Welcome to socket.io.');
cl.end();
io.server.close();
done();
});
},
'test handshake': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
cl.get('/socket.io/{protocol}/', function (res, data) {
res.statusCode.should.eql(200);
data.should.match(/([^:]+):([0-9]+)?:([0-9]+)?:(.+)/);
cl.end();
io.server.close();
done();
});
},
'test handshake with unsupported protocol version': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
cl.get('/socket.io/-1/', function (res, data) {
res.statusCode.should.eql(500);
data.should.match(/Protocol version not supported/);
cl.end();
io.server.close();
done();
});
},
'test authorization failure in handshake': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.configure(function () {
function auth (data, fn) {
fn(null, false);
};
io.set('authorization', auth);
});
cl.get('/socket.io/{protocol}/', function (res, data) {
res.statusCode.should.eql(403);
data.should.match(/handshake unauthorized/);
cl.end();
io.server.close();
done();
});
},
'test a handshake error': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.configure(function () {
function auth (data, fn) {
fn(new Error);
};
io.set('authorization', auth);
});
cl.get('/socket.io/{protocol}/', function (res, data) {
res.statusCode.should.eql(500);
data.should.match(/handshake error/);
cl.end();
io.server.close();
done();
});
},
'test that a referer is accepted for *:* origin': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.configure(function () {
io.set('origins', '*:*');
});
cl.get('/socket.io/{protocol}', { headers: { referer: 'http://foo.bar.com:82/something' } }, function (res, data) {
res.statusCode.should.eql(200);
cl.end();
io.server.close();
done();
});
},
'test that valid referer is accepted for addr:* origin': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.configure(function () {
io.set('origins', 'foo.bar.com:*');
});
cl.get('/socket.io/{protocol}', { headers: { referer: 'http://foo.bar.com/something' } }, function (res, data) {
res.statusCode.should.eql(200);
cl.end();
io.server.close();
done();
});
},
'test that a referer with implicit port 80 is accepted for foo.bar.com:80 origin': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.configure(function () {
io.set('origins', 'foo.bar.com:80');
});
cl.get('/socket.io/{protocol}', { headers: { referer: 'http://foo.bar.com/something' } }, function (res, data) {
res.statusCode.should.eql(200);
cl.end();
io.server.close();
done();
});
},
'test that erroneous referer is denied for addr:* origin': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.configure(function () {
io.set('origins', 'foo.bar.com:*');
});
cl.get('/socket.io/{protocol}', { headers: { referer: 'http://baz.bar.com/something' } }, function (res, data) {
res.statusCode.should.eql(403);
cl.end();
io.server.close();
done();
});
},
'test that valid referer port is accepted for addr:port origin': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.configure(function () {
io.set('origins', 'foo.bar.com:81');
});
cl.get('/socket.io/{protocol}', { headers: { referer: 'http://foo.bar.com:81/something' } }, function (res, data) {
res.statusCode.should.eql(200);
cl.end();
io.server.close();
done();
});
},
'test that erroneous referer port is denied for addr:port origin': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.configure(function () {
io.set('origins', 'foo.bar.com:81');
});
cl.get('/socket.io/{protocol}', { headers: { referer: 'http://foo.bar.com/something' } }, function (res, data) {
res.statusCode.should.eql(403);
cl.end();
io.server.close();
done();
});
},
'test handshake cross domain access control': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port)
, headers = {
Origin: 'http://example.org:1337'
, Cookie: 'name=value'
};
cl.get('/socket.io/{protocol}/', { headers:headers }, function (res, data) {
res.statusCode.should.eql(200);
res.headers['access-control-allow-origin'].should.eql('http://example.org:1337');
res.headers['access-control-allow-credentials'].should.eql('true');
cl.end();
io.server.close();
done();
});
},
'test limiting the supported transports for a manager': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.configure(function () {
io.set('transports', ['tobi', 'jane']);
});
cl.get('/socket.io/{protocol}/', function (res, data) {
res.statusCode.should.eql(200);
data.should.match(/([^:]+):([0-9]+)?:([0-9]+)?:tobi,jane/);
cl.end();
io.server.close();
done();
});
},
'test setting a custom close timeout': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.configure(function () {
io.set('close timeout', 66);
});
cl.get('/socket.io/{protocol}/', function (res, data) {
res.statusCode.should.eql(200);
data.should.match(/([^:]+):([0-9]+)?:66?:(.*)/);
cl.end();
io.server.close();
done();
});
},
'test setting a custom heartbeat timeout': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.configure(function () {
io.set('heartbeat timeout', 33);
});
cl.get('/socket.io/{protocol}/', function (res, data) {
res.statusCode.should.eql(200);
data.should.match(/([^:]+):33:([0-9]+)?:(.*)/);
cl.end();
io.server.close();
done();
});
},
'test disabling timeouts': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.configure(function () {
io.set('heartbeat timeout', null);
io.set('close timeout', '');
});
cl.get('/socket.io/{protocol}/', function (res, data) {
res.statusCode.should.eql(200);
data.should.match(/([^:]+)::?:(.*)/);
cl.end();
io.server.close();
done();
});
},
'test disabling heartbeats': function (done) {
var port = ++ports
, cl = client(port)
, io = create(cl)
, messages = 0
, beat = false
, ws;
io.configure(function () {
io.disable('heartbeats');
io.set('heartbeat interval', .05);
io.set('heartbeat timeout', .05);
io.set('close timeout', .05);
});
io.sockets.on('connection', function (socket) {
setTimeout(function () {
socket.disconnect();
}, io.get('heartbeat timeout') * 1000 + 100);
socket.on('disconnect', function (reason) {
beat.should.be.false;
ws.finishClose();
cl.end();
io.server.close();
done();
});
});
cl.get('/socket.io/{protocol}/', function (res, data) {
res.statusCode.should.eql(200);
data.should.match(/([^:]+)::[\.0-9]+:(.*)/);
cl.handshake(function (sid) {
ws = websocket(cl, sid);
ws.on('message', function (packet) {
if (++messages == 1) {
packet.type.should.eql('connect');
} else if (packet.type == 'heartbeat'){
beat = true;
}
});
});
});
},
'no duplicate room members': function (done) {
var port = ++ports
, io = sio.listen(port);
Object.keys(io.rooms).length.should.equal(0);
io.onJoin(123, 'foo');
io.rooms.foo.length.should.equal(1);
io.onJoin(123, 'foo');
io.rooms.foo.length.should.equal(1);
io.onJoin(124, 'foo');
io.rooms.foo.length.should.equal(2);
io.onJoin(124, 'foo');
io.rooms.foo.length.should.equal(2);
io.onJoin(123, 'bar');
io.rooms.foo.length.should.equal(2);
io.rooms.bar.length.should.equal(1);
io.onJoin(123, 'bar');
io.rooms.foo.length.should.equal(2);
io.rooms.bar.length.should.equal(1);
io.onJoin(124, 'bar');
io.rooms.foo.length.should.equal(2);
io.rooms.bar.length.should.equal(2);
io.onJoin(124, 'bar');
io.rooms.foo.length.should.equal(2);
io.rooms.bar.length.should.equal(2);
process.nextTick(function() {
io.server.close();
done();
});
},
'test passing options directly to the Manager through listen': function (done) {
var port = ++ports
, io = sio.listen(port, { resource: '/my resource', custom: 'opt' });
io.get('resource').should.equal('/my resource');
io.get('custom').should.equal('opt');
process.nextTick(function() {
io.server.close();
done();
});
},
'test disabling the log': function (done) {
var port = ++ports
, io = sio.listen(port, { log: false })
, _console = console.log
, calls = 0;
// the logger uses console.log to output data, override it to see if get's
// used
console.log = function () { ++calls };
io.log.debug('test');
io.log.log('testing');
console.log = _console;
calls.should.equal(0);
process.nextTick(function() {
io.server.close();
done();
});
},
'test disabling logging with colors': function (done) {
var port = ++ports
, io = sio.listen(port, { 'log colors': false })
, _console = console.log
, calls = 0;
// the logger uses console.log to output data, override it to see if get's
// used
console.log = function (data) {
++calls;
data.indexOf('\033').should.equal(-1);
};
io.log.debug('test');
io.log.log('testing');
console.log = _console;
calls.should.equal(2);
process.nextTick(function() {
io.server.close();
done();
});
}
};

View File

@@ -1,286 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Test dependencies.
*/
var sio = require('socket.io')
, should = require('./common')
, ports = 15700;
/**
* Test.
*/
module.exports = {
'namespace pass no authentication': function (done) {
var cl = client(++ports)
, io = create(cl)
, ws;
io.of('/a')
.on('connection', function (socket) {
cl.end();
ws.finishClose();
io.server.close()
done();
});
cl.handshake(function (sid) {
ws = websocket(cl, sid);
ws.on('open', function () {
ws.packet({
type: 'connect'
, endpoint: '/a'
});
})
});
},
'namespace pass authentication': function (done) {
var cl = client(++ports)
, io = create(cl)
, ws;
io.of('/a')
.authorization(function (data, fn) {
fn(null, true);
})
.on('connection', function (socket) {
cl.end();
ws.finishClose();
io.server.close()
done();
});
cl.handshake(function (sid) {
ws = websocket(cl, sid);
ws.on('open', function () {
ws.packet({
type: 'connect'
, endpoint: '/a'
});
})
});
},
'namespace authentication handshake data': function (done) {
var cl = client(++ports)
, io = create(cl)
, ws;
io.of('/a')
.authorization(function (data, fn) {
data.foo = 'bar';
fn(null, true);
})
.on('connection', function (socket) {
(!!socket.handshake.address.address).should.be.true;
(!!socket.handshake.address.port).should.be.true;
socket.handshake.headers.host.should.equal('localhost');
socket.handshake.headers.connection.should.equal('keep-alive');
socket.handshake.time.should.match(/GMT/);
socket.handshake.foo.should.equal('bar');
cl.end();
ws.finishClose();
io.server.close()
done();
});
cl.handshake(function (sid) {
ws = websocket(cl, sid);
ws.on('open', function () {
ws.packet({
type: 'connect'
, endpoint: '/a'
});
})
});
},
'namespace fail authentication': function (done) {
var cl = client(++ports)
, io = create(cl)
, calls = 0
, ws;
io.of('/a')
.authorization(function (data, fn) {
fn(null, false);
})
.on('connection', function (socket) {
throw new Error('Should not be called');
});
cl.handshake(function (sid) {
ws = websocket(cl, sid);
ws.on('open', function () {
ws.packet({
type: 'connect'
, endpoint: '/a'
});
});
ws.on('message', function (data) {
if (data.endpoint == '/a') {
data.type.should.eql('error');
data.reason.should.eql('unauthorized')
cl.end();
ws.finishClose();
io.server.close()
done();
}
})
});
},
'broadcasting sends and emits on a namespace': function (done) {
var cl = client(++ports)
, io = create(cl)
, calls = 0
, connect = 0
, message = 0
, events = 0
, expected = 5
, ws1
, ws2;
io.of('a')
.on('connection', function (socket){
if (connect < 2) {
return;
}
socket.broadcast.emit('b', 'test');
socket.broadcast.json.emit('json', {foo:'bar'});
socket.broadcast.send('foo');
});
function finish () {
connect.should.equal(2);
message.should.equal(1);
events.should.equal(2);
cl.end();
ws1.finishClose();
ws2.finishClose();
io.server.close();
done();
}
cl.handshake(function (sid) {
ws1 = websocket(cl, sid);
ws1.on('message', function (data) {
if (data.type === 'connect') {
if (connect == 0) {
cl.handshake(function (sid) {
ws2 = websocket(cl, sid);
ws2.on('open', function () {
ws2.packet({
type: 'connect'
, endpoint: 'a'
});
});
});
}
++connect;
if (++calls === expected) finish();
}
if (data.type === 'message') {
++message;
if (++calls === expected) finish();
}
if (data.type === 'event') {
if (data.name === 'b' || data.name === 'json') ++events;
if (++calls === expected) finish();
}
});
ws1.on('open', function() {
ws1.packet({
type: 'connect'
, endpoint: 'a'
});
});
})
},
'joining rooms inside a namespace': function (done) {
var cl = client(++ports)
, io = create(cl)
, calls = 0
, ws;
io.of('/foo').on('connection', function (socket) {
socket.join('foo.bar');
this.in('foo.bar').emit('baz', 'pewpew');
});
cl.handshake(function (sid) {
ws = websocket(cl, sid);
ws.on('open', function (){
ws.packet({
type: 'connect'
, endpoint: '/foo'
});
});
ws.on('message', function (data) {
if (data.type === 'event') {
data.name.should.equal('baz');
cl.end();
ws.finishClose();
io.server.close();
done();
}
});
})
},
'ignoring blacklisted events': function (done) {
var cl = client(++ports)
, io = create(cl)
, calls = 0
, ws;
io.set('heartbeat interval', 1);
io.set('blacklist', ['foobar']);
io.sockets.on('connection', function (socket) {
socket.on('foobar', function () {
calls++;
});
});
cl.handshake(function (sid) {
ws = websocket(cl, sid);
ws.on('open', function (){
ws.packet({
type: 'event'
, name: 'foobar'
, endpoint: ''
});
});
ws.on('message', function (data) {
if (data.type === 'heartbeat') {
cl.end();
ws.finishClose();
io.server.close();
calls.should.equal(0);
done();
}
});
});
}
};

View File

@@ -1,356 +0,0 @@
/**
* Test dependencies.
*/
var parser = require('socket.io').parser
, decode = parser.decode
, should = require('./common');
/**
* Test.
*/
module.exports = {
'decoding error packet': function () {
parser.decodePacket('7:::').should.eql({
type: 'error'
, reason: ''
, advice: ''
, endpoint: ''
});
},
'decoding error packet with reason': function () {
parser.decodePacket('7:::0').should.eql({
type: 'error'
, reason: 'transport not supported'
, advice: ''
, endpoint: ''
});
},
'decoding error packet with reason and advice': function () {
parser.decodePacket('7:::2+0').should.eql({
type: 'error'
, reason: 'unauthorized'
, advice: 'reconnect'
, endpoint: ''
});
},
'decoding error packet with endpoint': function () {
parser.decodePacket('7::/woot').should.eql({
type: 'error'
, reason: ''
, advice: ''
, endpoint: '/woot'
});
},
'decoding ack packet': function () {
parser.decodePacket('6:::140').should.eql({
type: 'ack'
, ackId: '140'
, endpoint: ''
, args: []
});
},
'decoding ack packet with args': function () {
parser.decodePacket('6:::12+["woot","wa"]').should.eql({
type: 'ack'
, ackId: '12'
, endpoint: ''
, args: ['woot', 'wa']
});
},
'decoding ack packet with bad json': function () {
var thrown = false;
try {
parser.decodePacket('6:::1+{"++]').should.eql({
type: 'ack'
, ackId: '1'
, endpoint: ''
, args: []
});
} catch (e) {
thrown = true;
}
thrown.should.be.false;
},
'decoding json packet': function () {
parser.decodePacket('4:::"2"').should.eql({
type: 'json'
, endpoint: ''
, data: '2'
});
},
'decoding json packet with message id and ack data': function () {
parser.decodePacket('4:1+::{"a":"b"}').should.eql({
type: 'json'
, id: 1
, ack: 'data'
, endpoint: ''
, data: { a: 'b' }
});
},
'decoding an event packet': function () {
parser.decodePacket('5:::{"name":"woot"}').should.eql({
type: 'event'
, name: 'woot'
, endpoint: ''
, args: []
});
},
'decoding an event packet with message id and ack': function () {
parser.decodePacket('5:1+::{"name":"tobi"}').should.eql({
type: 'event'
, id: 1
, ack: 'data'
, endpoint: ''
, name: 'tobi'
, args: []
});
},
'decoding an event packet with data': function () {
parser.decodePacket('5:::{"name":"edwald","args":[{"a": "b"},2,"3"]}')
.should.eql({
type: 'event'
, name: 'edwald'
, endpoint: ''
, args: [{a: 'b'}, 2, '3']
});
},
'decoding a message packet': function () {
parser.decodePacket('3:::woot').should.eql({
type: 'message'
, endpoint: ''
, data: 'woot'
});
},
'decoding a message packet with id and endpoint': function () {
parser.decodePacket('3:5:/tobi').should.eql({
type: 'message'
, id: 5
, ack: true
, endpoint: '/tobi'
, data: ''
});
},
'decoding a heartbeat packet': function () {
parser.decodePacket('2:::').should.eql({
type: 'heartbeat'
, endpoint: ''
});
},
'decoding a connection packet': function () {
parser.decodePacket('1::/tobi').should.eql({
type: 'connect'
, endpoint: '/tobi'
, qs: ''
});
},
'decoding a connection packet with query string': function () {
parser.decodePacket('1::/test:?test=1').should.eql({
type: 'connect'
, endpoint: '/test'
, qs: '?test=1'
});
},
'decoding a disconnection packet': function () {
parser.decodePacket('0::/woot').should.eql({
type: 'disconnect'
, endpoint: '/woot'
});
},
'encoding error packet': function () {
parser.encodePacket({
type: 'error'
, reason: ''
, advice: ''
, endpoint: ''
}).should.eql('7::');
},
'encoding error packet with reason': function () {
parser.encodePacket({
type: 'error'
, reason: 'transport not supported'
, advice: ''
, endpoint: ''
}).should.eql('7:::0');
},
'encoding error packet with reason and advice': function () {
parser.encodePacket({
type: 'error'
, reason: 'unauthorized'
, advice: 'reconnect'
, endpoint: ''
}).should.eql('7:::2+0');
},
'encoding error packet with endpoint': function () {
parser.encodePacket({
type: 'error'
, reason: ''
, advice: ''
, endpoint: '/woot'
}).should.eql('7::/woot');
},
'encoding ack packet': function () {
parser.encodePacket({
type: 'ack'
, ackId: '140'
, endpoint: ''
, args: []
}).should.eql('6:::140');
},
'encoding ack packet with args': function () {
parser.encodePacket({
type: 'ack'
, ackId: '12'
, endpoint: ''
, args: ['woot', 'wa']
}).should.eql('6:::12+["woot","wa"]');
},
'encoding json packet': function () {
parser.encodePacket({
type: 'json'
, endpoint: ''
, data: '2'
}).should.eql('4:::"2"');
},
'encoding json packet with message id and ack data': function () {
parser.encodePacket({
type: 'json'
, id: 1
, ack: 'data'
, endpoint: ''
, data: { a: 'b' }
}).should.eql('4:1+::{"a":"b"}');
},
'encoding an event packet': function () {
parser.encodePacket({
type: 'event'
, name: 'woot'
, endpoint: ''
, args: []
}).should.eql('5:::{"name":"woot"}');
},
'encoding an event packet with message id and ack': function () {
parser.encodePacket({
type: 'event'
, id: 1
, ack: 'data'
, endpoint: ''
, name: 'tobi'
, args: []
}).should.eql('5:1+::{"name":"tobi"}');
},
'encoding an event packet with data': function () {
parser.encodePacket({
type: 'event'
, name: 'edwald'
, endpoint: ''
, args: [{a: 'b'}, 2, '3']
}).should.eql('5:::{"name":"edwald","args":[{"a":"b"},2,"3"]}');
},
'encoding a message packet': function () {
parser.encodePacket({
type: 'message'
, endpoint: ''
, data: 'woot'
}).should.eql('3:::woot');
},
'encoding a message packet with id and endpoint': function () {
parser.encodePacket({
type: 'message'
, id: 5
, ack: true
, endpoint: '/tobi'
, data: ''
}).should.eql('3:5:/tobi');
},
'encoding a heartbeat packet': function () {
parser.encodePacket({
type: 'heartbeat'
, endpoint: ''
}).should.eql('2::');
},
'encoding a connection packet': function () {
parser.encodePacket({
type: 'connect'
, endpoint: '/tobi'
, qs: ''
}).should.eql('1::/tobi');
},
'encoding a connection packet with query string': function () {
parser.encodePacket({
type: 'connect'
, endpoint: '/test'
, qs: '?test=1'
}).should.eql('1::/test:?test=1');
},
'encoding a disconnection packet': function () {
parser.encodePacket({
type: 'disconnect'
, endpoint: '/woot'
}).should.eql('0::/woot');
},
'test decoding a payload': function () {
parser.decodePayload('\ufffd5\ufffd3:::5\ufffd7\ufffd3:::53d'
+ '\ufffd3\ufffd0::').should.eql([
{ type: 'message', data: '5', endpoint: '' }
, { type: 'message', data: '53d', endpoint: '' }
, { type: 'disconnect', endpoint: '' }
]);
},
'test encoding a payload': function () {
parser.encodePayload([
parser.encodePacket({ type: 'message', data: '5', endpoint: '' })
, parser.encodePacket({ type: 'message', data: '53d', endpoint: '' })
]).should.eql('\ufffd5\ufffd3:::5\ufffd7\ufffd3:::53d')
},
'test decoding newline': function () {
parser.decodePacket('3:::\n').should.eql({
type: 'message'
, endpoint: ''
, data: '\n'
});
}
};

View File

@@ -1,549 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Test dependencies.
*/
var sio = require('socket.io')
, should = require('./common')
, ports = 15400;
/**
* Test.
*/
module.exports = {
'test that the default static files are available': function (done) {
var port = ++ports
, io = sio.listen(port);
(!!io.static.has('/socket.io.js')).should.be.true;
(!!io.static.has('/socket.io.v1.0.0.js')).should.be.true;
(!!io.static.has('/socket.io+xhr-polling.js')).should.be.true;
(!!io.static.has('/socket.io+xhr-polling.v1.0.0.js')).should.be.true;
(!!io.static.has('/static/flashsocket/WebSocketMain.swf')).should.be.true;
(!!io.static.has('/static/flashsocket/WebSocketMainInsecure.swf')).should.be.true;
process.nextTick(function() {
io.server.close();
done();
});
},
'test that static files are correctly looked up': function (done) {
var port = ++ports
, io = sio.listen(port);
(!!io.static.has('/socket.io.js')).should.be.true;
(!!io.static.has('/invalidfilehereplease.js')).should.be.false;
process.nextTick(function() {
io.server.close();
done();
});
},
'test that the client is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
should.strictEqual(res.headers.etag, undefined);
data.should.match(/XMLHttpRequest/);
cl.end();
io.server.close();
done();
});
},
'test that the custom build client is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.enable('browser client etag');
cl.get('/socket.io/socket.io+websocket.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
res.headers.etag.should.match(/([0-9]+)\.([0-9]+)\.([0-9]+)/);
data.should.match(/XMLHttpRequest/);
data.should.match(/WS\.prototype\.name/);
data.should.not.match(/Flashsocket\.prototype\.name/);
data.should.not.match(/HTMLFile\.prototype\.name/);
data.should.not.match(/JSONPPolling\.prototype\.name/);
data.should.not.match(/XHRPolling\.prototype\.name/);
cl.end();
io.server.close();
done();
});
},
'test that the client is build with the enabled transports': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.set('transports', ['websocket']);
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
data.should.match(/XMLHttpRequest/);
data.should.match(/WS\.prototype\.name/);
data.should.not.match(/Flashsocket\.prototype\.name/);
data.should.not.match(/HTMLFile\.prototype\.name/);
data.should.not.match(/JSONPPolling\.prototype\.name/);
data.should.not.match(/XHRPolling\.prototype\.name/);
cl.end();
io.server.close();
done();
});
},
'test that the client cache is cleared when transports change': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.set('transports', ['websocket']);
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
data.should.match(/XMLHttpRequest/);
data.should.match(/WS\.prototype\.name/);
data.should.not.match(/Flashsocket\.prototype\.name/);
data.should.not.match(/HTMLFile\.prototype\.name/);
data.should.not.match(/JSONPPolling\.prototype\.name/);
data.should.not.match(/XHRPolling\.prototype\.name/);
io.set('transports', ['xhr-polling']);
should.strictEqual(io.static.cache['/socket.io.js'], undefined);
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
data.should.match(/XMLHttpRequest/);
data.should.match(/XHRPolling\.prototype\.name/);
data.should.not.match(/Flashsocket\.prototype\.name/);
data.should.not.match(/HTMLFile\.prototype\.name/);
data.should.not.match(/JSONPPolling\.prototype\.name/);
data.should.not.match(/WS\.prototype\.name/);
cl.end();
io.server.close();
done();
});
});
},
'test that the client etag is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.enable('browser client etag');
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
res.headers.etag.should.match(/([0-9]+)\.([0-9]+)\.([0-9]+)/);
data.should.match(/XMLHttpRequest/);
cl.end();
io.server.close();
done();
});
},
'test that the client etag is changed for new transports': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.set('transports', ['websocket']);
io.enable('browser client etag');
cl.get('/socket.io/socket.io.js', function (res, data) {
var wsEtag = res.headers.etag;
io.set('transports', ['xhr-polling']);
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers.etag.should.not.equal(wsEtag);
cl.end();
io.server.close();
done();
});
});
},
'test that the client is served with gzip': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.enable('browser client gzip');
cl.get('/socket.io/socket.io.js', {
headers: {
'accept-encoding': 'deflate, gzip'
}
}
, function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-encoding'].should.eql('gzip');
res.headers['content-length'].should.match(/([0-9]+)/);
cl.end();
io.server.close();
done();
}
);
},
'test that the cached client is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
should.strictEqual(res.headers.etag, undefined);
data.should.match(/XMLHttpRequest/);
var static = io.static;
static.cache['/socket.io.js'].content.should.match(/XMLHttpRequest/);
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
should.strictEqual(res.headers.etag, undefined);
data.should.match(/XMLHttpRequest/);
cl.end();
io.server.close();
done();
});
});
},
'test that the client is not cached': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.static.add('/random.js', function (path, callback) {
var random = Math.floor(Date.now() * Math.random()).toString();
callback(null, new Buffer(random));
});
io.disable('browser client cache');
cl.get('/socket.io/random.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
should.strictEqual(res.headers.etag, undefined);
cl.get('/socket.io/random.js', function (res, random) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
should.strictEqual(res.headers.etag, undefined);
data.should.not.equal(random);
cl.end();
io.server.close();
done();
});
});
},
'test that the cached client etag is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.enable('browser client etag');
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
res.headers.etag.should.match(/([0-9]+)\.([0-9]+)\.([0-9]+)/);
data.should.match(/XMLHttpRequest/);
var static = io.static
, cache = static.cache['/socket.io.js'];
cache.content.toString().should.match(/XMLHttpRequest/);
Buffer.isBuffer(cache.content).should.be.true;
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
res.headers.etag.should.match(/([0-9]+)\.([0-9]+)\.([0-9]+)/);
data.should.match(/XMLHttpRequest/);
cl.end();
io.server.close();
done();
});
});
},
'test that the cached client sends a 304 header': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.enable('browser client etag');
cl.get('/socket.io/socket.io.js', function (res, data) {
cl.get('/socket.io/socket.io.js', {
headers: {
'if-none-match': res.headers.etag
}
}, function (res, data) {
res.statusCode.should.eql(304);
cl.end();
io.server.close();
done();
}
);
});
},
'test that client minification works': function (done) {
// server 1
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
// server 2
var port = ++ports
, io2 = sio.listen(port)
, cl2 = client(port);
io.enable('browser client minification');
cl.get('/socket.io/socket.io.js', function (res, data) {
var length = data.length;
cl.end();
io.server.close();
cl2.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
should.strictEqual(res.headers.etag, undefined);
data.should.match(/XMLHttpRequest/);
data.length.should.be.greaterThan(length);
cl2.end();
io2.server.close();
done();
});
});
},
'test that the WebSocketMain.swf is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
cl.get('/socket.io/static/flashsocket/WebSocketMain.swf', function (res, data) {
res.headers['content-type'].should.eql('application/x-shockwave-flash');
res.headers['content-length'].should.match(/([0-9]+)/);
should.strictEqual(res.headers.etag, undefined);
var static = io.static
, cache = static.cache['/static/flashsocket/WebSocketMain.swf'];
Buffer.isBuffer(cache.content).should.be.true;
cl.end();
io.server.close();
done();
});
},
'test that the WebSocketMainInsecure.swf is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
cl.get('/socket.io/static/flashsocket/WebSocketMainInsecure.swf', function (res, data) {
res.headers['content-type'].should.eql('application/x-shockwave-flash');
res.headers['content-length'].should.match(/([0-9]+)/);
should.strictEqual(res.headers.etag, undefined);
var static = io.static
, cache = static.cache['/static/flashsocket/WebSocketMainInsecure.swf'];
Buffer.isBuffer(cache.content).should.be.true;
cl.end();
io.server.close();
done();
});
},
'test that swf files are not served with gzip': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.enable('browser client gzip');
cl.get('/socket.io/static/flashsocket/WebSocketMain.swf', {
headers: {
'accept-encoding': 'deflate, gzip'
}
}
, function (res, data) {
res.headers['content-type'].should.eql('application/x-shockwave-flash');
res.headers['content-length'].should.match(/([0-9]+)/);
should.strictEqual(res.headers['content-encoding'], undefined);
cl.end();
io.server.close();
done();
}
);
},
'test that you can serve custom clients': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.set('browser client handler', function (req, res) {
res.writeHead(200, {
'Content-Type': 'application/javascript'
, 'Content-Length': 13
, 'ETag': '1.0'
});
res.end('custom_client');
});
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.eql(13);
res.headers.etag.should.eql('1.0');
data.should.eql('custom_client');
cl.end();
io.server.close();
done();
});
},
'test that HEAD requests work': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
cl.head('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
data.should.eql('');
cl.end();
io.server.close()
done();
});
},
'test that a versioned client is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
cl.get('/socket.io/socket.io.v0.8.9.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
res.headers['cache-control']
.indexOf(io.get('browser client expires')).should.be.above(-1);
data.should.match(/XMLHttpRequest/);
cl.end();
io.server.close();
done();
});
},
'test that a custom versioned build client is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.set('browser client expires', 1337);
cl.get('/socket.io/socket.io+websocket.v0.8.10.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
res.headers['cache-control']
.indexOf(io.get('browser client expires')).should.be.above(-1);
data.should.match(/XMLHttpRequest/);
data.should.match(/WS\.prototype\.name/);
data.should.not.match(/Flashsocket\.prototype\.name/);
data.should.not.match(/HTMLFile\.prototype\.name/);
data.should.not.match(/JSONPPolling\.prototype\.name/);
data.should.not.match(/XHRPolling\.prototype\.name/);
cl.end();
io.server.close();
done();
});
},
'test that etags are ignored for versioned requests': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.enable('browser client etag');
cl.get('/socket.io/socket.io.v0.8.9.js', function (res, data) {
should.strictEqual(res.headers.etag, undefined);
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
res.headers['cache-control']
.indexOf(io.get('browser client expires')).should.be.above(-1);
data.should.match(/XMLHttpRequest/);
cl.end();
io.server.close();
done();
});
},
};

View File

@@ -1,190 +0,0 @@
/**
* Test dependencies
*
* @api private
*/
var sio = require('socket.io')
, should = require('should')
, MemoryStore = sio.MemoryStore;
/**
* Test.
*/
module.exports = {
'test storing data for a client': function (done) {
var store = new MemoryStore
, client = store.client('test');
client.id.should.equal('test');
client.set('a', 'b', function (err) {
should.strictEqual(err, null);
client.get('a', function (err, val) {
should.strictEqual(err, null);
val.should.eql('b');
client.has('a', function (err, has) {
should.strictEqual(err, null);
has.should.be.true;
client.has('b', function (err, has) {
should.strictEqual(err, null);
has.should.be.false;
client.del('a', function (err) {
should.strictEqual(err, null);
client.has('a', function (err, has) {
should.strictEqual(err, null);
has.should.be.false;
client.set('b', 'c', function (err) {
should.strictEqual(err, null);
client.set('c', 'd', function (err) {
should.strictEqual(err, null);
client.get('b', function (err, val) {
should.strictEqual(err, null);
val.should.equal('c');
client.get('c', function (err, val) {
should.strictEqual(err, null);
val.should.equal('d');
store.destroy();
done();
});
});
});
});
});
});
});
});
});
});
},
'test cleaning up clients data': function (done) {
var rand1 = Math.abs(Math.random() * Date.now() | 0)
, rand2 = Math.abs(Math.random() * Date.now() | 0);
var store = new MemoryStore()
, client1 = store.client(rand1)
, client2 = store.client(rand2);
client1.set('a', 'b', function (err) {
should.strictEqual(err, null);
client2.set('c', 'd', function (err) {
should.strictEqual(err, null);
client1.has('a', function (err, val) {
should.strictEqual(err, null);
val.should.be.true;
client2.has('c', function (err, val) {
should.strictEqual(err, null);
val.should.be.true;
store.destroy();
var newstore = new MemoryStore()
, newclient1 = newstore.client(rand1)
, newclient2 = newstore.client(rand2);
newclient1.has('a', function (err, val) {
should.strictEqual(err, null);
val.should.be.false;
newclient2.has('c', function (err, val) {
should.strictEqual(err, null);
val.should.be.false;
newstore.destroy();
done();
});
});
});
});
});
});
},
'test cleaning up a particular client': function (done) {
var rand1 = Math.abs(Math.random() * Date.now() | 0)
, rand2 = Math.abs(Math.random() * Date.now() | 0);
var store = new MemoryStore()
, client1 = store.client(rand1)
, client2 = store.client(rand2);
client1.set('a', 'b', function (err) {
should.strictEqual(err, null);
client2.set('c', 'd', function (err) {
should.strictEqual(err, null);
client1.has('a', function (err, val) {
should.strictEqual(err, null);
val.should.be.true;
client2.has('c', function (err, val) {
should.strictEqual(err, null);
val.should.be.true;
store.clients.should.have.property(rand1);
store.clients.should.have.property(rand2);
store.destroyClient(rand1);
store.clients.should.not.have.property(rand1);
store.clients.should.have.property(rand2);
client1.has('a', function (err, val) {
should.strictEqual(err, null);
val.should.equal(false);
store.destroy();
done();
});
});
});
});
});
},
'test destroy expiration': function (done) {
var store = new MemoryStore()
, id = Math.abs(Math.random() * Date.now() | 0)
, client = store.client(id);
client.set('a', 'b', function (err) {
should.strictEqual(err, null);
store.destroyClient(id, 1);
setTimeout(function () {
client.get('a', function (err, val) {
should.strictEqual(err, null);
val.should.equal('b');
});
}, 500);
setTimeout(function () {
client.get('a', function (err, val) {
should.strictEqual(err, null);
should.strictEqual(val, null);
store.destroy();
done();
});
}, 1900);
});
}
};

View File

@@ -1,240 +0,0 @@
/**
* Test dependencies
*
* @api private
*/
var sio = require('socket.io')
, redis = require('redis')
, should = require('should')
, RedisStore = sio.RedisStore;
/**
* Test.
*/
module.exports = {
'test publishing doesnt get caught by the own store subscriber': function (done) {
var a = new RedisStore
, b = new RedisStore;
a.subscribe('woot', function (arg) {
arg.should.equal('bb');
a.destroy();
b.destroy();
done();
}, function () {
a.publish('woot', 'aa');
b.publish('woot', 'bb');
});
},
'test publishing to multiple subscribers': function (done) {
var a = new RedisStore
, b = new RedisStore
, c = new RedisStore
, subscriptions = 3
, messages = 2;
a.subscribe('tobi', function () {
throw new Error('Shouldnt publish to itself');
}, publish);
function subscription (arg1, arg2, arg3) {
arg1.should.equal(1);
arg2.should.equal(2);
arg3.should.equal(3);
--messages || finish();
}
b.subscribe('tobi', subscription, publish);
c.subscribe('tobi', subscription, publish);
function publish () {
--subscriptions || a.publish('tobi', 1, 2, 3);
}
function finish () {
a.destroy();
b.destroy();
c.destroy();
done();
}
},
'test storing data for a client': function (done) {
var store = new RedisStore
, rand = 'test-' + Date.now()
, client = store.client(rand);
client.id.should.equal(rand);
client.set('a', 'b', function (err) {
should.strictEqual(err, null);
client.get('a', function (err, val) {
should.strictEqual(err, null);
val.should.equal('b');
client.has('a', function (err, has) {
should.strictEqual(err, null);
has.should.be.true;
client.has('b', function (err, has) {
should.strictEqual(err, null);
has.should.be.false;
client.del('a', function (err) {
should.strictEqual(err, null);
client.has('a', function (err, has) {
should.strictEqual(err, null);
has.should.be.false;
client.set('b', 'c', function (err) {
should.strictEqual(err, null);
client.set('c', 'd', function (err) {
should.strictEqual(err, null);
client.get('b', function (err, val) {
should.strictEqual(err, null);
val.should.equal('c');
client.get('c', function (err, val) {
should.strictEqual(err, null);
val.should.equal('d');
store.destroy();
done();
});
});
});
});
});
});
});
});
});
});
},
'test cleaning up clients data': function (done) {
var rand1 = Math.abs(Math.random() * Date.now() | 0)
, rand2 = Math.abs(Math.random() * Date.now() | 0);
var store = new RedisStore()
, client1 = store.client(rand1)
, client2 = store.client(rand2);
client1.set('a', 'b', function (err) {
should.strictEqual(err, null);
client2.set('c', 'd', function (err) {
should.strictEqual(err, null);
client1.has('a', function (err, val) {
should.strictEqual(err, null);
val.should.be.true;
client2.has('c', function (err, val) {
should.strictEqual(err, null);
val.should.be.true;
store.destroy();
var newstore = new RedisStore()
, newclient1 = newstore.client(rand1)
, newclient2 = newstore.client(rand2);
newclient1.has('a', function (err, val) {
should.strictEqual(err, null);
val.should.be.false;
newclient2.has('c', function (err, val) {
should.strictEqual(err, null);
val.should.be.false;
newstore.destroy();
done();
});
});
});
});
});
});
},
'test cleaning up a particular client': function (done) {
var rand1 = Math.abs(Math.random() * Date.now() | 0)
, rand2 = Math.abs(Math.random() * Date.now() | 0);
var store = new RedisStore()
, client1 = store.client(rand1)
, client2 = store.client(rand2);
client1.set('a', 'b', function (err) {
should.strictEqual(err, null);
client2.set('c', 'd', function (err) {
should.strictEqual(err, null);
client1.has('a', function (err, val) {
should.strictEqual(err, null);
val.should.be.true;
client2.has('c', function (err, val) {
should.strictEqual(err, null);
val.should.be.true;
store.clients.should.have.property(rand1);
store.clients.should.have.property(rand2);
store.destroyClient(rand1);
store.clients.should.not.have.property(rand1);
store.clients.should.have.property(rand2);
client1.has('a', function (err, val) {
should.strictEqual(err, null);
val.should.equal(false);
store.destroy();
done();
});
});
});
});
});
},
'test destroy expiration': function (done) {
var store = new RedisStore()
, id = Math.abs(Math.random() * Date.now() | 0)
, client = store.client(id);
client.set('a', 'b', function (err) {
should.strictEqual(err, null);
store.destroyClient(id, 1);
setTimeout(function () {
client.get('a', function (err, val) {
should.strictEqual(err, null);
val.should.equal('b');
});
}, 500);
setTimeout(function () {
client.get('a', function (err, val) {
should.strictEqual(err, null);
should.strictEqual(val, null);
store.destroy();
done();
});
}, 2000);
});
}
};

View File

@@ -1,187 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Test dependencies.
*/
var sio = require('socket.io')
, net = require('net')
, http = require('http')
, should = require('./common')
, WebSocket = require('../support/node-websocket-client/lib/websocket').WebSocket
, WSClient = require('./transports.websocket.test')
, parser = sio.parser
, ports = 15600;
/**
* FlashSocket client constructor.
*
* @api private
*/
function FlashSocket (port, sid) {
this.sid = sid;
this.port = port;
WebSocket.call(
this
, 'ws://localhost:' + port + '/socket.io/'
+ sio.protocol + '/flashsocket/' + sid
);
};
/**
* Inherits from WSClient.
*/
FlashSocket.prototype.__proto__ = WebSocket.prototype;
/**
* Creates a TCP connection to a port.
*
* @api public
*/
function netConnection (port, callback){
var nclient = net.createConnection(port);
nclient.on('data', function (data) {
callback.call(nclient, null, data);
});
nclient.on('error', function (e){
callback.call(nclient, e);
});
nclient.write('<policy-file-request/>\0');
}
/**
* Tests.
*/
module.exports = {
'flashsocket disabled by default': function (done) {
var io = sio.listen(http.createServer());
io.get('transports').should.not.contain('flashsocket');
done();
},
'flash policy port': function (done) {
var io = sio.listen(http.createServer())
, port = ++ports;
io.get('flash policy port').should.eql(10843);
io.set('flash policy port', port);
io.get('flash policy port').should.eql(port);
should.strictEqual(io.flashPolicyServer, undefined);
netConnection(port, function (err, data){
err.should.be.an.instanceof(Error);
err.code.should.eql('ECONNREFUSED');
this.destroy();
done();
})
},
'start flash policy': function (done) {
var io = sio.listen(http.createServer())
, port = ++ports;
io.set('flash policy port', port);
io.set('transports', ['flashsocket']);
io.flashPolicyServer.should.be.a('object');
netConnection(port, function (err, data){
should.strictEqual(err, null);
data.toString().should.include.string('<cross-domain-policy>');
this.destroy();
io.flashPolicyServer.close();
done();
})
},
'change running flash server port': function (done) {
var io = sio.listen(http.createServer())
, port = ++ports
, next = ++ports;
io.set('flash policy port', port);
io.set('transports', ['flashsocket']);
io.set('flash policy port', next);
io.flashPolicyServer.port.should.eql(next);
netConnection(port, function (err, data){
err.should.be.an.instanceof(Error);
err.code.should.eql('ECONNREFUSED');
this.destroy();
// should work
netConnection(next, function (err, data){
should.strictEqual(err, null);
data.toString().should.include.string('<cross-domain-policy>');
this.destroy();
io.flashPolicyServer.close();
done();
});
});
},
'different origins': function(done) {
var io = sio.listen(http.createServer())
, port = ++ports;
io.set('flash policy port', port);
io.set('transports', ['flashsocket']);
io.set('origins', 'google.com:80');
var server = io.flashPolicyServer;
server.origins.should.contain('google.com:80');
server.origins.should.not.contain('*.*');
io.set('origins', ['foo.bar:80', 'socket.io:1337']);
server.origins.should.not.contain('google.com:80');
server.origins.should.contain('foo.bar:80');
server.origins.should.contain('socket.io:1337');
server.buffer.toString('utf8').should.include.string('socket.io');
io.flashPolicyServer.close();
done();
},
'flashsocket identifies as flashsocket': function (done) {
var cl = client(++ports)
, io = create(cl)
, ws;
io.set('transports', ['flashsocket']);
io.sockets.on('connection', function (socket) {
socket.manager.transports[socket.id].name.should.equal('flashsocket');
ws.finishClose();
cl.end();
io.flashPolicyServer.close();
io.server.close();
done();
});
cl.handshake(function (sid) {
ws = websocket(cl, sid, 'flashsocket');
});
}
};

View File

@@ -1,458 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Test dependencies.
*/
var sio = require('socket.io')
, should = require('./common')
, HTTPClient = should.HTTPClient
, parser = sio.parser
, ports = 15300;
/**
* HTTPClient for htmlfile transport.
*/
function HTMLFile (port) {
HTTPClient.call(this, port);
};
/**
* Inhertis from HTTPClient.
*/
HTMLFile.prototype.__proto__ = HTTPClient.prototype;
/**
* Override GET request with streaming parser.
*
* @api public
*/
var head = '<script>_('
, foot = ');</script>'
, initial = '<html><body>'
+ '<script>var _ = function (msg) { parent.s._(msg, document); };</script>'
+ new Array(174).join(' ')
HTMLFile.prototype.data = function (path, opts, fn) {
if ('function' == typeof opts) {
fn = opts;
opts = {};
}
opts.buffer = false;
return this.request(path, opts, function (res) {
var buf = ''
, messages = 0
, state = 0;
res.on('data', function (chunk) {
buf += chunk;
function parse () {
switch (state) {
case 0:
if (buf.indexOf(initial) === 0) {
buf = buf.substr(initial.length);
state = 1;
} else {
break;
}
case 1:
if (buf.indexOf(head) === 0) {
buf = buf.substr(head.length);
state = 2;
} else {
break;
}
case 2:
if (buf.indexOf(foot) != -1) {
var data = buf.slice(0, buf.indexOf(foot))
, obj = JSON.parse(data);
fn(obj === '' ? obj : parser.decodePayload(obj), ++messages);
buf = buf.substr(data.length + foot.length);
state = 1;
parse();
}
};
};
parse();
});
});
};
/**
* Create client for this transport.
*
* @api public
*/
function client (port) {
return new HTMLFile(port);
};
/**
* Tests.
*/
module.exports = {
'test that not responding to a heartbeat drops client': function (done) {
var port = ++ports
, cl = client(port)
, io = create(cl)
, beat = false;
io.configure(function () {
io.set('heartbeat interval', .05);
io.set('heartbeat timeout', .05);
io.set('close timeout', 0);
});
io.sockets.on('connection', function (socket) {
socket.on('disconnect', function (reason) {
beat.should.be.true;
reason.should.eql('heartbeat timeout');
cl.end();
io.server.close();
done();
});
});
cl.handshake(function (sid) {
cl.data('/socket.io/{protocol}/htmlfile/' + sid, function (msgs, i) {
switch (i) {
case 1:
msgs.should.have.length(1);
msgs[0].type.should.eql('connect');
msgs[0].endpoint.should.eql('');
break;
case 2:
msgs.should.have.length(1);
msgs[0].type.should.eql('heartbeat');
beat = true;
};
});
});
},
'test that responding to a heartbeat maintains session': function (done) {
var port = ++ports
, cl = client(port)
, io = create(cl)
, heartbeats = 0;
io.configure(function () {
io.set('heartbeat interval', .05);
io.set('heartbeat timeout', .05);
io.set('close timeout', 0);
});
io.sockets.on('connection', function (socket) {
socket.on('disconnect', function (reason) {
heartbeats.should.eql(2);
reason.should.eql('heartbeat timeout');
cl.end();
io.server.close();
done();
});
});
cl.handshake(function (sid) {
cl.data('/socket.io/{protocol}/htmlfile/' + sid, function (msgs, i) {
switch (i) {
case 1:
msgs.should.have.length(1);
msgs[0].type.should.eql('connect');
msgs[0].endpoint.should.eql('');
break;
default:
msgs.should.have.length(1);
msgs[0].type.should.eql('heartbeat');
heartbeats++;
if (heartbeats == 1) {
cl.post('/socket.io/{protocol}/htmlfile/' + sid, parser.encodePacket({
type: 'heartbeat'
}));
}
}
});
});
},
'test sending undeliverable volatile messages': function (done) {
var port = ++ports
, cl = client(port)
, io = create(cl)
, messaged = false
, s;
io.configure(function () {
io.set('close timeout', 0);
});
io.sockets.on('connection', function (socket) {
s = socket;
socket.on('disconnect', function () {
messaged.should.be.false;
io.server.close();
done();
});
});
cl.handshake(function (sid) {
cl.data('/socket.io/{protocol}/htmlfile/' + sid, function () { });
setTimeout(function () {
cl.end();
setTimeout(function () {
s.volatile.send('wooooot');
cl = client(port);
cl.data('/socket.io/{protocol}/htmlfile/' + sid, function (msgs) {
if (msgs && msgs.length)
messaged = true;
});
setTimeout(function () {
cl.end();
}, 20);
}, 20);
}, 20);
});
},
'test sending undeliverable volatile json': function (done) {
var port = ++ports
, cl = client(port)
, io = create(cl)
, messaged = false
, s;
io.configure(function () {
io.set('close timeout', 0);
});
io.sockets.on('connection', function (socket) {
s = socket;
socket.on('disconnect', function () {
messaged.should.be.false;
io.server.close();
done();
});
});
cl.handshake(function (sid) {
cl.data('/socket.io/{protocol}/htmlfile/' + sid, function () { });
setTimeout(function () {
cl.end();
setTimeout(function () {
s.volatile.json.send(123);
cl = client(port);
cl.data('/socket.io/{protocol}/htmlfile/' + sid, function (msgs) {
if (msgs && msgs.length)
messaged = true;
});
setTimeout(function () {
cl.end();
}, 20);
}, 20);
}, 20);
});
},
'test sending undeliverable volatile events': function (done) {
var port = ++ports
, cl = client(port)
, io = create(cl)
, messaged = false
, s;
io.configure(function () {
io.set('close timeout', 0);
});
io.sockets.on('connection', function (socket) {
s = socket;
socket.on('disconnect', function () {
messaged.should.be.false;
io.server.close();
done();
});
});
cl.handshake(function (sid) {
cl.data('/socket.io/{protocol}/htmlfile/' + sid, function () { });
setTimeout(function () {
cl.end();
setTimeout(function () {
s.volatile.emit('tobi');
cl = client(port);
cl.data('/socket.io/{protocol}/htmlfile/' + sid, function (msgs) {
if (msgs && msgs.length)
messaged = true;
});
setTimeout(function () {
cl.end();
}, 20);
}, 20);
}, 20);
});
},
'test sending deliverable volatile messages': function (done) {
var port = ++ports
, cl = client(port)
, io = create(cl)
, messaged = false;
io.configure(function () {
io.set('close timeout', 0);
});
io.sockets.on('connection', function (socket) {
socket.volatile.send('woot');
socket.on('disconnect', function () {
io.server.close();
done();
});
});
cl.handshake(function (sid) {
cl.data('/socket.io/{protocol}/htmlfile/' + sid, function (msgs, i) {
switch (i) {
case 1:
msgs.should.have.length(1);
msgs[0].type.should.eql('connect');
msgs[0].endpoint.should.eql('');
break;
case 2:
msgs.should.have.length(1);
msgs[0].should.eql({
type: 'message'
, data: 'woot'
, endpoint: ''
});
cl.end();
}
});
});
},
'test sending deliverable volatile json': function (done) {
var port = ++ports
, cl = client(port)
, io = create(cl)
, messaged = false;
io.configure(function () {
io.set('close timeout', 0);
});
io.sockets.on('connection', function (socket) {
socket.volatile.json.send(['woot']);
socket.on('disconnect', function () {
io.server.close();
done();
});
});
cl.handshake(function (sid) {
cl.data('/socket.io/{protocol}/htmlfile/' + sid, function (msgs, i) {
switch (i) {
case 1:
msgs.should.have.length(1);
msgs[0].type.should.eql('connect');
msgs[0].endpoint.should.eql('');
break;
case 2:
msgs.should.have.length(1);
msgs[0].should.eql({
type: 'json'
, data: ['woot']
, endpoint: ''
});
cl.end();
}
});
});
},
'test sending deliverable volatile events': function (done) {
var port = ++ports
, cl = client(port)
, io = create(cl)
, messaged = false;
io.configure(function () {
io.set('close timeout', 0);
});
io.sockets.on('connection', function (socket) {
socket.volatile.emit('aaa');
socket.on('disconnect', function () {
io.server.close();
done();
});
});
cl.handshake(function (sid) {
cl.data('/socket.io/{protocol}/htmlfile/' + sid, function (msgs, i) {
switch (i) {
case 1:
msgs.should.have.length(1);
msgs[0].type.should.eql('connect');
msgs[0].endpoint.should.eql('');
break;
case 2:
msgs.should.have.length(1);
msgs[0].should.eql({
type: 'event'
, name: 'aaa'
, endpoint: ''
, args: []
});
cl.end();
}
});
});
}
};

View File

@@ -1,774 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Test dependencies.
*/
var sio = require('socket.io')
, should = require('./common')
, qs = require('querystring')
, HTTPClient = should.HTTPClient
, parser = sio.parser
, ports = 15500;
/**
* HTTPClient for jsonp-polling transport.
*/
function JSONPPolling (port) {
HTTPClient.call(this, port);
};
/**
* Inhertis from HTTPClient.
*/
JSONPPolling.prototype.__proto__ = HTTPClient.prototype;
/**
* Performs a json-p (cross domain) handshake
*
* @api public
*/
JSONPPolling.prototype.handshake = function (opts, fn) {
if ('function' == typeof opts) {
fn = opts;
opts = {};
}
var self = this;
return this.get(
'/socket.io/{protocol}?jsonp=0'
, opts
, function (res, data) {
var head = 'io.j[0]('
, foot = ');';
data.substr(0, head.length).should.eql(head);
data.substr(-foot.length).should.eql(foot);
data = data.slice(head.length, data.length - foot.length);
var parts = JSON.parse(data).split(':');
if (opts.ignoreConnect) {
return fn && fn.apply(null, parts);
}
// expect connect packet right after handshake
self.get(
'/socket.io/{protocol}/jsonp-polling/' + parts[0]
, function (res, msgs) {
res.statusCode.should.eql(200);
msgs.should.have.length(1);
msgs[0].should.eql({ type: 'connect', endpoint: '', qs: '' });
fn && fn.apply(null, parts);
}
);
}
);
};
/**
* Override GET requests.
*
* @api public
*/
JSONPPolling.prototype.get = function (path, opts, fn) {
if ('function' == typeof opts) {
fn = opts;
opts = {};
}
opts = opts || {};
opts.parse = function (data) {
var head = 'io.j[0]('
, foot = ');';
if (~path.indexOf('?i=1')) {
head = 'io.j[1](';
}
data.substr(0, head.length).should.eql(head);
data.substr(-foot.length).should.eql(foot);
data = data.substr(head.length, data.length - head.length - foot.length);
return JSON.parse(data);
};
return HTTPClient.prototype.get.call(this, path, opts, fn);
};
/**
* Issue an encoded POST request
*
* @api private
*/
JSONPPolling.prototype.post = function (path, data, opts, fn) {
if ('function' == typeof opts) {
fn = opts;
opts = {};
}
opts = opts || {};
opts.method = 'POST';
opts.data = qs.stringify({ d: data });
return this.request(path, opts, fn);
};
/**
* Create client for this transport.
*
* @api public
*/
function client (port) {
return new JSONPPolling(port);
};
/**
* Test.
*/
module.exports = {
'test jsonp handshake': function (done) {
var cl = client(++ports)
, io = create(cl);
io.configure(function () {
io.set('close timeout', .05);
io.set('polling duration', 0);
});
function finish () {
cl.end();
io.server.close();
done();
};
cl.handshake(function (sid) {
var total = 2;
cl.get('/socket.io/{protocol}/jsonp-polling/tobi', function (res, msgs) {
res.statusCode.should.eql(200);
msgs.should.have.length(1);
msgs[0].should.eql({
type: 'error'
, reason: 'client not handshaken'
, endpoint: ''
, advice: 'reconnect'
});
--total || finish();
});
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, msgs) {
res.statusCode.should.eql(200);
msgs.should.have.length(1);
msgs[0].should.eql({ type: 'noop', endpoint: '' });
--total || finish();
});
});
},
'test the connection event': function (done) {
var cl = client(++ports)
, io = create(cl)
, sid;
io.configure(function () {
io.set('polling duration', 0);
io.set('close timeout', .05);
});
io.sockets.on('connection', function (socket) {
socket.id.should.eql(sid);
socket.on('disconnect', function () {
cl.end();
io.server.close();
done();
});
});
cl.handshake({ ignoreConnect: true }, function (sessid) {
sid = sessid;
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, msgs) {
res.statusCode.should.eql(200);
msgs.should.have.length(1);
msgs[0].type.should.eql('connect');
});
});
},
'test the disconnection event after a close timeout': function (done) {
var cl = client(++ports)
, io = create(cl)
, sid;
io.configure(function () {
io.set('close timeout', 0);
});
io.sockets.on('connection', function (socket) {
socket.id.should.eql(sid);
socket.on('disconnect', function () {
io.server.close();
done();
});
});
cl.handshake({ ignoreConnect: true }, function (sessid) {
sid = sessid;
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, msgs) {
res.statusCode.should.eql(200);
msgs.should.have.length(1);
msgs[0].type.should.eql('connect');
setTimeout(function () {
cl.end();
}, 10);
});
});
},
'test the disconnection event when the client sends ?disconnect req': function (done) {
var cl = client(++ports)
, io = create(cl)
, disconnected = false
, sid;
io.configure(function () {
io.set('close timeout', .05);
});
io.sockets.on('connection', function (socket) {
socket.on('disconnect', function () {
disconnected = true;
});
});
cl.handshake({ ignoreConnect: true }, function (sessid) {
sid = sessid;
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, msgs) {
res.statusCode.should.eql(200);
msgs.should.have.length(1);
msgs[0].type.should.eql('connect');
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, msgs) {
msgs.should.have.length(1);
msgs[0].should.eql({ type: 'disconnect', endpoint: '' });
disconnected.should.be.true;
io.server.close();
cl.end();
done();
});
// with the new http bits in node 0.5, there's no guarantee that
// the previous request is actually dispatched (and received) before the following
// reset call is sent. to not waste more time on a workaround, a timeout is added.
setTimeout(function() {
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid + '/?disconnect');
}, 500);
});
});
},
'test the disconnection event booting a client': function (done) {
var cl = client(++ports)
, io = create(cl)
, forced = false;
io.sockets.on('connection', function (socket) {
socket.on('disconnect', function () {
io.server.close();
done();
});
cl.end();
socket.disconnect();
forced = true;
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, msgs) {
msgs.should.have.length(1);
msgs[0].should.eql({ type: 'disconnect', endpoint: '' });
forced.should.be.true;
});
});
},
'test the disconnection event with client disconnect packet': function (done) {
var cl = client(++ports)
, io = create(cl)
, sid;
io.sockets.on('connection', function (client) {
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, JSON.stringify(parser.encodePacket({ type: 'disconnect' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
}
);
client.on('disconnect', function () {
cl.end();
io.server.close();
done();
});
});
cl.handshake({ ignoreConnect: true }, function (sessid) {
sid = sessid;
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, msgs) {
res.statusCode.should.eql(200);
msgs.should.have.length(1);
msgs[0].type.should.eql('connect');
});
});
},
'test sending back data': function (done) {
var cl = client(++ports)
, io = create(cl);
io.configure(function () {
io.set('polling duration', .05);
io.set('close timeout', .05);
});
io.sockets.on('connection', function (socket) {
socket.send('woot');
socket.on('disconnect', function () {
cl.end();
io.server.close();
done();
});
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, packs) {
packs.should.have.length(1);
packs[0].type.should.eql('message');
packs[0].data.should.eql('woot');
});
});
},
'test sending a batch of messages': function (done) {
var cl = client(++ports)
, io = create(cl)
, sid;
io.configure(function () {
io.set('close timeout', .05);
});
io.sockets.on('connection', function (socket) {
var messages = 0;
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, JSON.stringify(parser.encodePayload([
parser.encodePacket({ type: 'message', data: 'a' })
, parser.encodePacket({ type: 'message', data: 'b' })
, parser.encodePacket({ type: 'disconnect' })
]))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
}
);
socket.on('message', function (data) {
messages++;
if (messages == 1)
data.should.eql('a');
if (messages == 2)
data.should.eql('b');
});
socket.on('disconnect', function () {
messages.should.eql(2);
cl.end();
io.server.close();
done();
});
});
cl.handshake({ ignoreConnect: true }, function (sessid) {
sid = sessid;
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, msgs) {
res.statusCode.should.eql(200);
msgs.should.have.length(1);
msgs[0].type.should.eql('connect');
});
});
},
'test message buffering between a response and a request': function (done) {
var cl = client(++ports)
, io = create(cl)
, messages = false
, tobi;
io.configure(function () {
io.set('polling duration', .1);
io.set('close timeout', .2);
});
io.sockets.on('connection', function (socket) {
tobi = function () {
socket.send('a');
socket.send('b');
socket.send('c');
};
socket.on('disconnect', function () {
messages.should.be.true;
cl.end();
io.server.close();
done();
});
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, msgs) {
msgs.should.have.length(1);
msgs[0].should.eql({ type: 'noop', endpoint: '' });
tobi();
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, msgs) {
msgs.should.have.length(3);
msgs[0].should.eql({ type: 'message', endpoint: '', data: 'a' });
msgs[1].should.eql({ type: 'message', endpoint: '', data: 'b' });
msgs[2].should.eql({ type: 'message', endpoint: '', data: 'c' });
messages = true;
});
})
});
},
'test connecting to a specific endpoint': function (done) {
var cl = client(++ports)
, io = create(cl)
, connectMessage = false
, sid;
io.configure(function () {
io.set('polling duration', 0);
io.set('close timeout', .05);
});
io.of('/woot').on('connection', function (socket) {
connectMessage.should.be.true;
socket.on('disconnect', function () {
cl.end();
io.server.close();
done();
});
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, data) {
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid);
connectMessage = true;
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, JSON.stringify(parser.encodePacket({ type: 'connect', endpoint: '/woot' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
}
);
});
});
},
'test that connecting doesnt connect to defined endpoints': function (done) {
var cl = client(++ports)
, io = create(cl)
, tobiConnected = false
, mainConnected = false
, sid;
io.configure(function () {
io.set('polling duration', .05);
io.set('close timeout', .05);
});
io.sockets.on('connection', function (socket) {
mainConnected = true;
socket.on('disconnect', function () {
tobiConnected.should.be.false;
cl.end();
io.server.close();
done();
});
});
io.of('/tobi').on('connection', function () {
tobiConnected = true;
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid);
});
},
'test disconnecting a specific endpoint': function (done) {
var cl = client(++ports)
, io = create(cl)
, wootDisconnected = false
, mainDisconnected = false
, checked = false;
io.configure(function () {
io.set('polling duration', 0);
io.set('close timeout', .05);
});
io.sockets.on('connection', function (socket) {
socket.on('message', function (data) {
data.should.eql('ferret');
mainDisconnected.should.be.false;
wootDisconnected.should.be.true;
checked = true;
});
socket.on('disconnect', function () {
mainDisconnected = true;
checked.should.be.true;
cl.end();
io.server.close();
done();
});
});
io.of('/woot').on('connection', function (socket) {
socket.on('disconnect', function () {
wootDisconnected = true;
});
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function () {
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, JSON.stringify(parser.encodePacket({ type: 'connect', endpoint: '/woot' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, JSON.stringify(parser.encodePacket({ type: 'disconnect', endpoint: '/woot' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, JSON.stringify(parser.encodePacket({ type: 'message', data: 'ferret' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
}
);
}
);
}
);
});
});
},
'test that disconnecting disconnects all endpoints': function (done) {
var cl = client(++ports)
, io = create(cl)
, aDisconnected = false
, bDisconnected = false;
io.configure(function () {
io.set('polling duration', .2);
io.set('close timeout', .2);
});
io.sockets.on('connection', function (socket) {
socket.on('disconnect', function () {
setTimeout(function () {
aDisconnected.should.be.true;
bDisconnected.should.be.true;
cl.end();
io.server.close();
done();
}, 50);
});
});
io.of('/a').on('connection', function (socket) {
socket.on('disconnect', function (msg) {
aDisconnected = true;
});
});
io.of('/b').on('connection', function (socket) {
socket.on('disconnect', function (msg) {
bDisconnected = true;
});
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, msgs) {
res.statusCode.should.eql(200);
msgs.should.have.length(1);
msgs[0].should.eql({ type: 'noop', endpoint: '' });
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, JSON.stringify(parser.encodePacket({ type: 'connect', endpoint: '/a' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
}
);
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, JSON.stringify(parser.encodePacket({ type: 'connect', endpoint: '/b' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
}
);
});
});
},
'test messaging a specific endpoint': function (done) {
var cl = client(++ports)
, io = create(cl)
, messaged = true
, aMessaged = false
, bMessaged = false;
io.configure(function () {
io.set('polling duration', 0);
io.set('close timeout', .05);
});
io.sockets.on('connection', function (socket) {
socket.on('message', function (msg) {
msg.should.eql('');
messaged = true;
});
socket.on('disconnect', function () {
messaged.should.be.true;
aMessaged.should.be.true;
bMessaged.should.be.true;
cl.end();
io.server.close();
done();
});
});
io.of('/a').on('connection', function (socket) {
socket.on('message', function (msg) {
msg.should.eql('a');
aMessaged = true;
});
});
io.of('/b').on('connection', function (socket) {
socket.on('message', function (msg) {
msg.should.eql('b');
bMessaged = true;
});
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, data) {
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, JSON.stringify(parser.encodePacket({ type: 'message', data: '' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
}
);
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, JSON.stringify(parser.encodePacket({ type: 'connect', endpoint: '/a' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, JSON.stringify(parser.encodePacket({ type: 'message', endpoint: '/a', data: 'a' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
}
);
}
);
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, JSON.stringify(parser.encodePacket({ type: 'connect', endpoint: '/b' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, JSON.stringify(parser.encodePacket({ type: 'message', endpoint: '/b', data: 'b' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
}
);
}
);
});
});
}
};

View File

@@ -1,262 +0,0 @@
/**
* Test dependencies.
*/
var assert = require('assert');
var Parser = require('../lib/transports/websocket/hybi-07-12.js').Parser;
require('./hybi-common');
/**
* Tests.
*/
module.exports = {
'can parse unmasked text message': function() {
var p = new Parser();
var packet = '81 05 48 65 6c 6c 6f';
var gotData = false;
p.on('data', function(data) {
gotData = true;
assert.equal('Hello', data);
});
p.add(getBufferFromHexString(packet));
assert.ok(gotData);
},
'can parse close message': function() {
var p = new Parser();
var packet = '88 00';
var gotClose = false;
p.on('close', function(data) {
gotClose = true;
});
p.add(getBufferFromHexString(packet));
assert.ok(gotClose);
},
'can parse masked text message': function() {
var p = new Parser();
var packet = '81 93 34 83 a8 68 01 b9 92 52 4f a1 c6 09 59 e6 8a 52 16 e6 cb 00 5b a1 d5';
var gotData = false;
p.on('data', function(data) {
gotData = true;
assert.equal('5:::{"name":"echo"}', data);
});
p.add(getBufferFromHexString(packet));
assert.ok(gotData);
},
'can parse a masked text message longer than 125 bytes': function() {
var p = new Parser();
var message = 'A';
for (var i = 0; i < 300; ++i) message += (i % 5).toString();
var packet = '81 FE ' + pack(4, message.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
var gotData = false;
p.on('data', function(data) {
gotData = true;
assert.equal(message, data);
});
p.add(getBufferFromHexString(packet));
assert.ok(gotData);
},
'can parse a really long masked text message': function() {
var p = new Parser();
var message = 'A';
for (var i = 0; i < 64*1024; ++i) message += (i % 5).toString();
var packet = '81 FF ' + pack(16, message.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
var gotData = false;
p.on('data', function(data) {
gotData = true;
assert.equal(message, data);
});
p.add(getBufferFromHexString(packet));
assert.ok(gotData);
},
'can parse a fragmented masked text message of 300 bytes': function() {
var p = new Parser();
var message = 'A';
for (var i = 0; i < 300; ++i) message += (i % 5).toString();
var msgpiece1 = message.substr(0, 150);
var msgpiece2 = message.substr(150);
var packet1 = '01 FE ' + pack(4, msgpiece1.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece1, '34 83 a8 68'));
var packet2 = '80 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece2, '34 83 a8 68'));
var gotData = false;
p.on('data', function(data) {
gotData = true;
assert.equal(message, data);
});
p.add(getBufferFromHexString(packet1));
p.add(getBufferFromHexString(packet2));
assert.ok(gotData);
},
'can parse a ping message': function() {
var p = new Parser();
var message = 'Hello';
var packet = '89 FE ' + pack(4, message.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
var gotPing = false;
p.on('ping', function(data) {
gotPing = true;
assert.equal(message, data);
});
p.add(getBufferFromHexString(packet));
assert.ok(gotPing);
},
'can parse a ping with no data': function() {
var p = new Parser();
var packet = '89 00';
var gotPing = false;
p.on('ping', function(data) {
gotPing = true;
});
p.add(getBufferFromHexString(packet));
assert.ok(gotPing);
},
'can parse a fragmented masked text message of 300 bytes with a ping in the middle': function() {
var p = new Parser();
var message = 'A';
for (var i = 0; i < 300; ++i) message += (i % 5).toString();
var msgpiece1 = message.substr(0, 150);
var packet1 = '01 FE ' + pack(4, msgpiece1.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece1, '34 83 a8 68'));
var pingMessage = 'Hello';
var pingPacket = '89 FE ' + pack(4, pingMessage.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(pingMessage, '34 83 a8 68'));
var msgpiece2 = message.substr(150);
var packet2 = '80 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece2, '34 83 a8 68'));
var gotData = false;
p.on('data', function(data) {
gotData = true;
assert.equal(message, data);
});
var gotPing = false;
p.on('ping', function(data) {
gotPing = true;
assert.equal(pingMessage, data);
});
p.add(getBufferFromHexString(packet1));
p.add(getBufferFromHexString(pingPacket));
p.add(getBufferFromHexString(packet2));
assert.ok(gotData);
assert.ok(gotPing);
},
'can parse a fragmented masked text message of 300 bytes with a ping in the middle, which is delievered over sevaral tcp packets': function() {
var p = new Parser();
var message = 'A';
for (var i = 0; i < 300; ++i) message += (i % 5).toString();
var msgpiece1 = message.substr(0, 150);
var packet1 = '01 FE ' + pack(4, msgpiece1.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece1, '34 83 a8 68'));
var pingMessage = 'Hello';
var pingPacket = '89 FE ' + pack(4, pingMessage.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(pingMessage, '34 83 a8 68'));
var msgpiece2 = message.substr(150);
var packet2 = '80 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece2, '34 83 a8 68'));
var gotData = false;
p.on('data', function(data) {
gotData = true;
assert.equal(message, data);
});
var gotPing = false;
p.on('ping', function(data) {
gotPing = true;
assert.equal(pingMessage, data);
});
var buffers = [];
buffers = buffers.concat(splitBuffer(getBufferFromHexString(packet1)));
buffers = buffers.concat(splitBuffer(getBufferFromHexString(pingPacket)));
buffers = buffers.concat(splitBuffer(getBufferFromHexString(packet2)));
for (var i = 0; i < buffers.length; ++i) {
p.add(buffers[i]);
}
assert.ok(gotData);
assert.ok(gotPing);
},
'can parse a 100 byte long masked binary message': function() {
var p = new Parser();
var length = 100;
var message = new Buffer(length);
for (var i = 0; i < length; ++i) message[i] = i % 256;
var originalMessage = getHexStringFromBuffer(message);
var packet = '82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
var gotData = false;
p.on('binary', function(data) {
gotData = true;
assert.equal(originalMessage, getHexStringFromBuffer(data));
});
p.add(getBufferFromHexString(packet));
assert.ok(gotData);
},
'can parse a 256 byte long masked binary message': function() {
var p = new Parser();
var length = 256;
var message = new Buffer(length);
for (var i = 0; i < length; ++i) message[i] = i % 256;
var originalMessage = getHexStringFromBuffer(message);
var packet = '82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
var gotData = false;
p.on('binary', function(data) {
gotData = true;
assert.equal(originalMessage, getHexStringFromBuffer(data));
});
p.add(getBufferFromHexString(packet));
assert.ok(gotData);
},
'can parse a 200kb long masked binary message': function() {
var p = new Parser();
var length = 200 * 1024;
var message = new Buffer(length);
for (var i = 0; i < length; ++i) message[i] = i % 256;
var originalMessage = getHexStringFromBuffer(message);
var packet = '82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
var gotData = false;
p.on('binary', function(data) {
gotData = true;
assert.equal(originalMessage, getHexStringFromBuffer(data));
});
p.add(getBufferFromHexString(packet));
assert.ok(gotData);
},
'can parse a 200kb long unmasked binary message': function() {
var p = new Parser();
var length = 200 * 1024;
var message = new Buffer(length);
for (var i = 0; i < length; ++i) message[i] = i % 256;
var originalMessage = getHexStringFromBuffer(message);
var packet = '82 ' + getHybiLengthAsHexString(length, false) + ' ' + getHexStringFromBuffer(message);
var gotData = false;
p.on('binary', function(data) {
gotData = true;
assert.equal(originalMessage, getHexStringFromBuffer(data));
});
p.add(getBufferFromHexString(packet));
assert.ok(gotData);
},
};

View File

@@ -1,262 +0,0 @@
/**
* Test dependencies.
*/
var assert = require('assert');
var Parser = require('../lib/transports/websocket/hybi-16.js').Parser;
require('./hybi-common');
/**
* Tests.
*/
module.exports = {
'can parse unmasked text message': function() {
var p = new Parser();
var packet = '81 05 48 65 6c 6c 6f';
var gotData = false;
p.on('data', function(data) {
gotData = true;
assert.equal('Hello', data);
});
p.add(getBufferFromHexString(packet));
assert.ok(gotData);
},
'can parse close message': function() {
var p = new Parser();
var packet = '88 00';
var gotClose = false;
p.on('close', function(data) {
gotClose = true;
});
p.add(getBufferFromHexString(packet));
assert.ok(gotClose);
},
'can parse masked text message': function() {
var p = new Parser();
var packet = '81 93 34 83 a8 68 01 b9 92 52 4f a1 c6 09 59 e6 8a 52 16 e6 cb 00 5b a1 d5';
var gotData = false;
p.on('data', function(data) {
gotData = true;
assert.equal('5:::{"name":"echo"}', data);
});
p.add(getBufferFromHexString(packet));
assert.ok(gotData);
},
'can parse a masked text message longer than 125 bytes': function() {
var p = new Parser();
var message = 'A';
for (var i = 0; i < 300; ++i) message += (i % 5).toString();
var packet = '81 FE ' + pack(4, message.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
var gotData = false;
p.on('data', function(data) {
gotData = true;
assert.equal(message, data);
});
p.add(getBufferFromHexString(packet));
assert.ok(gotData);
},
'can parse a really long masked text message': function() {
var p = new Parser();
var message = 'A';
for (var i = 0; i < 64*1024; ++i) message += (i % 5).toString();
var packet = '81 FF ' + pack(16, message.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
var gotData = false;
p.on('data', function(data) {
gotData = true;
assert.equal(message, data);
});
p.add(getBufferFromHexString(packet));
assert.ok(gotData);
},
'can parse a fragmented masked text message of 300 bytes': function() {
var p = new Parser();
var message = 'A';
for (var i = 0; i < 300; ++i) message += (i % 5).toString();
var msgpiece1 = message.substr(0, 150);
var msgpiece2 = message.substr(150);
var packet1 = '01 FE ' + pack(4, msgpiece1.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece1, '34 83 a8 68'));
var packet2 = '80 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece2, '34 83 a8 68'));
var gotData = false;
p.on('data', function(data) {
gotData = true;
assert.equal(message, data);
});
p.add(getBufferFromHexString(packet1));
p.add(getBufferFromHexString(packet2));
assert.ok(gotData);
},
'can parse a ping message': function() {
var p = new Parser();
var message = 'Hello';
var packet = '89 FE ' + pack(4, message.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
var gotPing = false;
p.on('ping', function(data) {
gotPing = true;
assert.equal(message, data);
});
p.add(getBufferFromHexString(packet));
assert.ok(gotPing);
},
'can parse a ping with no data': function() {
var p = new Parser();
var packet = '89 00';
var gotPing = false;
p.on('ping', function(data) {
gotPing = true;
});
p.add(getBufferFromHexString(packet));
assert.ok(gotPing);
},
'can parse a fragmented masked text message of 300 bytes with a ping in the middle': function() {
var p = new Parser();
var message = 'A';
for (var i = 0; i < 300; ++i) message += (i % 5).toString();
var msgpiece1 = message.substr(0, 150);
var packet1 = '01 FE ' + pack(4, msgpiece1.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece1, '34 83 a8 68'));
var pingMessage = 'Hello';
var pingPacket = '89 FE ' + pack(4, pingMessage.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(pingMessage, '34 83 a8 68'));
var msgpiece2 = message.substr(150);
var packet2 = '80 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece2, '34 83 a8 68'));
var gotData = false;
p.on('data', function(data) {
gotData = true;
assert.equal(message, data);
});
var gotPing = false;
p.on('ping', function(data) {
gotPing = true;
assert.equal(pingMessage, data);
});
p.add(getBufferFromHexString(packet1));
p.add(getBufferFromHexString(pingPacket));
p.add(getBufferFromHexString(packet2));
assert.ok(gotData);
assert.ok(gotPing);
},
'can parse a fragmented masked text message of 300 bytes with a ping in the middle, which is delievered over sevaral tcp packets': function() {
var p = new Parser();
var message = 'A';
for (var i = 0; i < 300; ++i) message += (i % 5).toString();
var msgpiece1 = message.substr(0, 150);
var packet1 = '01 FE ' + pack(4, msgpiece1.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece1, '34 83 a8 68'));
var pingMessage = 'Hello';
var pingPacket = '89 FE ' + pack(4, pingMessage.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(pingMessage, '34 83 a8 68'));
var msgpiece2 = message.substr(150);
var packet2 = '80 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece2, '34 83 a8 68'));
var gotData = false;
p.on('data', function(data) {
gotData = true;
assert.equal(message, data);
});
var gotPing = false;
p.on('ping', function(data) {
gotPing = true;
assert.equal(pingMessage, data);
});
var buffers = [];
buffers = buffers.concat(splitBuffer(getBufferFromHexString(packet1)));
buffers = buffers.concat(splitBuffer(getBufferFromHexString(pingPacket)));
buffers = buffers.concat(splitBuffer(getBufferFromHexString(packet2)));
for (var i = 0; i < buffers.length; ++i) {
p.add(buffers[i]);
}
assert.ok(gotData);
assert.ok(gotPing);
},
'can parse a 100 byte long masked binary message': function() {
var p = new Parser();
var length = 100;
var message = new Buffer(length);
for (var i = 0; i < length; ++i) message[i] = i % 256;
var originalMessage = getHexStringFromBuffer(message);
var packet = '82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
var gotData = false;
p.on('binary', function(data) {
gotData = true;
assert.equal(originalMessage, getHexStringFromBuffer(data));
});
p.add(getBufferFromHexString(packet));
assert.ok(gotData);
},
'can parse a 256 byte long masked binary message': function() {
var p = new Parser();
var length = 256;
var message = new Buffer(length);
for (var i = 0; i < length; ++i) message[i] = i % 256;
var originalMessage = getHexStringFromBuffer(message);
var packet = '82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
var gotData = false;
p.on('binary', function(data) {
gotData = true;
assert.equal(originalMessage, getHexStringFromBuffer(data));
});
p.add(getBufferFromHexString(packet));
assert.ok(gotData);
},
'can parse a 200kb long masked binary message': function() {
var p = new Parser();
var length = 200 * 1024;
var message = new Buffer(length);
for (var i = 0; i < length; ++i) message[i] = i % 256;
var originalMessage = getHexStringFromBuffer(message);
var packet = '82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
var gotData = false;
p.on('binary', function(data) {
gotData = true;
assert.equal(originalMessage, getHexStringFromBuffer(data));
});
p.add(getBufferFromHexString(packet));
assert.ok(gotData);
},
'can parse a 200kb long unmasked binary message': function() {
var p = new Parser();
var length = 200 * 1024;
var message = new Buffer(length);
for (var i = 0; i < length; ++i) message[i] = i % 256;
var originalMessage = getHexStringFromBuffer(message);
var packet = '82 ' + getHybiLengthAsHexString(length, false) + ' ' + getHexStringFromBuffer(message);
var gotData = false;
p.on('binary', function(data) {
gotData = true;
assert.equal(originalMessage, getHexStringFromBuffer(data));
});
p.add(getBufferFromHexString(packet));
assert.ok(gotData);
},
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff