Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

got the basic console working, added modifications to the sandbox to …

…be able to have a loging function, included socket.io to run the interface
  • Loading branch information...
commit 92d91f1afeab572547c5806db31137c305cd8972 1 parent 6a50fc4
@matthewfl authored
Showing with 4,783 additions and 3 deletions.
  1. +2 −0  config.sample.js
  2. +49 −0 lib/socket.io/History.md
  3. +19 −0 lib/socket.io/Makefile
  4. +343 −0 lib/socket.io/Readme.md
  5. +8 −0 lib/socket.io/index.js
  6. +96 −0 lib/socket.io/lib/logger.js
  7. +961 −0 lib/socket.io/lib/manager.js
  8. +348 −0 lib/socket.io/lib/namespace.js
  9. +243 −0 lib/socket.io/lib/parser.js
  10. +125 −0 lib/socket.io/lib/socket.io.js
  11. +366 −0 lib/socket.io/lib/socket.js
  12. +98 −0 lib/socket.io/lib/store.js
  13. +143 −0 lib/socket.io/lib/stores/memory.js
  14. +248 −0 lib/socket.io/lib/stores/redis.js
  15. +533 −0 lib/socket.io/lib/transport.js
  16. +102 −0 lib/socket.io/lib/transports/flashsocket.js
  17. +82 −0 lib/socket.io/lib/transports/htmlfile.js
  18. +135 −0 lib/socket.io/lib/transports/http-polling.js
  19. +111 −0 lib/socket.io/lib/transports/http.js
  20. +12 −0 lib/socket.io/lib/transports/index.js
  21. +77 −0 lib/socket.io/lib/transports/jsonp-polling.js
  22. +350 −0 lib/socket.io/lib/transports/websocket.js
  23. +72 −0 lib/socket.io/lib/transports/xhr-polling.js
  24. +25 −0 lib/socket.io/lib/util.js
  25. +22 −0 lib/socket.io/new-old/app.js
  26. +34 −0 lib/socket.io/new-old/index.jade
  27. +42 −0 lib/socket.io/new-old/layout.jade
  28. +9 −0 lib/socket.io/new-old/package.json
  29. +22 −0 lib/socket.io/new-old/public/js/main.js
  30. +28 −0 lib/socket.io/package.json
  31. +1 −1  production.js
  32. +1 −1  sandbox/index.js
  33. +20 −0 static/live_console.html
  34. +56 −1 test.js
