commit 38cb105b627bd75ecd53175e6a24446aa6e49975 Author: Guillermo Rauch Date: Sun Dec 9 20:25:37 2012 -0300 *: initial component diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..bb66ab2e --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules +build +components diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..a31c14cf --- /dev/null +++ b/Makefile @@ -0,0 +1,9 @@ + +REPORTER = dot + +test: + @./node_modules/.bin/mocha \ + --reporter $(REPORTER) \ + --bail + +.PHONY: test diff --git a/Readme.md b/Readme.md new file mode 100644 index 00000000..22a38fd3 --- /dev/null +++ b/Readme.md @@ -0,0 +1,98 @@ + +# socket.io-protocol + + This repository contains the protocol specification and JavaScript + parser for the Socket.IO protocol. + +## Protocol version + + **Current protocol revision:** `1`. + +## Parser API + +### Parser#encode(Object:packet):String + + Encodes a `Packet` object as a string. + +### Parser#decode(String:packet):Packet + + Returns a `Packet` object for the given string. If a parsing error + occurs the returned packet is an error object. + +### Parser#types + + Array of packet type keys. + +### Packet + + Each packet is represented as a vanilla `Object` with a `type` key that + can be one of the following: + + - `Packet#CONNECT` (`0`) + - `Packet#DISCONNECT` (`1`) + - `Packet#EVENT` (`2`) + - `Packet#ACK` (`3`) + - `Packet#ERROR` (`4`) + + Each packet also contains a `nsp` key that indicates what namespace it + belongs to (see "Multiplexing" below for an explanation). + +#### EVENT + + - `data` (`Array`) a list of arguments, the first of which is the event + name. Arguments can contain any type of field that can result of + `JSON` decoding, including objects and arrays of arbitrary size. + + - `id` (`Number`) if the `id` identifier is present, it indicates that the + server wishes to be acknowledged of the reception of this event. + +#### ACK + + - `data` (`Array`) see `EVENT` `data`. + - `id` (`Number`) see `EVENT` `id`. + +#### ERROR + + - `data` (`Mixed`) error data + +## Transport + + The socket.io protocol can be delivered over a variety of transports. + [socket.io-client](http://github.com/learnboost/socket.io-client) + is the implementation of the protocol for the browser and Node.JS over + [engine.io-client](http://github.com/learnboost/engine.io-client). + + [socket.io](http://github.com/learnboost/socket.io) is the server + implementation of the protocol over + [engine.io](http://github.com/learnboost/engine.io). + +## Multiplexing + + Socket.IO has built-in multiplexing support, which means that each packet + always belongs to a given `namespace`, identified by a path string (like + `/this`). The corresponding key in the `Packet` object is `nsp`. + + When the socket.io transport connection is established, a connection + attempt to the `/` namespace is assumed (ie: the server behaves as if + the client had sent a `CONNECT` packet to the `/` namespace). + + In order to support multiplexing of multiple sockets under + the same transport, additional `CONNECT` packets can be sent by the + client to arbitrary namespace URIs (eg: `/another`). + + When the server responds with a `CONNECT` packet to the corresponding + namespace, the multiplexed socket is considered connected. + + Alternatively, the server can respond with an `ERROR` packet to indicate + a multiplexed socket connection error, such as authentication errors. + The associated error payload varies according to each error, and can + be user-defined. + + After a `CONNECT` packet is received by the server for a given `nsp`, + the client can then send and receive `EVENT` packets. If any of the + parties receives an `EVENT` packet with an `id` field, an `ACK` packet is + expected to confirm the reception of said packet. + +## License + +MIT diff --git a/component.json b/component.json new file mode 100644 index 00000000..91bfbeec --- /dev/null +++ b/component.json @@ -0,0 +1,11 @@ +{ + "name": "socket.io-parser", + "version": "0.0.1", + "description": "socket.io protocol parser", + "dependencies": { + "component/json": "0.0.1" + }, + "scripts": [ + "index.js" + ] +} diff --git a/index.js b/index.js new file mode 100644 index 00000000..167e8fbd --- /dev/null +++ b/index.js @@ -0,0 +1,177 @@ + +/** + * Module dependencies. + */ + +var json; + +try { + json = require('json'); +} catch(e){ + json = JSON; +} + +/** + * Protocol version. + * + * @api public + */ + +exports.protocol = 1; + +/** + * Packet types. + * + * @api public + */ + +exports.types = [ + 'CONNECT', + 'DISCONNECT', + 'EVENT', + 'ACK', + 'ERROR' +]; + +/** + * Packet type `connect`. + * + * @api public + */ + +exports.CONNECT = 0; + +/** + * Packet type `disconnect`. + * + * @api public + */ + +exports.DISCONNECT = 1; + +/** + * Packet type `event`. + * + * @api public + */ + +exports.EVENT = 2; + +/** + * Packet type `ack`. + * + * @api public + */ + +exports.ACK = 3; + +/** + * Packet type `error`. + * + * @api public + */ + +exports.ERROR = 4; + +/** + * Encode. + * + * @param {Object} packet + * @return {String} encoded + * @api public + */ + +exports.encode = function(obj){ + var str = ''; + var nsp = false; + + // first is type + str += obj.type; + + // if we have a namespace other than `/` + // we append it followed by a comma `,` + if (obj.nsp && '/' != obj.nsp) { + nsp = true; + str += obj.nsp; + } + + // immediately followed by the id + if (obj.id) { + if (nsp) { + str += ','; + nsp = false; + } + str += obj.id; + } + + // json data + if (obj.data) { + if (nsp) str += ','; + str += json.stringify(obj.data); + } + + return str; +}; + +/** + * Decode. + * + * @param {String} str + * @return {Object} packet + * @api public + */ + +exports.decode = function(str){ + var p = {}; + var i = 0; + + // look up type + p.type = Number(str.charAt(0)); + if (null == exports.types[p.type]) return error(); + + // look up namespace (if any) + if ('/' == str.charAt(i + 1)) { + p.nsp = ''; + while (++i) { + var c = str.charAt(i); + if (',' == c) break; + p.nsp += c; + if (i + 1 == str.length) break; + } + } else { + p.nsp = '/'; + } + + // look up id + var next = str.charAt(i + 1); + if ('' != next && Number(next) == next) { + p.id = ''; + while (++i) { + var c = str.charAt(i); + if (null == c || Number(c) != c) { + --i; + break; + } + p.id += str.charAt(i); + if (i + 1 == str.length) break; + } + } + + // look up json data + if (str.charAt(++i)) { + try { + p.data = json.parse(str.substr(i)); + } catch(e){ + return error(); + } + } + + return p; +}; + +function error(data){ + return { + type: exports.ERROR, + data: 'parser error' + }; +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..85cd1a03 --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "socket.io-parser", + "version": "0.0.1", + "description": "socket.io protocol parser", + "devDependencies": { + "mocha": "*", + "expect.js": "*" + }, + "component": { + "dependencies": { + "component/json": "0.0.1" + }, + "scripts": [ + "index.js" + ] + } +} diff --git a/test/test.js b/test/test.js new file mode 100644 index 00000000..935d90e8 --- /dev/null +++ b/test/test.js @@ -0,0 +1,60 @@ + +var parser = require('..'); +var expect = require('expect.js'); +var encode = parser.encode; +var decode = parser.decode; + +// tests encoding and decoding a packet + +function test(obj){ + expect(decode(encode(obj))).to.eql(obj); +} + +describe('parser', function(){ + + it('exposes types', function(){ + expect(parser.CONNECT).to.be.a('number'); + expect(parser.DISCONNECT).to.be.a('number'); + expect(parser.EVENT).to.be.a('number'); + expect(parser.ACK).to.be.a('number'); + expect(parser.ERROR).to.be.a('number'); + }); + + it('encodes connection', function(){ + test({ + type: parser.CONNECT, + nsp: '/woot' + }); + }); + + it('encodes disconnection', function(){ + test({ + type: parser.DISCONNECT, + nsp: '/woot' + }); + }); + + it('encodes an event', function(){ + test({ + type: parser.EVENT, + data: ['a', 1, {}], + nsp: '/' + }); + test({ + type: parser.EVENT, + data: ['a', 1, {}], + id: 1, + nsp: '/test' + }); + }); + + it('encodes an ack', function(){ + test({ + type: parser.ACK, + data: ['a', 1, {}], + id: 123, + nsp: '/' + }); + }); + +});