├── .gitignore ├── index.js ├── lib ├── middleware │ ├── uid.js │ ├── sid.js │ ├── date.js │ ├── json.js │ ├── track.js │ ├── dict.js │ ├── broadcast.js │ ├── events.js │ ├── log.js │ └── rpc.js ├── client.js ├── server.js ├── simpl.js ├── utils.js ├── websocket-client.js ├── browser │ └── simpl.js ├── websocket-server.js └── events.js ├── bench ├── rpc-dnode-bench-server.js ├── rpc-simpl-bench-server.js ├── bench.js ├── rpc-dnode-bench-client.js ├── client-bench.js └── rpc-simpl-bench-client.js ├── examples ├── minimal.js ├── json.js ├── browser-rpc.js ├── public │ ├── rpc.html │ └── index.html └── chat.js ├── package.json ├── LICENCE ├── test ├── test-events.js ├── test.js ├── test-rpc.js ├── test-json.js ├── test-date.js └── test-dict.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/simpl') 2 | -------------------------------------------------------------------------------- /lib/middleware/uid.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | return function () { 3 | this.after('out', function (args, next) { 4 | args[1].id = Math.floor(Math.random() * Date.now()).toString(36) 5 | next() 6 | }) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /lib/client.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils') 2 | 3 | module.exports = function () { 4 | var opts = utils.parseArgs(arguments) 5 | if (opts.port) { 6 | var WebSocketClient = require('./websocket-client') 7 | return new WebSocketClient(opts) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/middleware/sid.js: -------------------------------------------------------------------------------- 1 | // adds a unique id to the socket 2 | 3 | module.exports = function () { 4 | return function () { 5 | this.on('connection', function (socket) { 6 | socket.id = Math.floor(Math.random() * Date.now()).toString(36) 7 | }) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /bench/rpc-dnode-bench-server.js: -------------------------------------------------------------------------------- 1 | //var app = require('express').createServer() 2 | var dnode = require('dnode') 3 | 4 | var port = 5888 5 | var host = '127.0.0.1' 6 | 7 | var server = dnode({ 8 | ping: function (callback) { 9 | callback('pong') 10 | } 11 | }) 12 | 13 | server.listen(port, host) 14 | 15 | server.on('ready', function () { 16 | console.log('listening') 17 | }) 18 | -------------------------------------------------------------------------------- /examples/minimal.js: -------------------------------------------------------------------------------- 1 | // minimal example 2 | 3 | var simpl = require('../') 4 | 5 | var server = simpl.createServer(8080) 6 | 7 | server.use(simpl.log('server')) 8 | 9 | server.on('ready', function () { 10 | var client = simpl.createClient(8080) 11 | client.use(simpl.log('client')) 12 | client.on('connect', function () { 13 | client.send('hello, world') 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /lib/middleware/date.js: -------------------------------------------------------------------------------- 1 | // date middleware 2 | 3 | module.exports = function (key) { 4 | key = key || 'date' 5 | return function () { 6 | this.before('in', function (args, next) { 7 | args[1][key] = new Date(args[1][key]) 8 | next() 9 | }) 10 | this.after('out', function (args, next) { 11 | args[1][key] = new Date() 12 | next() 13 | }) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/middleware/json.js: -------------------------------------------------------------------------------- 1 | // json middleware 2 | 3 | module.exports = function () { 4 | return function () { 5 | this.before('in', function (args, next) { 6 | try { args[1] = JSON.parse(args[1]) } 7 | catch (_) {} 8 | next() 9 | }) 10 | this.after('out', function (args, next) { 11 | try { args[1] = JSON.stringify(args[1]) } 12 | catch (_) {} 13 | next() 14 | }) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/server.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils') 2 | 3 | module.exports = function () { 4 | var opts = utils.parseArgs(arguments) 5 | if (opts.port || opts.server) { 6 | if (opts.port) { 7 | opts.server = opts.httpServer = require('express').createServer() 8 | opts.server.listen(opts.port, opts.host) 9 | } 10 | var WebSocketServer = require('./websocket-server') 11 | return new WebSocketServer(opts) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /bench/rpc-simpl-bench-server.js: -------------------------------------------------------------------------------- 1 | var app = require('express').createServer() 2 | 3 | var port = 5888 4 | var host = '127.0.0.1' 5 | 6 | var simpl = require('../') 7 | var server = simpl.createServer(app); 8 | 9 | server.use(simpl.uid()) 10 | server.use(simpl.rpc({ 11 | ping: function (callback) { 12 | callback('pong') 13 | } 14 | })) 15 | server.use(simpl.json()) 16 | 17 | app.on('listening', function () { 18 | console.log('listening') 19 | }) 20 | 21 | app.listen(port, host) 22 | -------------------------------------------------------------------------------- /examples/json.js: -------------------------------------------------------------------------------- 1 | // json example 2 | 3 | var simpl = require('../') 4 | 5 | var server = simpl.createServer(8080) 6 | 7 | server.use(simpl.json()) 8 | server.use(simpl.log('server', true)) // the `true` flag will log both ends 9 | 10 | server.on('ready', function () { 11 | var client = simpl.createClient(8080) 12 | client.use(simpl.json()) 13 | client.use(simpl.log('client', true)) 14 | client.on('connect', function () { 15 | client.send({ hello: 'world' }) 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /examples/browser-rpc.js: -------------------------------------------------------------------------------- 1 | var express = require('express') 2 | var app = express.createServer() 3 | app.use(express.static(__dirname + '/public')) 4 | 5 | var simpl = require('../'); 6 | var server = simpl.createServer(app); 7 | 8 | server.use(simpl.uid()); 9 | server.use(simpl.rpc({ 10 | 'multiply': function (a, b, callback) { 11 | callback(a * b); 12 | } 13 | })); 14 | server.use(simpl.json()); 15 | server.use(simpl.log()); 16 | 17 | app.listen(8080); 18 | app.on('listening', function () { 19 | console.log('Go to: http://localhost:8080/rpc.html') 20 | }) 21 | -------------------------------------------------------------------------------- /examples/public/rpc.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 16 |
17 | -------------------------------------------------------------------------------- /lib/middleware/track.js: -------------------------------------------------------------------------------- 1 | // track connections middleware 2 | 3 | module.exports = function () { 4 | return function () { 5 | if (this.server) { 6 | var clients = this.clients = [] 7 | this.on('connection', function (socket) { 8 | clients.push(socket) 9 | clients[socket.id] = socket 10 | }) 11 | this.on('disconnect', function (socket) { 12 | var index = clients.indexOf(socket) 13 | if (index > -1) { 14 | clients.splice(index, 1) 15 | delete clients[socket.id] 16 | } 17 | }) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/middleware/dict.js: -------------------------------------------------------------------------------- 1 | module.exports = function (dict) { 2 | if (!Array.isArray(dict)) throw new Error('Invalid dictionary') 3 | var reversed = dict.slice().reverse() 4 | return function () { 5 | this.before('in', function (args, next) { 6 | var s = args[1] 7 | reversed.forEach(function (rule) { 8 | s = s.split(rule[1]).join(rule[0]) 9 | }) 10 | args[1] = s 11 | next() 12 | }) 13 | this.after('out', function (args, next) { 14 | var s = args[1] 15 | dict.forEach(function (rule) { 16 | s = s.split(rule[0]).join(rule[1]) 17 | }) 18 | args[1] = s 19 | next() 20 | }) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /bench/bench.js: -------------------------------------------------------------------------------- 1 | var app = require('express').createServer() 2 | 3 | var port = 5888 4 | var host = '127.0.0.1' 5 | 6 | var simpl = require('../') 7 | var server = simpl.createServer(app); 8 | 9 | server.use(simpl.sid()) 10 | server.use(simpl.track()) 11 | server.use(simpl.broadcast()) 12 | 13 | server.on('connection', function (socket) { 14 | socket.on('message', function (data) { 15 | if (data === 'broadcast') { 16 | server.broadcast(data) 17 | } else { 18 | socket.send(data) 19 | } 20 | }) 21 | }).on('disconnect', function (err) { 22 | console.log('disconnect') 23 | }) 24 | 25 | app.on('listening', function () { 26 | console.log('listening') 27 | }) 28 | 29 | app.listen(port, host) 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simpl", 3 | "description": "Highly pluggable WebSockets framework", 4 | "version": "0.4.0", 5 | "homepage": "https://github.com/stagas/simpl", 6 | "author": "George Stagas (http://stagas.com/)", 7 | "repository": { 8 | "type": "git", 9 | "url": "git://github.com/stagas/simpl.git" 10 | }, 11 | "main": "index", 12 | "dependencies": { 13 | "ws": "~0.4.7", 14 | "eventstack": "~0.2.0", 15 | "express": "~2.5.6", 16 | "express-expose": "~0.2.1" 17 | }, 18 | "devDependencies": { 19 | "tap": "~0.0.14", 20 | "dnode": "~0.9.6" 21 | }, 22 | "scripts": { 23 | "test": "tap test" 24 | }, 25 | "engines": { 26 | "node": "~0.6.8" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/chat.js: -------------------------------------------------------------------------------- 1 | var simpl = require('../') 2 | var path = require('path') 3 | var express = require('express') 4 | var app = express.createServer() 5 | 6 | app.use(express.logger()) 7 | app.use(express.static(path.join(__dirname, 'public'))) 8 | 9 | var server = simpl.createServer(app) 10 | server.use(simpl.sid()) 11 | server.use(simpl.track()) 12 | server.use(simpl.broadcast()) 13 | server.use(simpl.log()) 14 | 15 | server.on('message', function (message, socket) { 16 | socket.broadcast('<' + socket.id + '> ' + message) 17 | }) 18 | 19 | server.on('connection', function (socket) { 20 | socket.broadcast('* join ' + socket.id) 21 | socket.on('close', function () { 22 | socket.broadcast('* leave ' + socket.id) 23 | }) 24 | }) 25 | 26 | app.listen(8080) 27 | -------------------------------------------------------------------------------- /lib/middleware/broadcast.js: -------------------------------------------------------------------------------- 1 | // adds .broadcast to server and server sockets 2 | 3 | module.exports = function (context) { 4 | return function () { 5 | if (this.server) { 6 | var server = this 7 | 8 | this.on('connection', function (socket) { 9 | if (!socket.broadcast) { 10 | socket.constructor.prototype.broadcast = function (message) { 11 | var self = this 12 | server.clients.forEach(function (client) { 13 | if (client !== self) { 14 | client.send(message) 15 | } 16 | }) 17 | } 18 | } 19 | }) 20 | 21 | server.constructor.prototype.broadcast = function (message) { 22 | this.clients.forEach(function (client) { 23 | client.send(message) 24 | }) 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/simpl.js: -------------------------------------------------------------------------------- 1 | var EventStack = require('eventstack') 2 | 3 | var slice = [].slice 4 | 5 | var constrs = [ 'Server', 'Client' ] 6 | 7 | var middlewares = [ 'json', 'date', 'log', 'uid', 'rpc', 'events', 'sid' 8 | , 'track', 'broadcast', 'dict' ] 9 | 10 | constrs.forEach(function (constr) { 11 | exports['create' + constr] = function (params) { 12 | var instance = require('./' + constr.toLowerCase())(params) 13 | instance._middleware = Object.create(null) 14 | middlewares.forEach(function (middleware) { 15 | instance._middleware[middleware] = require('./middleware/' + middleware) 16 | }) 17 | return instance 18 | } 19 | }) 20 | 21 | middlewares.forEach(function (middleware) { 22 | exports[middleware] = require('./middleware/' + middleware) 23 | }) 24 | 25 | exports.version = require('../package.json').version 26 | -------------------------------------------------------------------------------- /bench/rpc-dnode-bench-client.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') 2 | var dnode = require('dnode') 3 | 4 | var port = 5888 5 | var host = '127.0.0.1' 6 | 7 | var n_clients = +(process.argv[2] || 100) 8 | var n_calls = +(process.argv[3] || 100) 9 | var total = n_clients * n_calls 10 | 11 | console.log('clients:', n_clients, 'calls:', n_calls) 12 | 13 | function measure () { 14 | var cnt = total 15 | var then = Date.now() 16 | function done () { 17 | var diff = Date.now() - then 18 | console.log('' + (total / (diff / 1000)), 'roundtrips per second') 19 | } 20 | function pong (pong) { 21 | if (pong !== 'pong') throw new Error('Not correct answer') 22 | else { 23 | --cnt || done() 24 | } 25 | } 26 | for (var r = remotes.length; r--;) { 27 | var count = n_calls 28 | for (var n = count; n--;) { 29 | remotes[r].ping(pong) 30 | } 31 | } 32 | } 33 | 34 | 35 | var remotes = [] 36 | var cnt = n_clients 37 | for (var i = n_clients; i--;) { 38 | dnode.connect(port, host, function (remote) { 39 | remotes.push(remote) 40 | --cnt || measure() 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 George Stagakis 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /bench/client-bench.js: -------------------------------------------------------------------------------- 1 | var simpl = require('../') 2 | var clients = {} 3 | 4 | var cnt = +(process.argv[2] || 10) 5 | var total = cnt 6 | var massTotal = 0 7 | var massMul = +(process.argv[3] || 10) 8 | var port = 5888 9 | var time 10 | var what = 'connections' 11 | 12 | var massOk = false 13 | 14 | function mass () { 15 | massOk = true 16 | what = 'roundtrips' 17 | massTotal = cnt = Math.pow(total, 2) * massMul 18 | console.log(massTotal) 19 | time = Date.now() 20 | for (var i = total; i--;) { 21 | clients[i].on('message', function () { 22 | --cnt || done() 23 | }) 24 | for (var x = massMul; x--;) { 25 | clients[i].send('broadcast') 26 | } 27 | } 28 | } 29 | 30 | function done () { 31 | var diff = Date.now() - time 32 | console.log(diff) 33 | console.log('' + ((massTotal || total) / (diff / 1000)), what, 'per second') 34 | if (!massOk) mass() 35 | } 36 | 37 | time = Date.now() 38 | for (var i = cnt; i--;) { 39 | ;(function (client) { 40 | client.once('connect', function (socket) { 41 | client.once('message', function (message) { 42 | --cnt || done() 43 | }) 44 | client.send('hello') 45 | }); 46 | }(clients[i] = simpl.createClient(port))); 47 | } 48 | -------------------------------------------------------------------------------- /lib/middleware/events.js: -------------------------------------------------------------------------------- 1 | // events middleware 2 | 3 | var EventStack = require('eventstack') 4 | var slice = [].slice 5 | 6 | module.exports = function () { 7 | function mergeBind (dest, src, methods) { 8 | methods.forEach(function (method) { 9 | dest[method] = src[method].bind(src) 10 | }) 11 | } 12 | return function () { 13 | var self = this 14 | this.use(this._middleware.rpc({ 15 | emit: function () { 16 | this.emitter.emit.apply(this.emitter, slice.call(arguments)) 17 | } 18 | }, 'emit', true)) 19 | if (this.client) { 20 | var emitter = this.emitter = new EventStack 21 | this.use('connect', function (args, next) { 22 | var socket = args[1] 23 | socket.emitter = emitter 24 | mergeBind(socket.remote, emitter 25 | , ['on', 'once', 'removeListener', 'removeAllListeners']) 26 | next() 27 | }) 28 | } else { 29 | this.use('connection', function (args, next) { 30 | var socket = args[1] 31 | var emitter = socket.emitter = new EventStack 32 | mergeBind(socket.remote, emitter 33 | , ['on', 'once', 'removeListener', 'removeAllListeners']) 34 | next() 35 | }) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /bench/rpc-simpl-bench-client.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') 2 | var simpl = require('../') 3 | var port = 5888 4 | var host = '127.0.0.1' 5 | 6 | var n_clients = +(process.argv[2] || 100) 7 | var n_calls = +(process.argv[3] || 100) 8 | var total = n_clients * n_calls 9 | 10 | console.log('clients:', n_clients, 'calls:', n_calls) 11 | 12 | function measure () { 13 | var cnt = total 14 | var then = Date.now() 15 | function done () { 16 | var diff = Date.now() - then 17 | console.log('' + (total / (diff / 1000)), 'roundtrips per second') 18 | } 19 | function pong (pong) { 20 | if (pong !== 'pong') throw new Error('Not correct answer') 21 | else { 22 | --cnt || done() 23 | } 24 | } 25 | for (var r = remotes.length; r--;) { 26 | var count = n_calls 27 | for (var n = count; n--;) { 28 | remotes[r].ping(pong) 29 | } 30 | } 31 | } 32 | 33 | 34 | var remotes = [] 35 | var cnt = n_clients 36 | for (var i = n_clients; i--;) { 37 | simpl.createClient(port, host) 38 | .use(simpl.uid()) 39 | .use(simpl.rpc('ping')) 40 | .use(simpl.json()) 41 | .on('connect', function (client) { 42 | client.remote(function (remote) { 43 | remotes.push(remote) 44 | --cnt || measure() 45 | }) 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | // ~ripped from SubStack/dnode-protocol 2 | var parseArgs = exports.parseArgs = function (argv) { 3 | var params = {} 4 | 5 | ;[].forEach.call(argv, function (arg) { 6 | if (typeof arg === 'string') { 7 | if (arg.match(/^\d+$/)) { 8 | params.port = +arg 9 | } 10 | else if (arg.match('^/')) { 11 | params.path = arg 12 | } 13 | else { 14 | params.host = arg 15 | } 16 | } 17 | else if (typeof arg === 'number') { 18 | params.port = arg 19 | } 20 | else if (typeof arg === 'object') { 21 | if (arg.__proto__ === Object.prototype) { 22 | // merge vanilla objects into params 23 | merge(params, arg) 24 | } 25 | else { 26 | // and non-vanilla objects are probably servers 27 | params.server = arg 28 | } 29 | } 30 | else if (typeof arg === 'undefined') { 31 | // ignore 32 | } 33 | else { 34 | throw new Error('Not sure what to do about ' 35 | + typeof arg + ' objects') 36 | } 37 | }) 38 | 39 | params.httpServer = params.server || params.httpServer 40 | params.app = params.server = params.httpServer || params.server 41 | 42 | return params 43 | } 44 | 45 | var merge = exports.merge = function (target, source) { 46 | for (var key in source) { 47 | if (source.hasOwnProperty(key)) { 48 | target[key] = source[key] 49 | } 50 | } 51 | return target 52 | } 53 | -------------------------------------------------------------------------------- /lib/middleware/log.js: -------------------------------------------------------------------------------- 1 | // log middleware 2 | 3 | var slice = [].slice 4 | 5 | module.exports = function (name, both) { 6 | name = name && name + ':' || '*' 7 | function log () { 8 | var args = slice.call(arguments) 9 | args.unshift(name) 10 | console.log.apply(console, args) 11 | } 12 | function listening (args, next) { 13 | log('listening on port', args[1].settings.port) 14 | next() 15 | } 16 | function login (args, next) { 17 | log(' in <==', args[1]) 18 | next() 19 | } 20 | function logout (args, next) { 21 | log('out ==>', args[1]) 22 | next() 23 | } 24 | function connection (args, next) { 25 | log('connection <==', args[1].remoteAddress) 26 | next() 27 | } 28 | function disconnect (args, next) { 29 | log('disconnected !!!', (args[1] && args[1].remoteAddress) || args[1].url) 30 | next() 31 | } 32 | function connect (args, next) { 33 | log('connected ==>', args[1].url) 34 | next() 35 | } 36 | function close (args, next) { 37 | log('connection closed ###') 38 | next() 39 | } 40 | return function () { 41 | this.use('listening', listening) 42 | this.use('connection', connection) 43 | this.use('disconnect', disconnect) 44 | this.use('connect', connect) 45 | this.use('close', close) 46 | this.before('in', login) 47 | this.after('out', logout) 48 | if (both) { 49 | this.after('in', login) 50 | this.before('out', logout) 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /test/test-events.js: -------------------------------------------------------------------------------- 1 | var simpl = require('../') 2 | var test = require('tap').test 3 | 4 | var server, client, socket 5 | 6 | test("start server", function (t) { 7 | t.plan(2) 8 | server = simpl.createServer(8080) 9 | server.use(simpl.uid()) 10 | server.use(simpl.events()) 11 | server.use(simpl.json()) 12 | server.on('ready', function () { 13 | t.pass("server is ready") 14 | }) 15 | server.on('listening', function () { 16 | t.pass("server is listening") 17 | }) 18 | }) 19 | 20 | test("start client", function (t) { 21 | t.plan(2) 22 | server.on('connection', function (s) { 23 | t.pass("received connection") 24 | socket = s 25 | }) 26 | client = simpl.createClient(8080) 27 | client.use(simpl.uid()) 28 | client.use(simpl.events()) 29 | client.use(simpl.json()) 30 | client.on('connect', function () { 31 | t.pass("client connected") 32 | }) 33 | }) 34 | 35 | test("client emit to server", function (t) { 36 | t.plan(1) 37 | socket.remote.once('hello', function (message) { 38 | t.equal(message, 'world') 39 | }) 40 | client.remote.emit('hello', 'world') 41 | }) 42 | 43 | test("server emit to client", function (t) { 44 | t.plan(1) 45 | client.remote.once('hello', function (message) { 46 | t.equal(message, 'world') 47 | }) 48 | socket.remote.emit('hello', 'world') 49 | }) 50 | 51 | test("close client", function (t) { 52 | t.plan(2) 53 | client.once('disconnect', function () { 54 | t.pass("client disconnected") 55 | }) 56 | socket.once('close', function () { 57 | t.pass("socket closed") 58 | }) 59 | client.close() 60 | }) 61 | 62 | test("close server", function (t) { 63 | t.plan(1) 64 | server.once('close', function () { 65 | t.pass("server closed") 66 | }) 67 | server.close() 68 | }) 69 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var simpl = require('../') 2 | var test = require('tap').test 3 | 4 | var server, client, socket 5 | 6 | test("start server", function (t) { 7 | t.plan(2) 8 | server = simpl.createServer(8080) 9 | server.on('ready', function () { 10 | t.pass("server is ready") 11 | }) 12 | server.on('listening', function () { 13 | t.pass("server is listening") 14 | }) 15 | }) 16 | 17 | test("start client", function (t) { 18 | t.plan(2) 19 | server.on('connection', function (s) { 20 | t.pass("received connection") 21 | socket = s 22 | }) 23 | client = simpl.createClient(8080) 24 | client.on('connect', function () { 25 | t.pass("client connected") 26 | }) 27 | }) 28 | 29 | test("client send 'hello' to server", function (t) { 30 | t.plan(3) 31 | socket.once('message', function (message) { 32 | t.equals(message, 'hello') 33 | }) 34 | server.once('message', function (message, s) { 35 | t.equals(message, 'hello') 36 | t.same(s, socket) 37 | }) 38 | client.send('hello') 39 | }) 40 | 41 | test("server send 'hello back' to client", function (t) { 42 | t.plan(3) 43 | client.socket.once('message', function (message) { 44 | t.equals(message, 'hello back') 45 | }) 46 | client.once('message', function (message, s) { 47 | t.equals(message, 'hello back') 48 | t.same(s, client.socket) 49 | }) 50 | socket.send('hello back') 51 | }) 52 | 53 | test("close client", function (t) { 54 | t.plan(2) 55 | client.once('disconnect', function () { 56 | t.pass("client disconnected") 57 | }) 58 | socket.once('close', function () { 59 | t.pass("socket closed") 60 | }) 61 | client.close() 62 | }) 63 | 64 | test("close server", function (t) { 65 | t.plan(1) 66 | server.once('close', function () { 67 | t.pass("server closed") 68 | }) 69 | server.close() 70 | }) 71 | -------------------------------------------------------------------------------- /test/test-rpc.js: -------------------------------------------------------------------------------- 1 | var simpl = require('../') 2 | var test = require('tap').test 3 | 4 | var server, client, socket 5 | 6 | test("start server", function (t) { 7 | t.plan(2) 8 | server = simpl.createServer(8080) 9 | server.use(simpl.uid()) 10 | server.use(simpl.rpc({ 11 | 'multiply': function (a, b, callback) { 12 | callback(a * b) 13 | } 14 | }, 'divide')) 15 | server.use(simpl.json()) 16 | server.on('ready', function () { 17 | t.pass("server is ready") 18 | }) 19 | server.on('listening', function () { 20 | t.pass("server is listening") 21 | }) 22 | }) 23 | 24 | test("start client", function (t) { 25 | t.plan(2) 26 | server.on('connection', function (s) { 27 | t.pass("received connection") 28 | socket = s 29 | }) 30 | client = simpl.createClient(8080) 31 | client.use(simpl.uid()) 32 | client.use(simpl.rpc({ 33 | 'divide': function (a, b, callback) { 34 | callback(a / b) 35 | } 36 | }, 'multiply')) 37 | client.use(simpl.json()) 38 | client.on('connect', function () { 39 | t.pass("client connected") 40 | }) 41 | }) 42 | 43 | test("client call server procedure", function (t) { 44 | t.plan(1) 45 | client.remote.multiply(2, 5, function (reply) { 46 | t.equal(reply, 10, "got server reply") 47 | }) 48 | }) 49 | 50 | test("server call client procedure", function (t) { 51 | t.plan(1) 52 | socket.remote.divide(12, 4 , function (reply) { 53 | t.equal(reply, 3, "got client reply") 54 | }) 55 | }) 56 | 57 | test("close client", function (t) { 58 | t.plan(2) 59 | client.once('disconnect', function () { 60 | t.pass("client disconnected") 61 | }) 62 | socket.once('close', function () { 63 | t.pass("socket closed") 64 | }) 65 | client.close() 66 | }) 67 | 68 | test("close server", function (t) { 69 | t.plan(1) 70 | server.once('close', function () { 71 | t.pass("server closed") 72 | }) 73 | server.close() 74 | }) 75 | -------------------------------------------------------------------------------- /test/test-json.js: -------------------------------------------------------------------------------- 1 | var simpl = require('../') 2 | var test = require('tap').test 3 | 4 | var server, client, socket 5 | 6 | test("start server", function (t) { 7 | t.plan(2) 8 | server = simpl.createServer(8080) 9 | server.use(simpl.json()) 10 | server.on('ready', function () { 11 | t.pass("server is ready") 12 | }) 13 | server.on('listening', function () { 14 | t.pass("server is listening") 15 | }) 16 | }) 17 | 18 | test("start client", function (t) { 19 | t.plan(2) 20 | server.on('connection', function (s) { 21 | t.pass("received connection") 22 | socket = s 23 | }) 24 | client = simpl.createClient(8080) 25 | client.use(simpl.json()) 26 | client.on('connect', function () { 27 | t.pass("client connected") 28 | }) 29 | }) 30 | 31 | test("client send 'hello' to server", function (t) { 32 | t.plan(3) 33 | var msg = { hello: 'world' } 34 | socket.once('message', function (message) { 35 | t.same(message, msg) 36 | }) 37 | server.once('message', function (message, s) { 38 | t.same(message, msg) 39 | t.same(s, socket) 40 | }) 41 | client.send(msg) 42 | }) 43 | 44 | test("server send 'hello back' to client", function (t) { 45 | t.plan(3) 46 | var msg = { hello: 'world' } 47 | client.socket.once('message', function (message) { 48 | t.same(message, msg) 49 | }) 50 | client.once('message', function (message, s) { 51 | t.same(message, msg) 52 | t.same(s, client.socket) 53 | }) 54 | socket.send(msg) 55 | }) 56 | 57 | test("close client", function (t) { 58 | t.plan(2) 59 | client.once('disconnect', function () { 60 | t.pass("client disconnected") 61 | }) 62 | socket.once('close', function () { 63 | t.pass("socket closed") 64 | }) 65 | client.close() 66 | }) 67 | 68 | test("close server", function (t) { 69 | t.plan(1) 70 | server.once('close', function () { 71 | t.pass("server closed") 72 | }) 73 | server.close() 74 | }) 75 | -------------------------------------------------------------------------------- /lib/websocket-client.js: -------------------------------------------------------------------------------- 1 | var util = require('util') 2 | var EventStack = require('eventstack') 3 | var WebSocketClient = require('ws') 4 | 5 | // Client 6 | var Client = exports = module.exports = function (opts) { 7 | EventStack.call(this) 8 | 9 | var self = this 10 | 11 | this.settings = { 12 | host: opts.host || '127.0.0.1' 13 | , port: opts.port || 8080 14 | } 15 | 16 | this.on('in', function (message, socket) { 17 | socket.emit('message', message) 18 | this.emit('message', message, socket) 19 | }) 20 | this.on('out', function (message, socket) { 21 | socket.socket.send(message) 22 | }) 23 | 24 | var wsurl = 'ws://' + this.settings.host + ':' + this.settings.port 25 | this.client = new WebSocketClient(wsurl) 26 | this.client.url = wsurl 27 | this.client.on('error', this.emit.bind(this)) 28 | this.client.on('open', function () { 29 | self.socket = new Connection(self, self.client) 30 | self.socket.on('in', function (message) { 31 | self.emit('in', message, self.socket) 32 | }) 33 | self.socket.on('out', function (message) { 34 | self.emit('out', message, self.socket) 35 | }) 36 | self.socket.on('close', self.emit.bind(self, 'disconnect', self.socket)) 37 | self.emit('connect', self.socket) 38 | }) 39 | } 40 | 41 | util.inherits(Client, EventStack) 42 | 43 | Client.prototype.send = function (message) { 44 | this.socket.send(message) 45 | } 46 | 47 | Client.prototype.close = function () { 48 | this.socket.close() 49 | } 50 | 51 | // Connection 52 | var Connection = exports.Connection = function (parent, socket) { 53 | EventStack.call(this) 54 | 55 | var self = this 56 | 57 | this.url = socket.url 58 | this.parent = parent 59 | this.socket = socket 60 | this.socket.on('close', this.emit.bind(this)) 61 | this.socket._socket.on('end', this.emit.bind(this, 'close')) 62 | this.socket.on('message', this.emit.bind(this, 'in')) 63 | } 64 | 65 | util.inherits(Connection, EventStack) 66 | 67 | Connection.prototype.close = function () { 68 | this.socket._socket.end() 69 | } 70 | 71 | Connection.prototype.send = function (message) { 72 | this.emit('out', message) 73 | } 74 | -------------------------------------------------------------------------------- /lib/browser/simpl.js: -------------------------------------------------------------------------------- 1 | var util = require('util') 2 | var EventStack = require('eventstack') 3 | var WS = window.MozWebSocket ? window.MozWebSocket : window.WebSocket 4 | 5 | var Client = function (port, host) { 6 | EventStack.call(this) 7 | 8 | var self = this 9 | 10 | this.parent = this 11 | 12 | this.settings = { 13 | port: port || '' 14 | , host: host || window.location.href.split('/')[2] 15 | } 16 | 17 | this.on('in', function (message, socket) { 18 | self.emit('message', message, socket) 19 | }) 20 | this.on('out', function (message) { 21 | self.client.send(message) 22 | }) 23 | } 24 | 25 | util.inherits(Client, EventStack) 26 | 27 | Client.prototype.connect = function () { 28 | var self = this 29 | var wsurl = 'ws://' + this.settings.host + (this.settings.port ? ':' + this.settings.port : '') 30 | this.url = wsurl 31 | this.client = new WS(wsurl) 32 | 33 | this.client.onmessage = function (message) { 34 | self.emit('in', message.data, self) 35 | } 36 | this.client.onopen = function () { 37 | self.emit('connect', self) 38 | self.emit('open', self) 39 | self.emit('ready', self) 40 | } 41 | this.client.onerror = function (err) { 42 | self.emit('error', err) 43 | } 44 | this.client.onclose = function () { 45 | self.emit('close', self) 46 | } 47 | } 48 | 49 | Client.prototype.send = function (message) { 50 | this.emit('out', message) 51 | } 52 | 53 | Client.prototype.destroy = function () { 54 | this.client.onmessage = null 55 | this.client.onopen = null 56 | this.client.onerror = null 57 | delete this.client 58 | this.removeAllListeners() 59 | } 60 | 61 | var simpl = Object.create(null) 62 | var middlewares = [ 'date', 'events', 'json', 'log', 'rpc', 'uid', 'dict' ] 63 | 64 | simpl.createClient = function (port, host) { 65 | var client = new Client(port, host) 66 | client.connect() 67 | client._middleware = Object.create(null) 68 | middlewares.forEach(function (m) { 69 | client._middleware[m] = require('middleware/' + m) 70 | }) 71 | return client 72 | } 73 | 74 | middlewares.forEach(function (m) { 75 | simpl[m] = require('middleware/' + m) 76 | }) 77 | 78 | module.exports = simpl 79 | -------------------------------------------------------------------------------- /test/test-date.js: -------------------------------------------------------------------------------- 1 | var simpl = require('../') 2 | var test = require('tap').test 3 | 4 | var server, client, socket 5 | 6 | test("start server", function (t) { 7 | t.plan(2) 8 | server = simpl.createServer(8080) 9 | server.use(simpl.date()) 10 | server.use(simpl.json()) 11 | server.on('ready', function () { 12 | t.pass("server is ready") 13 | }) 14 | server.on('listening', function () { 15 | t.pass("server is listening") 16 | }) 17 | }) 18 | 19 | test("start client", function (t) { 20 | t.plan(2) 21 | server.on('connection', function (s) { 22 | t.pass("received connection") 23 | socket = s 24 | }) 25 | client = simpl.createClient(8080) 26 | client.use(simpl.date()) 27 | client.use(simpl.json()) 28 | client.on('connect', function () { 29 | t.pass("client connected") 30 | }) 31 | }) 32 | 33 | test("client send 'hello' to server", function (t) { 34 | t.plan(5) 35 | var msg = { hello: 'world' } 36 | socket.once('message', function (message) { 37 | t.ok(message.date) 38 | msg.date = message.date 39 | t.same(message, msg) 40 | }) 41 | server.once('message', function (message, s) { 42 | t.ok(message.date) 43 | msg.date = message.date 44 | t.same(message, msg) 45 | t.same(s, socket) 46 | }) 47 | client.send(msg) 48 | }) 49 | 50 | test("server send 'hello back' to client", function (t) { 51 | t.plan(5) 52 | var msg = { hello: 'world' } 53 | client.socket.once('message', function (message) { 54 | t.ok(message.date) 55 | msg.date = message.date 56 | t.same(message, msg) 57 | }) 58 | client.once('message', function (message, s) { 59 | t.ok(message.date) 60 | msg.date = message.date 61 | t.same(message, msg) 62 | t.same(s, client.socket) 63 | }) 64 | socket.send(msg) 65 | }) 66 | 67 | test("close client", function (t) { 68 | t.plan(2) 69 | client.once('disconnect', function () { 70 | t.pass("client disconnected") 71 | }) 72 | socket.once('close', function () { 73 | t.pass("socket closed") 74 | }) 75 | client.close() 76 | }) 77 | 78 | test("close server", function (t) { 79 | t.plan(1) 80 | server.once('close', function () { 81 | t.pass("server closed") 82 | }) 83 | server.close() 84 | }) 85 | -------------------------------------------------------------------------------- /test/test-dict.js: -------------------------------------------------------------------------------- 1 | var simpl = require('../') 2 | var test = require('tap').test 3 | 4 | var server, client, socket 5 | 6 | var dict = [ 7 | [ '{"hello":"world"}', '^a' ] 8 | ] 9 | 10 | test("start server", function (t) { 11 | t.plan(2) 12 | server = simpl.createServer(8080) 13 | server.use(simpl.json()) 14 | server.use(simpl.dict(dict)) 15 | server.on('ready', function () { 16 | t.pass("server is ready") 17 | }) 18 | server.on('listening', function () { 19 | t.pass("server is listening") 20 | }) 21 | }) 22 | 23 | test("start client", function (t) { 24 | t.plan(2) 25 | server.on('connection', function (s) { 26 | t.pass("received connection") 27 | socket = s 28 | }) 29 | client = simpl.createClient(8080) 30 | client.use(simpl.json()) 31 | client.use(simpl.dict(dict)) 32 | client.on('connect', function () { 33 | t.pass("client connected") 34 | }) 35 | }) 36 | 37 | test("client send 'hello' to server", function (t) { 38 | t.plan(4) 39 | var msg = { hello: 'world' } 40 | socket.before('in', function (args, next) { 41 | t.same(args[1], '^a', 'compressed') 42 | next() 43 | }) 44 | socket.once('message', function (message) { 45 | t.same(message, msg) 46 | }) 47 | server.once('message', function (message, s) { 48 | t.same(message, msg) 49 | t.same(s, socket) 50 | }) 51 | client.send(msg) 52 | }) 53 | 54 | test("server send 'hello back' to client", function (t) { 55 | t.plan(4) 56 | var msg = { hello: 'world' } 57 | client.before('in', function (args, next) { 58 | t.same(args[1], '^a', 'compressed') 59 | next() 60 | }) 61 | client.socket.once('message', function (message) { 62 | t.same(message, msg) 63 | }) 64 | client.once('message', function (message, s) { 65 | t.same(message, msg) 66 | t.same(s, client.socket) 67 | }) 68 | socket.send(msg) 69 | }) 70 | 71 | test("close client", function (t) { 72 | t.plan(2) 73 | client.once('disconnect', function () { 74 | t.pass("client disconnected") 75 | }) 76 | socket.once('close', function () { 77 | t.pass("socket closed") 78 | }) 79 | client.close() 80 | }) 81 | 82 | test("close server", function (t) { 83 | t.plan(1) 84 | server.once('close', function () { 85 | t.pass("server closed") 86 | }) 87 | server.close() 88 | }) 89 | -------------------------------------------------------------------------------- /examples/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | simpl chat 7 | 30 | 31 | 32 | 33 |

 34 | 
 35 | 
 36 | 
 37 | 
 99 | 
100 | 
101 | 


--------------------------------------------------------------------------------
/lib/middleware/rpc.js:
--------------------------------------------------------------------------------
  1 | // rpc middleware
  2 | 
  3 | var slice = [].slice
  4 | 
  5 | module.exports = function () {
  6 |   var namespace = 'rpc' + Math.floor(Math.random() * Date.now()).toString(36)
  7 |   var args = slice.call(arguments)
  8 |   var arg
  9 |   var procs = []
 10 |   var context = {}
 11 |   var callbacks = {}
 12 |   var inSocket = false
 13 | 
 14 |   while (arg = args.shift()) {
 15 |     if ('string' === typeof arg) procs.push(arg)
 16 |     else if (Array.isArray(arg)) procs = procs.concat(arg)
 17 |     else if ('boolean' === typeof arg) inSocket = arg
 18 |     else context = arg || {}
 19 |   }
 20 | 
 21 |   function merge (dest, src) {
 22 |     for (var k in src) {
 23 |       dest[k] = src[k]
 24 |     }
 25 |     return dest
 26 |   }
 27 | 
 28 |   function rpcListener (proc, args, socket) {
 29 |     if ((context.hasOwnProperty && context.hasOwnProperty(proc))
 30 |       || (proc in context)) {
 31 |       var procfn = context[proc]
 32 |       if ('function' === typeof procfn) {
 33 |         try {
 34 |           procfn.apply(inSocket ? socket : context, args)
 35 |         } catch (_) {}
 36 |       }
 37 |     }
 38 |   }
 39 | 
 40 |   function makeRemoteProcedures (parent) {
 41 |     var remoteContext = {}
 42 |     procs.forEach(function (proc) {
 43 |       remoteContext[proc] = function remoteCall () {
 44 |         var args = slice.call(arguments)
 45 |         var callback = function () {}
 46 |         if ('function' === typeof args[args.length - 1]) {
 47 |           callback = args.pop()
 48 |         }
 49 |         parent.send({ proc: proc, args: args, cb: callback })
 50 |       }
 51 |     })
 52 |     return remoteContext
 53 |   }
 54 | 
 55 |   function socketMiddleware (args, next) {
 56 |     var socket = args[1]
 57 |     socket.on(namespace, rpcListener)
 58 |     socket.remote = merge(socket.remote || {}, makeRemoteProcedures(socket))
 59 |     if (socket.parent) socket.parent.remote = socket.remote
 60 |     next()
 61 |   }
 62 | 
 63 |   return function () {
 64 |     if (this.client) {
 65 |       this.use('connect', socketMiddleware)
 66 |     } else {
 67 |       this.use('connection', socketMiddleware)
 68 |     }
 69 | 
 70 |     this.before('in', function (args, next) {
 71 |       var o = args[1], socket = args[2]
 72 |       if (o.reply && o.to) {
 73 |         try {
 74 |           callbacks[o.to].apply(this, o.reply)
 75 |         } catch (_) {}
 76 |         delete callbacks[o.to]
 77 |       } else if ('string' === typeof o.proc
 78 |         && Array.isArray(o.args)) {
 79 |         o.args.push(function () {
 80 |           socket.send({ reply: slice.call(arguments), to: o.id })
 81 |         })
 82 |         socket.emit(namespace, o.proc, o.args, socket)
 83 |       } else {
 84 |         next()
 85 |       }
 86 |     })
 87 | 
 88 |     this.after('out', function (args, next) {
 89 |       var o = args[1]
 90 |       if ('string' === typeof o.proc
 91 |         && Array.isArray(o.args)
 92 |         && 'function' === typeof o.cb) {
 93 |         callbacks[o.id] = o.cb
 94 |         delete o.cb
 95 |       }
 96 |       next()
 97 |     })
 98 |   }
 99 | }
100 | 


--------------------------------------------------------------------------------
/lib/websocket-server.js:
--------------------------------------------------------------------------------
  1 | var util = require('util')
  2 | var EventStack = require('eventstack')
  3 | var expose = require('express-expose')
  4 | var WebSocketServer = require('ws').Server
  5 | 
  6 | // Server
  7 | var Server = exports = module.exports = function (opts) {
  8 |   EventStack.call(this)
  9 | 
 10 |   var self = this
 11 | 
 12 |   var app = this.app = opts.httpServer || opts.app
 13 |   app.on('listening', function () {
 14 |     self.settings.port = app.address().port
 15 |     self.emit('ready', self)
 16 |     self.emit('listening', self)
 17 |   })
 18 | 
 19 |   self.settings = opts
 20 | 
 21 |   app.exposeRequire()
 22 |   app.expose({ inherits: util.inherits }, 'util')
 23 |   app.exposeModule(require.resolve('./events'), 'events')
 24 |   app.exposeModule(require.resolve('eventstack/lib/eventstack'), 'eventstack')
 25 |   app.exposeModule(require.resolve('./browser/simpl'), 'simpl')
 26 | 
 27 |   if (app.get) {
 28 |     ;[ 'date', 'events', 'json', 'log', 'rpc', 'uid', 'dict' ]
 29 |     .forEach(function (m) {
 30 |       app.exposeModule(require.resolve('./middleware/' + m), 'middleware/' + m)
 31 |     })
 32 | 
 33 |     app.get('/simpl.js', function (req, res) {
 34 |       res.setHeader('content-type', 'application/javascript')
 35 |       res.send(app.exposed())
 36 |     })
 37 |   }
 38 |   else {
 39 |     app.on('request', function (req, res) {
 40 |       if (req.url === '/simpl.js') {
 41 |         res.writeHead(200, { 'content-type': 'application/javascript' })
 42 |         res.end(app.exposed())
 43 |       }
 44 |     })
 45 |   }
 46 | 
 47 |   app.on('close', this.emit.bind(this))
 48 | 
 49 |   this.on('in', function (message, socket) {
 50 |     socket.emit('message', message)
 51 |     this.emit('message', message, socket)
 52 |   })
 53 |   this.on('out', function (message, socket) {
 54 |     try {
 55 |       socket.socket.send(message)
 56 |     } catch (_) {}
 57 |   })
 58 | 
 59 |   this.server = new WebSocketServer({ server: this.app })
 60 |   this.server.on('error', this.emit.bind(this))
 61 |   this.server.on('connection', function (socket) {
 62 |     socket = new Connection(self, socket)
 63 |     socket.on('in', function (message) {
 64 |       self.emit('in', message, socket)
 65 |     })
 66 |     socket.on('out', function (message) {
 67 |       self.emit('out', message, socket)
 68 |     })
 69 |     socket.on('close', function () {
 70 |       self.emit('disconnect', socket)
 71 |     })
 72 |     self.emit('connection', socket)
 73 |   })
 74 | }
 75 | 
 76 | util.inherits(Server, EventStack)
 77 | 
 78 | Server.prototype.close = function () {
 79 |   this.server.close()
 80 |   this.app.close()
 81 |   this.emit('close', this)
 82 | }
 83 | 
 84 | // Connection
 85 | var Connection = exports.Connection = function (server, socket) {
 86 |   EventStack.call(this)
 87 | 
 88 |   this.server = server
 89 |   this.socket = socket
 90 | 
 91 |   this.remoteAddress = socket.upgradeReq.socket.remoteAddress
 92 | 
 93 |   this.socket.on('close', this.emit.bind(this))
 94 |   this.socket._socket.on('end', this.emit.bind(this, 'close'))
 95 |   this.socket.on('message', this.emit.bind(this, 'in'))
 96 |   this.socket.on('error', this.emit.bind(this))
 97 | }
 98 | 
 99 | util.inherits(Connection, EventStack)
100 | 
101 | Connection.prototype.send = function (message) {
102 |   this.emit('out', message)
103 | }
104 | 
105 | Connection.prototype.close = function () {
106 |   this.socket.close()
107 | }
108 | 
109 | Connection.prototype.disconnect = Connection.prototype.close
110 | 


--------------------------------------------------------------------------------
/lib/events.js:
--------------------------------------------------------------------------------
  1 | if ('undefined' === typeof process) process = {};
  2 | if (!process.EventEmitter) process.EventEmitter = function () {};
  3 | 
  4 | var EventEmitter = exports.EventEmitter = process.EventEmitter;
  5 | var isArray = Array.isArray;
  6 | 
  7 | // By default EventEmitters will print a warning if more than
  8 | // 10 listeners are added to it. This is a useful default which
  9 | // helps finding memory leaks.
 10 | //
 11 | // Obviously not all Emitters should be limited to 10. This function allows
 12 | // that to be increased. Set to zero for unlimited.
 13 | var defaultMaxListeners = 10;
 14 | EventEmitter.prototype.setMaxListeners = function(n) {
 15 |   this._events.maxListeners = n;
 16 | };
 17 | 
 18 | 
 19 | EventEmitter.prototype.emit = function(type) {
 20 |   // If there is no 'error' event listener then throw.
 21 |   if (type === 'error') {
 22 |     if (!this._events || !this._events.error ||
 23 |         (isArray(this._events.error) && !this._events.error.length))
 24 |     {
 25 |       if (arguments[1] instanceof Error) {
 26 |         throw arguments[1]; // Unhandled 'error' event
 27 |       } else {
 28 |         throw new Error("Uncaught, unspecified 'error' event.");
 29 |       }
 30 |       return false;
 31 |     }
 32 |   }
 33 | 
 34 |   if (!this._events) return false;
 35 |   var handler = this._events[type];
 36 |   if (!handler) return false;
 37 | 
 38 |   if (typeof handler == 'function') {
 39 |     switch (arguments.length) {
 40 |       // fast cases
 41 |       case 1:
 42 |         handler.call(this);
 43 |         break;
 44 |       case 2:
 45 |         handler.call(this, arguments[1]);
 46 |         break;
 47 |       case 3:
 48 |         handler.call(this, arguments[1], arguments[2]);
 49 |         break;
 50 |       // slower
 51 |       default:
 52 |         var args = Array.prototype.slice.call(arguments, 1);
 53 |         handler.apply(this, args);
 54 |     }
 55 |     return true;
 56 | 
 57 |   } else if (isArray(handler)) {
 58 |     var args = Array.prototype.slice.call(arguments, 1);
 59 | 
 60 |     var listeners = handler.slice();
 61 |     for (var i = 0, l = listeners.length; i < l; i++) {
 62 |       listeners[i].apply(this, args);
 63 |     }
 64 |     return true;
 65 | 
 66 |   } else {
 67 |     return false;
 68 |   }
 69 | };
 70 | 
 71 | // EventEmitter is defined in src/node_events.cc
 72 | // EventEmitter.prototype.emit() is also defined there.
 73 | EventEmitter.prototype.addListener = function(type, listener) {
 74 |   if ('function' !== typeof listener) {
 75 |     throw new Error('addListener only takes instances of Function');
 76 |   }
 77 | 
 78 |   if (!this._events) this._events = {};
 79 | 
 80 |   // To avoid recursion in the case that type == "newListeners"! Before
 81 |   // adding it to the listeners, first emit "newListeners".
 82 |   this.emit('newListener', type, listener);
 83 | 
 84 |   if (!this._events[type]) {
 85 |     // Optimize the case of one listener. Don't need the extra array object.
 86 |     this._events[type] = listener;
 87 |   } else if (isArray(this._events[type])) {
 88 | 
 89 |     // Check for listener leak
 90 |     if (!this._events[type].warned) {
 91 |       var m;
 92 |       if (this._events.maxListeners !== undefined) {
 93 |         m = this._events.maxListeners;
 94 |       } else {
 95 |         m = defaultMaxListeners;
 96 |       }
 97 | 
 98 |       if (m && m > 0 && this._events[type].length > m) {
 99 |         this._events[type].warned = true;
100 |         console.error('(node) warning: possible EventEmitter memory ' +
101 |                       'leak detected. %d listeners added. ' +
102 |                       'Use emitter.setMaxListeners() to increase limit.',
103 |                       this._events[type].length);
104 |         console.trace();
105 |       }
106 |     }
107 | 
108 |     // If we've already got an array, just append.
109 |     this._events[type].push(listener);
110 |   } else {
111 |     // Adding the second element, need to change to array.
112 |     this._events[type] = [this._events[type], listener];
113 |   }
114 | 
115 |   return this;
116 | };
117 | 
118 | EventEmitter.prototype.on = EventEmitter.prototype.addListener;
119 | 
120 | EventEmitter.prototype.once = function(type, listener) {
121 |   var self = this;
122 |   self.on(type, function g() {
123 |     self.removeListener(type, g);
124 |     listener.apply(this, arguments);
125 |   });
126 | 
127 |   return this;
128 | };
129 | 
130 | EventEmitter.prototype.removeListener = function(type, listener) {
131 |   if ('function' !== typeof listener) {
132 |     throw new Error('removeListener only takes instances of Function');
133 |   }
134 | 
135 |   // does not use listeners(), so no side effect of creating _events[type]
136 |   if (!this._events || !this._events[type]) return this;
137 | 
138 |   var list = this._events[type];
139 | 
140 |   if (isArray(list)) {
141 |     var i = list.indexOf(listener);
142 |     if (i < 0) return this;
143 |     list.splice(i, 1);
144 |     if (list.length == 0)
145 |       delete this._events[type];
146 |   } else if (this._events[type] === listener) {
147 |     delete this._events[type];
148 |   }
149 | 
150 |   return this;
151 | };
152 | 
153 | EventEmitter.prototype.removeAllListeners = function(type) {
154 |   // does not use listeners(), so no side effect of creating _events[type]
155 |   if (type && this._events && this._events[type]) this._events[type] = null;
156 |   return this;
157 | };
158 | 
159 | EventEmitter.prototype.listeners = function(type) {
160 |   if (!this._events) this._events = {};
161 |   if (!this._events[type]) this._events[type] = [];
162 |   if (!isArray(this._events[type])) {
163 |     this._events[type] = [this._events[type]];
164 |   }
165 |   return this._events[type];
166 | };
167 | 


--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
  1 | # simpl
  2 | 
  3 | Highly pluggable WebSockets framework
  4 | 
  5 | ## Introduction
  6 | 
  7 | simpl is a framework over WebSockets, which allows extensions (or middleware)
  8 | to be built that extend functionality with a clean and easy api.
  9 | 
 10 | ## Installation
 11 | 
 12 | `npm install simpl`
 13 | 
 14 | ## Full-featured example
 15 | 
 16 | Server:
 17 | 
 18 | ```javascript
 19 | var express = require('express')
 20 | var app = express.createServer()
 21 | app.use(express.static(__dirname + '/public'))
 22 | 
 23 | var simpl = require('simpl');
 24 | var server = simpl.createServer(app);
 25 | 
 26 | server.use(simpl.uid());
 27 | server.use(simpl.rpc({
 28 |   'multiply': function (a, b, callback) {
 29 |     callback(a * b);
 30 |   }
 31 | }));
 32 | server.use(simpl.json());
 33 | server.use(simpl.log());
 34 | 
 35 | app.listen(8080);
 36 | ```
 37 | 
 38 | Browser:
 39 | 
 40 | ```html
 41 | 
 42 | 
 50 | 
 55 | 
56 | ``` 57 | 58 | 59 | ## API 60 | 61 | 62 | ### Server 63 | 64 | 65 | **server = simpl.createServer(port[, host])** 66 | 67 | Creates a webserver with a websocket server attached to it and listens on `port` 68 | and `host`. 69 | 70 | **server = simpl.createServer(app)** 71 | 72 | Attaches a websocket server to an existing `express` server. 73 | 74 | 75 | 76 | ### Server Methods 77 | 78 | **server.use(fn || 'event', fn )** 79 | 80 | Use an [EventStack](https://github.com/stagas/eventstack) middleware. 81 | 82 | **server.close()** 83 | 84 | Closes the server. In case where it's attached to a http server it closes the 85 | http server. 86 | 87 | 88 | 89 | ### Server Events 90 | 91 | 92 | **ready** 93 | 94 | Emits when server is listening for connections. 95 | 96 | **connection (connection)** 97 | 98 | Emits when a client connects to the server. Callbacks a `Connection` object. 99 | 100 | **message (message, connection)** 101 | 102 | Emits when server receives a message. 103 | 104 | 105 | 106 | 107 | ### Connection 108 | 109 | 110 | ### Connection Methods 111 | 112 | 113 | **socket.send(message)** 114 | 115 | Sends a message. 116 | 117 | **socket.close()** 118 | 119 | Closes a connection. 120 | 121 | 122 | 123 | ### Connection Events 124 | 125 | 126 | **message (message)** 127 | 128 | Emits when a message is received. 129 | 130 | **close** 131 | 132 | Emits when the connection closes. 133 | 134 | 135 | 136 | 137 | ### Client 138 | 139 | 140 | **client = simpl.createClient(port[, host])** 141 | 142 | Creates a client and connects to a websocket server on `port` and `host`. 143 | 144 | 145 | 146 | ### Client Methods 147 | 148 | **client.use(fn || 'event', fn)** 149 | 150 | Use an [EventStack](https://github.com/stagas/eventstack) middleware. 151 | 152 | **client.send(message)** 153 | 154 | Sends a message to the server. 155 | 156 | **client.close()** 157 | 158 | Closes the connection. 159 | 160 | 161 | 162 | ### Client Events 163 | 164 | 165 | **connect** 166 | 167 | Emits when the client connects to the server. 168 | 169 | **message (message)** 170 | 171 | Emits when the client receives a message. 172 | 173 | 174 | ## Middleware 175 | 176 | **[log](https://github.com/stagas/simpl/blob/master/lib/middleware/log.js)** -- Logs activity. 177 | 178 | **[uid](https://github.com/stagas/simpl/blob/master/lib/middleware/uid.js)** -- Provides a unique id to each outgoing message. Used by `rpc`. 179 | 180 | **[sid](https://github.com/stagas/simpl/blob/master/lib/middleware/sid.js)** -- Attach a unique id to the socket. 181 | 182 | **[track](https://github.com/stagas/simpl/blob/master/lib/middleware/track.js)** -- Keep track of connected clients. 183 | 184 | **[broadcast](https://github.com/stagas/simpl/blob/master/lib/middleware/broadcast.js)** -- Adds a `.broadcast` method to the sockets. 185 | 186 | **[json](https://github.com/stagas/simpl/blob/master/lib/middleware/json.js)** -- Send and receive objects (wrapper for `JSON.parse`/`stringify`). Used by almost all other middleware. 187 | 188 | **[date](https://github.com/stagas/simpl/blob/master/lib/middleware/date.js)** -- Adds a date field to each outgoing message, and parses incoming dates to native `Date` objects. 189 | 190 | **[rpc](https://github.com/stagas/simpl/blob/master/lib/middleware/rpc.js)** -- Remote Procedure Call. Used by `events`. 191 | 192 | Example: 193 | 194 | _Server:_ 195 | 196 | ```javascript 197 | server.use(simpl.rpc({ 198 | someMethod: function (x, y, z, callback) { 199 | // do stuff 200 | callback(result); 201 | } 202 | })); 203 | ``` 204 | 205 | _Client:_ 206 | 207 | ```javascript 208 | client.use(simpl.rpc('someMethod')) 209 | client.on('connect', function () { 210 | client.remote.someMethod('arg', 'arg', ..., function (result) { 211 | // do something with the result 212 | }); 213 | }) 214 | ``` 215 | 216 | **[events](https://github.com/stagas/simpl/blob/master/lib/middleware/events.js)** -- Emit events remotely. 217 | 218 | Example: 219 | 220 | _Server:_ 221 | 222 | ```javascript 223 | server.use(simpl.events()); 224 | server.on('connection', function (socket) { 225 | socket.remote.on('some event', function (data) { 226 | // do something with the data 227 | }); 228 | }); 229 | ``` 230 | 231 | _Client:_ 232 | 233 | ```javascript 234 | client.use(simpl.events()); 235 | client.on('connect', function () { 236 | client.remote.emit('some event', 'some data'); 237 | }); 238 | ``` 239 | 240 | **[dict](https://github.com/stagas/simpl/blob/master/lib/middleware/dict.js)** -- Dictionary (de)compressor. 241 | 242 | Example: 243 | 244 | ```javascript 245 | var dict = [ 246 | [ '{"some":"big","data":"thing"}', '^a' ], 247 | [ '{"some":"other","big":"data"}', '^b' ] 248 | ]; 249 | server.use(simpl.json()); 250 | server.use(simpl.dict(dict)); 251 | ``` 252 | It will replace all occurrences of the first string in each array with the second on 253 | an outgoing message and run in reverse order for incoming messages. It can significantly reduce 254 | bandwidth in cases where the same pattern gets repeated in I/O. Use simpl.log() to find 255 | strings that are repeated often and copy & paste into the dictionary. 256 | 257 | 258 | ## Licence 259 | 260 | MIT/X11 261 | --------------------------------------------------------------------------------