View
2  config.sample.js
@@ -17,6 +17,8 @@ exports.testHost="node_test_host_id";
exports.testSKey="sampleKey"; // https://www.random.org/passwords/?num=1&len=24&format=plain&rnd=new
exports.testTimeToLive=15*60*1000; // in ms
//exports.testDoNotOverwrite=true; // should be true in production
+exports.testConsole=7654;
+
exports.errorPage="http://jsapp.us/error";
View
49 lib/socket.io/History.md
@@ -0,0 +1,49 @@
+
+0.7.6 / 2011-06-30
+==================
+
+ * Fixed general dispatching when a client has closed.
+
+0.7.5 / 2011-06-30
+==================
+
+ * Fixed dispatching to clients that are disconnected.
+
+0.7.4 / 2011-06-30
+==================
+
+ * Fixed; only clear handlers if they were set. [level09]
+
+0.7.3 / 2011-06-30
+==================
+
+ * Exposed handshake data to clients.
+ * Refactored dispatcher interface.
+ * Changed; Moved id generation method into the manager.
+ * Added sub-namespace authorization. [3rd-Eden]
+ * Changed; normalized SocketNamespace local eventing [dvv]
+ * Changed; Use packet.reason or default to 'packet' [3rd-Eden]
+ * Changed console.error to console.log.
+ * Fixed; bind both servers at the same time do that the test never times out.
+ * Added 304 support.
+ * Removed `Transport#name` for abstract interface.
+ * Changed; lazily require http and https module only when needed. [3rd-Eden]
+
+0.7.2 / 2011-06-22
+==================
+
+ * Make sure to write a packet (of type `noop`) when closing a poll.
+ This solves a problem with cross-domain requests being flagged as aborted and
+ reconnection being triggered.
+ * Added `noop` message type.
+
+0.7.1 / 2011-06-21
+==================
+
+ * Fixed cross-domain XHR.
+ * Added CORS test to xhr-polling suite.
+
+0.7.0 / 2010-06-21
+==================
+
+ * http://socket.io/announcement.html
View
19 lib/socket.io/Makefile
@@ -0,0 +1,19 @@
+
+ALL_TESTS = $(shell find test/ -name '*.test.js')
+
+run-tests:
+ @npm link > /dev/null --local
+ @./node_modules/.bin/expresso \
+ -I support \
+ -I lib \
+ --serial \
+ $(TESTFLAGS) \
+ $(TESTS)
+
+test:
+ @$(MAKE) TESTS="$(ALL_TESTS)" run-tests
+
+test-cov:
+ @TESTFLAGS=--cov $(MAKE) test
+
+.PHONY: test
View
343 lib/socket.io/Readme.md
@@ -0,0 +1,343 @@
+# 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.
+
+## How to Install
+
+ 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:
+
+```js
+var app = express.createServer();
+ , io = io.listen(app);
+
+app.listen(80);
+
+io.sockets.on('connection', function (socket) {
+ socket.emit('news', { hello: 'world' });
+ socket.on('my other event', function (data) {
+ console.log(data);
+ });
+});
+```
+
+Finally, load it from the client side code:
+
+```html
+<script src="/socket.io/socket.io.js"></script>
+<script>
+ var socket = io.connect('http://localhost');
+ socket.on('news', function (data) {
+ console.log(data);
+ socket.emit('my other event', { my: 'data' });
+ });
+</script>
+```
+
+For more thorough examples, look at the `examples/` directory.
+
+## Short recipes
+
+### Sending and receiving events.
+
+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);
+
+io.sockets.on('connection', function (socket) {
+ io.sockets.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 () {
+ sockets.emit('user disconnected');
+ });
+});
+```
+
+### Storing data associated to a client
+
+Sometimes it's necessary to store data associated with a client that's
+necessary for the duration of the session.
+
+#### Server side
+
+```js
+var io = require('socket.io').listen(80);
+
+io.sockets.on('connection', function (socket) {
+ socket.on('set nickname', function (name) {
+ socket.set('nickname', name, function () { socket.emit('ready'); });
+ });
+
+ socket.on('msg', function () {
+ socket.get('nickname', function (err, name) {
+ console.log('Chat message by ', name);
+ });
+ });
+});
+```
+
+#### Client side
+
+```html
+<script>
+ var socket = io.connect('http://localhost');
+
+ socket.on('connect', function () {
+ socket.emit('set nickname', confirm('What is your nickname?'));
+ socket.on('ready', function () {
+ console.log('Connected !');
+ socket.emit('msg', confirm('What is your message?'));
+ });
+ });
+</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
+or not.
+
+### Getting acknowledgements
+
+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`.
+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);
+
+io.sockets.on('connection', function (socket) {
+ socket.on('ferret', function (name, fn) {
+ fn('woot');
+ });
+});
+```
+
+#### Client side
+
+```html
+<script>
+ var socket = io.connect(); // TIP: .connect with no args does auto-discovery
+ socket.on('connection', function () {
+ socket.emit('ferret', 'tobi', function (data) {
+ console.log(data); // data will be 'woot'
+ });
+ });
+</script>
+```
+
+### Broadcasting messages
+
+To broadcast, simply add a `broadcast` flag to `emit` and `send` method calls.
+Broadcasting means sending a message to everyone else except for the socket
+that starts it.
+
+#### Server side
+
+```js
+var io = require('socket.io').listen(80);
+
+io.sockets.on('connection', function (socket) {
+ socket.broadcast.emit('user connected');
+ socket.broadcast.json.send({ a: 'message' });
+});
+```
+
+### Rooms
+
+Sometimes you want to put certain sockets in the same room, so that it's easy
+to broadcast to all of them together.
+
+Think of this as built-in channels for sockets. Sockets `join` and `leave`
+rooms in each socket.
+
+#### Server side
+
+```js
+var io = require('socket.io').listen(80);
+
+io.sockets.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');
+});
+```
+
+### Using it just as a cross-browser WebSocket
+
+If you just want the WebSocket semantics, you can do that too.
+Simply leverage `send` and listen on the `message` event:
+
+#### Server side
+
+```js
+var io = require('socket.io').listen(80);
+
+io.sockets.on('connection', function (socket) {
+ socket.on('message', function () { });
+ socket.on('disconnect', function () { });
+});
+```
+
+#### Client side
+
+```html
+<script>
+ var socket = io.connect('http://localhost/');
+ socket.on('connect', function () {
+ socket.send('hi');
+
+ socket.on('message', function (msg) {
+ // my msg
+ });
+ });
+</script>
+```
+
+### Changing configuration
+
+Configuration in socket.io is TJ-style:
+
+#### Server side
+
+```js
+var io = require('socket.io').listen(80);
+
+io.configure(function () {
+ io.set('transports', ['websocket', 'flashsocket', 'xhr-polling']);
+});
+
+io.configure('development', function () {
+ io.set('transports', ['websocket', 'xhr-polling']);
+ io.enable('log');
+});
+```
+
+## License
+
+(The MIT License)
+
+Copyright (c) 2011 Guillermo Rauch &lt;guillermo@learnboost.com&gt;
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
View
8 lib/socket.io/index.js
@@ -0,0 +1,8 @@
+
+/*!
+ * socket.io-node
+ * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
+ * MIT Licensed
+ */
+
+module.exports = require('./lib/socket.io');
View
96 lib/socket.io/lib/logger.js
@@ -0,0 +1,96 @@
+
+/*!
+ * 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;
+};
+
+/**
+ * Log method.
+ *
+ * @api public
+ */
+
+Logger.prototype.log = function (type) {
+ var index = levels.indexOf(type);
+
+ if (index > this.level)
+ 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
961 lib/socket.io/lib/manager.js
@@ -0,0 +1,961 @@
+
+/*!
+ * socket.io-node
+ * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
+ * MIT Licensed
+ */
+
+/**
+ * Module dependencies.
+ */
+
+var http = require('http')
+ , https = require('https')
+ , fs = require('fs')
+ , url = require('url')
+ , 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')
+ , 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) {
+ this.server = server;
+ this.namespaces = {};
+ this.sockets = this.of('');
+ this.settings = {
+ origins: '*:*'
+ , log: true
+ , store: new MemoryStore
+ , logger: new Logger
+ , heartbeats: true
+ , resource: '/socket.io'
+ , transports: defaultTransports
+ , authorization: false
+ , 'log level': 3
+ , 'close timeout': 25
+ , 'heartbeat timeout': 15
+ , 'heartbeat interval': 20
+ , 'polling duration': 20
+ , 'flash policy server': true
+ , 'flash policy port': 843
+ , 'destroy upgrade': true
+ , 'browser client': true
+ , 'browser client minification': false
+ , 'browser client etag': false
+ , 'browser client handler': false
+ , 'client store expiration': 15
+ };
+
+ this.initStore();
+
+ // reset listeners
+ this.oldListeners = server.listeners('request');
+ server.removeAllListeners('request');
+
+ var self = this;
+
+ server.on('request', function (req, res) {
+ self.handleRequest(req, res);
+ });
+
+ server.on('upgrade', function (req, socket, head) {
+ self.handleUpgrade(req, socket, head);
+ });
+
+ 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 () {
+ if (this.disabled('log')) return;
+
+ var logger = this.get('logger');
+ logger.level = this.get('log level');
+
+ return logger;
+});
+
+/**
+ * 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.closedA = [];
+ 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.closedA.splice(this.closedA.indexOf(id), 1);
+
+ 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) {
+ // go through the users who have pending buffers
+ for (var i = 0, l = this.closedA.length; i < l; i++) {
+ if (!this.roomClients[this.closedA[i]]) continue;
+
+ if (this.roomClients[this.closedA[i]][room]) {
+ if (!~exceptions.indexOf(this.closedA[i])) {
+ this.closed[this.closedA[i]].push(packet);
+ }
+ }
+ }
+
+ // go through room clients
+ 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] = [];
+ }
+
+ 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]) {
+ this.rooms[room].splice(this.rooms[room].indexOf(id), 1);
+ delete this.roomClients[id][room];
+ }
+};
+
+/**
+ * Called when a client closes a request in different node.
+ *
+ * @api private
+ */
+
+Manager.prototype.onClose = function (id) {
+ this.closed[id] = [];
+ this.closedA.push(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) {
+ if (this.roomClients[id][name]) {
+ this.namespaces[name].handleDisconnect(id, reason);
+ }
+ }
+};
+
+/**
+ * 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];
+ this.closedA.splice(this.closedA.indexOf(id), 1);
+ }
+
+ if (this.roomClients[id]) {
+ for (var room in this.roomClients[id]) {
+ this.rooms[room].splice(this.rooms.indexOf(id), 1);
+ }
+ }
+
+ 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.handleClientRequest(req, res, data);
+ } 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);
+
+ if (this.handshaken[data.id]) {
+ 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);
+
+ // 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();
+ }
+};
+
+/**
+ * Dictionary for static file serving
+ *
+ * @api public
+ */
+
+Manager.static = {
+ cache: {}
+ , paths: {
+ '/static/flashsocket/WebSocketMain.swf': client.dist + '/WebSocketMain.swf'
+ , '/static/flashsocket/WebSocketMainInsecure.swf':
+ client.dist + '/WebSocketMainInsecure.swf'
+ , '/socket.io.js': client.dist + '/socket.io.js'
+ , '/socket.io.js.min': client.dist + '/socket.io.min.js'
+ }
+ , mime: {
+ 'js': {
+ contentType: 'application/javascript'
+ , encoding: 'utf8'
+ }
+ , 'swf': {
+ contentType: 'application/x-shockwave-flash'
+ , encoding: 'binary'
+ }
+ }
+};
+
+/**
+ * Serves the client.
+ *
+ * @api private
+ */
+
+Manager.prototype.handleClientRequest = function (req, res, data) {
+ var static = Manager.static
+ , extension = data.path.split('.').pop()
+ , file = data.path + (this.enabled('browser client minification')
+ && extension == 'js' ? '.min' : '')
+ , location = static.paths[file]
+ , cache = static.cache[file];
+
+ var self = this;
+
+ /**
+ * Writes a response, safely
+ *
+ * @api private
+ */
+
+ function write (status, headers, content, encoding) {
+ try {
+ res.writeHead(status, headers || null);
+ res.end(content || '', encoding || null);
+ } catch (e) {}
+ }
+
+ function serve () {
+ if (req.headers['if-none-match'] === cache.Etag) {
+ return write(304);
+ }
+
+ var mime = static.mime[extension]
+ , headers = {
+ 'Content-Type': mime.contentType
+ , 'Content-Length': cache.length
+ };
+
+ if (self.enabled('browser client etag') && cache.Etag) {
+ headers.Etag = cache.Etag;
+ }
+
+ write(200, headers, cache.content, mime.encoding);
+ self.log.debug('served static ' + data.path);
+ }
+
+ if (this.get('browser client handler')) {
+ this.get('browser client handler').call(this, req, res);
+ } else if (!cache) {
+ fs.readFile(location, function (err, data) {
+ if (err) {
+ write(500, null, 'Error serving static ' + data.path);
+ self.log.warn('Can\'t cache '+ data.path +', ' + err.message);
+ return;
+ }
+
+ cache = Manager.static.cache[file] = {
+ content: data
+ , length: data.length
+ , Etag: client.version
+ };
+
+ serve();
+ });
+ } else {
+ serve();
+ }
+};
+
+/**
+ * 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;
+
+ 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);
+ 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);
+
+ this.authorize(handshakeData, function (err, authorized, newData) {
+ if (err) return error(err);
+
+ if (authorized) {
+ var id = self.generateId()
+ , hs = [
+ id
+ , 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);
+ }
+
+ 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 connectionAddress = null;
+ if (data.request.connection.address) {
+ connectionAddress = data.request.connection.address();
+ }
+ return {
+ headers: data.headers
+ , address: connectionAddress
+ , time: (new Date).toString()
+ , xdomain: !!data.request.headers.origin
+ , secure: data.request.connection.secure
+ };
+};
+
+/**
+ * Verifies the origin of a request.
+ *
+ * @api private
+ */
+
+Manager.prototype.verifyOrigin = function (request) {
+ var origin = request.headers.origin
+ , origins = this.get('origins');
+
+ if (origin === 'null') origin = '*';
+
+ if (origins.indexOf('*:*') !== -1) {
+ return true;
+ }
+
+ if (origin) {
+ try {
+ var parts = url.parse(origin);
+
+ return
+ ~origins.indexOf(parts.host + ':' + parts.port) ||
+ ~origins.indexOf(parts.host + ':*') ||
+ ~origins.indexOf('*:' + parts.port);
+ } catch (ex) {}
+ }
+
+ 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 = !!Manager.static.paths[path];
+ };
+
+ return data;
+ }
+
+ return false;
+};
+
+/**
+ * Declares a socket namespace
+ */
+
+Manager.prototype.of = function (nsp) {
+ if (this.namespaces[nsp]) {
+ return this.namespaces[nsp];
+ }
+
+ return this.namespaces[nsp] = new SocketNamespace(this, nsp);
+};
View
348 lib/socket.io/lib/namespace.js
@@ -0,0 +1,348 @@
+/**
+ * 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 ?
+ (this.name !== '' ? '/' : '') + 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 = function (room) {
+ this.flags.endpoint = (this.name === '' ? '' : (this.name + '/')) + 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 private
+ */
+
+SocketNamespace.prototype.emit = function (name) {
+ if (name == 'connection' || 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 private
+ */
+
+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) {
+ if (this.sockets[sid] && this.sockets[sid].readable) {
+ this.sockets[sid].onDisconnect(reason);
+ }
+};
+
+/**
+ * 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'
+ , 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 manager = this.manager
+ , 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':
+ 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
243 lib/socket.io/lib/parser.js
@@ -0,0 +1,243 @@
+
+/*!
+ * socket.io-node
+ * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
+ * MIT Licensed
+ */
+
+/**
+ * Module dependencies.
+ */
+
+/**
+ * Packet types.
+ */
+
+var packets = exports.packets = [
+ 'disconnect'
+ , 'connect'
+ , 'heartbeat'
+ , 'message'
+ , 'json'
+ , 'event'
+ , 'ack'
+ , 'error'
+ , 'noop'
+];
+
+/**
+ * Errors reasons.
+ */
+
+var reasons = exports.reasons = [
+ 'transport not supported'
+ , 'client not handshaken'
+ , 'unauthorized'
+];
+
+/**
+ * Errors advice.
+ */
+
+var advice = exports.advice = [
+ 'reconnect'
+];
+
+/**
+ * Encodes a packet.
+ *
+ * @api private
+ */
+
+exports.encodePacket = function (packet) {
+ var type = packets.indexOf(packet.type)
+ , id = packet.id || ''
+ , endpoint = packet.endpoint || ''
+ , ack = packet.ack
+ , data = null;
+
+ switch (packet.type) {
+ case 'error':
+ var reason = packet.reason ? reasons.indexOf(packet.reason) : ''
+ , adv = packet.advice ? advice.indexOf(packet.advice) : ''
+
+ if (reason !== '' || adv !== '')
+ data = reason + (adv !== '' ? ('+' + adv) : '')
+
+ break;
+
+ 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 'connect':
+ if (packet.qs)
+ data = packet.qs;
+ break;
+
+ case 'ack':
+ data = packet.ackId
+ + (packet.args && packet.args.length
+ ? '+' + JSON.stringify(packet.args) : '');
+ break;
+ }
+
+ // construct packet with required fragments
+ var encoded = [
+ type
+ , id + (ack == 'data' ? '+' : '')
+ , endpoint
+ ];
+
+ // data fragment is optional
+ if (data !== null && data !== undefined)
+ encoded.push(data);
+
+ return encoded.join(':');
+};
+
+/**
+ * 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]+)?(\+)?:([^:]+)?:?(.*)?$/;
+
+exports.decodePacket = function (data) {
+ var pieces = data.match(regexp);
+
+ if (!pieces) return {};
+
+ var id = pieces[2] || ''
+ , data = pieces[5] || ''
+ , packet = {
+ type: packets[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 'error':
+ var pieces = data.split('+');
+ packet.reason = reasons[pieces[0]] || '';
+ packet.advice = advice[pieces[1]] || '';
+ break;
+
+ case 'message':
+ packet.data = data || '';
+ break;
+
+ case 'event':
+ try {
+ var opts = JSON.parse(data);
+ packet.name = opts.name;
+ packet.args = opts.args;
+ } catch (e) { }
+
+ packet.args = packet.args || [];
+ break;
+
+ case 'json':
+ try {
+ packet.data = JSON.parse(data);
+ } catch (e) { }
+ break;
+
+ case 'connect':
+ packet.qs = data || '';
+ break;
+
+ case 'ack':
+ var pieces = data.match(/^([0-9]+)(\+)?(.*)/);
+ if (pieces) {
+ packet.ackId = pieces[1];
+ packet.args = [];
+
+ if (pieces[3]) {
+ try {
+ packet.args = pieces[3] ? JSON.parse(pieces[3]) : [];
+ } catch (e) { }
+ }
+ }
+ break;
+
+ case 'disconnect':
+ case 'heartbeat':
+ break;
+ };
+
+ return packet;
+};
+
+/**
+ * Decodes data payload. Detects multiple messages
+ *
+ * @return {Array} messages
+ * @api public
+ */
+
+exports.decodePayload = function (data) {
+ 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).substr(0, length)));
+ i += Number(length) + 1;
+ length = '';
+ } else {
+ length += data[i];
+ }
+ }
+
+ return ret;
+ } else {
+ return [exports.decodePacket(data)];
+ }
+};
View
125 lib/socket.io/lib/socket.io.js
@@ -0,0 +1,125 @@
+
+/*!
+ * socket.io-node
+ * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
+ * MIT Licensed
+ */
+
+/**
+ * Module dependencies.
+ */
+
+var client = require('socket.io-client');
+
+/**
+ * Version.
+ */
+
+exports.version = '0.7.6';
+
+/**
+ * Supported protocol version.
+ */
+
+exports.protocol = 1;
+
+/**
+ * Client that we serve.
+ */
+
+exports.clientVersion = client.version;
+
+/**
+ * Attaches a manager
+ *
+ * @api public
+ */
+
+exports.listen = function (server, options, fn) {
+ if ('function' == typeof options) {
+ fn = options;
+ options = {};
+ }
+
+ 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);
+};
+
+/**
+ * Manager constructor.
+ *
+ * @api public
+ */
+
+exports.Manager = require('./manager');
+
+/**
+ * Transport constructor.
+ *
+ * @api public
+ */
+
+exports.Transport = require('./transport');
+
+/**
+ * Socket constructor.
+ *
+ * @api public
+ */
+
+exports.Socket = require('./socket');
+
+/**
+ * Store constructor.
+ *
+ * @api public
+ */
+
+exports.Store = require('./store');
+
+/**
+ * Memory Store constructor.
+ *
+ * @api public
+ */
+
+exports.MemoryStore = require('./stores/memory');
+
+/**
+ * Redis Store constructor.
+ *
+ * @api public
+ */
+
+exports.RedisStore = require('./stores/redis');
+
+/**
+ * Parser.
+ *
+ * @api public
+ */
+
+exports.parser = require('./parser');
View
366 lib/socket.io/lib/socket.js
@@ -0,0 +1,366 @@
+
+/*!
+ * socket.io-node
+ * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
+ * MIT Licensed
+ */
+
+/**
+ * Module dependencies.
+ */
+
+var parser = require('./parser')
+ , util = require('./util')
+ , EventEmitter = process.EventEmitter;
+
+/**
+ * Export the constructor.
+ */
+
+exports = module.exports = Socket;
+
+/**
+ * Reserved event names.
+ */
+
+var events = {
+ message: 1
+ , connect: 1
+ , disconnect: 1
+ , open: 1
+ , close: 1
+ , error: 1
+ , retry: 1
+ , reconnect: 1
+ , newListener: 1
+};
+
+/**
+ * Socket constructor.
+ *
+ * @param {Manager} manager instance
+ * @param {String} session id
+ * @param {Namespace} namespace the socket belongs to
+ * @param {Boolean} whether the
+ * @api public
+ */
+
+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);
+};
+
+/**
+ * Inherits from EventEmitter.
+ */
+
+Socket.prototype.__proto__ = EventEmitter.prototype;
+
+/**
+ * Accessor shortcut for the handshake data
+ *
+ * @api private
+ */
+
+Socket.prototype.__defineGetter__('handshake', function () {
+ return this.manager.handshaken[this.id];
+});
+
+/**
+ * Accessor shortcut for the logger.
+ *
+ * @api private
+ */
+
+Socket.prototype.__defineGetter__('log', function () {
+ return this.manager.log;
+});
+
+/**
+ * JSON message flag.
+ *
+ * @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 = 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 === '' ? '' : (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;
+};
+
+/**
+ * Joins a user to a room.
+ *
+ * @api public
+ */
+
+Socket.prototype.leave = function (name, fn) {
+ var nsp = this.namespace.name
+ , name = (nsp === '' ? '' : (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);
+ } else {
+ packet.endpoint = this.flags.endpoint;
+ packet = parser.encodePacket(packet);
+
+ this.dispatch(packet, this.flags.volatile);
+ }
+
+ this.setFlags();
+
+ return this;
+};
+
+/**
+ * Dispatches a packet
+ *
+ * @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);
+ }
+};
+
+/**