var EventEmitter = require('events').EventEmitter; var scrubber = require('./lib/scrub'); var objectKeys = require('./lib/keys'); var forEach = require('./lib/foreach'); var isEnumerable = require('./lib/is_enum'); module.exports = function (cons, opts) { return new Proto(cons, opts); }; (function () { // browsers bleh for (var key in EventEmitter.prototype) { Proto.prototype[key] = EventEmitter.prototype[key]; } })(); function Proto (cons, opts) { var self = this; EventEmitter.call(self); if (!opts) opts = {}; self.remote = {}; self.callbacks = { local : [], remote : [] }; self.wrap = opts.wrap; self.unwrap = opts.unwrap; self.scrubber = scrubber(self.callbacks.local); if (typeof cons === 'function') { self.instance = new cons(self.remote, self); } else self.instance = cons || {}; } Proto.prototype.start = function () { this.request('methods', [ this.instance ]); }; Proto.prototype.cull = function (id) { delete this.callbacks.remote[id]; this.emit('request', { method : 'cull', arguments : [ id ] }); }; Proto.prototype.request = function (method, args) { var scrub = this.scrubber.scrub(args); this.emit('request', { method : method, arguments : scrub.arguments, callbacks : scrub.callbacks, links : scrub.links }); }; Proto.prototype.handle = function (req) { var self = this; var args = self.scrubber.unscrub(req, function (id) { if (self.callbacks.remote[id] === undefined) { // create a new function only if one hasn't already been created // for a particular id var cb = function () { self.request(id, [].slice.apply(arguments)); }; self.callbacks.remote[id] = self.wrap ? self.wrap(cb, id) : cb; return cb; } return self.unwrap ? self.unwrap(self.callbacks.remote[id], id) : self.callbacks.remote[id] ; }); if (req.method === 'methods') { self.handleMethods(args[0]); } else if (req.method === 'cull') { forEach(args, function (id) { delete self.callbacks.local[id]; }); } else if (typeof req.method === 'string') { if (isEnumerable(self.instance, req.method)) { self.apply(self.instance[req.method], args); } else { self.emit('fail', new Error( 'request for non-enumerable method: ' + req.method )); } } else if (typeof req.method == 'number') { var fn = self.callbacks.local[req.method]; if (!fn) { self.emit('fail', new Error('no such method')); } else self.apply(fn, args); } }; Proto.prototype.handleMethods = function (methods) { var self = this; if (typeof methods != 'object') { methods = {}; } // copy since assignment discards the previous refs forEach(objectKeys(self.remote), function (key) { delete self.remote[key]; }); forEach(objectKeys(methods), function (key) { self.remote[key] = methods[key]; }); self.emit('remote', self.remote); self.emit('ready'); }; Proto.prototype.apply = function (f, args) { try { f.apply(undefined, args) } catch (err) { this.emit('error', err) } };