├── .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 |
--------------------------------------------------------------------------------