From 55d0f0b18ad08c984ac6501c949dc95d3eeb003d Mon Sep 17 00:00:00 2001 From: Jack Allnutt Date: Thu, 21 Aug 2014 11:54:17 +0100 Subject: [PATCH] Modify PluginInterface to use promises on server --- package.json | 15 +- server/clientcommands.js | 5 +- server/irc/channel.js | 18 +- server/irc/connection.js | 2 +- server/irc/state.js | 2 +- server/irc/user.js | 6 +- server/plugininterface.js | 300 +++++++++++---------------------- server_modules/example.js | 5 + server_modules/proxychecker.js | 2 +- server_modules/stats.js | 6 +- 10 files changed, 131 insertions(+), 230 deletions(-) diff --git a/package.json b/package.json index 4a0dfab..a8b0381 100644 --- a/package.json +++ b/package.json @@ -15,18 +15,19 @@ "postinstall": "node client/build.js" }, "dependencies": { - "node-static": "0.7.3", - "uglify-js": "2.4.12", - "engine.io": "1.2", - "lodash": "2.4.1", "daemonize2": "0.4.2", + "engine.io": "1.2", + "es6-promise": "^1.0.0", "eventemitter2": "0.4.13", + "iconv-lite": "0.2.11", "ipaddr.js": "0.1.2", + "lodash": "2.4.1", + "negotiator": "0.4.3", + "node-static": "0.7.3", + "po2json": "0.2.3", "socksjs": "0.4.4", - "iconv-lite": "0.2.11", "spdy": "1.19.1", - "po2json": "0.2.3", - "negotiator": "0.4.3", + "uglify-js": "2.4.12", "winston": "~0.7.2" }, "engines": { diff --git a/server/clientcommands.js b/server/clientcommands.js index e8abdb5..11375ea 100644 --- a/server/clientcommands.js +++ b/server/clientcommands.js @@ -46,15 +46,14 @@ ClientCommands.prototype.addRpcEvents = function(client, rpc) { client: client, connection: connection }) - .done(function() { + .then(function() { // Listeners expect arguments in a (connection, callback, args..n) format, so preppend // the connection + callback fn_args = rpc_args.slice(0); fn_args.unshift(connection, callback); the_fn.apply(client, fn_args); - }) - .prevented(function() { + }, function() { // The RPC call was prevented from running by a module }); }; diff --git a/server/irc/channel.js b/server/irc/channel.js index b95dd1f..089aefc 100644 --- a/server/irc/channel.js +++ b/server/irc/channel.js @@ -53,7 +53,7 @@ function onJoin(event) { connection: this.irc_connection, irc_event: event }) - .done(function() { + .then(function() { that.irc_connection.clientEvent('channel', { type: 'join', channel: that.name, @@ -74,7 +74,7 @@ function onPart(event) { connection: this.irc_connection, irc_event: event }) - .done(function() { + .then(function() { that.irc_connection.clientEvent('channel', { type: 'part', nick: event.nick, @@ -96,7 +96,7 @@ function onKick(event) { connection: this.irc_connection, irc_event: event }) - .done(function() { + .then(function() { that.irc_connection.clientEvent('channel', { type: 'kick', kicked: event.kicked, // Nick of the kicked @@ -119,7 +119,7 @@ function onQuit(event) { connection: this.irc_connection, irc_event: event }) - .done(function() { + .then(function() { that.irc_connection.clientEvent('channel', { type: 'quit', nick: event.nick, @@ -140,7 +140,7 @@ function onMsg(event) { connection: this.irc_connection, irc_event: event }) - .done(function() { + .then(function() { that.irc_connection.clientEvent('message', { type: 'message', nick: event.nick, @@ -162,7 +162,7 @@ function onAction(event) { connection: this.irc_connection, irc_event: event }) - .done(function() { + .then(function() { that.irc_connection.clientEvent('message', { type: 'action', nick: event.nick, @@ -184,7 +184,7 @@ function onNotice(event) { connection: this.irc_connection, irc_event: event }) - .done(function() { + .then(function() { that.irc_connection.clientEvent('message', { type: 'notice', from_server: event.from_server, @@ -265,7 +265,7 @@ function onTopic(event) { connection: this.irc_connection, irc_event: event }) - .done(function() { + .then(function() { that.irc_connection.clientEvent('topic', { nick: event.nick, channel: that.name, @@ -313,7 +313,7 @@ function onMode(event) { connection: this.irc_connection, irc_event: event }) - .done(function() { + .then(function() { that.irc_connection.clientEvent('mode', { target: event.target, nick: event.nick, diff --git a/server/irc/connection.js b/server/irc/connection.js index da5f173..db81fa9 100644 --- a/server/irc/connection.js +++ b/server/irc/connection.js @@ -676,7 +676,7 @@ var socketConnectHandler = function () { // Let the webirc/etc detection modify any required parameters connect_data = findWebIrc.call(this, connect_data); - global.modules.emit('irc authorize', connect_data).done(function ircAuthorizeCb() { + global.modules.emit('irc authorize', connect_data).then(function ircAuthorizeCb() { var gecos = that.gecos; if (!gecos && global.config.default_gecos) { diff --git a/server/irc/state.js b/server/irc/state.js index 9ae2df0..f1a6d51 100755 --- a/server/irc/state.js +++ b/server/irc/state.js @@ -84,7 +84,7 @@ State.prototype.connect = function (hostname, port, ssl, nick, user, options, ca // Call any modules before making the connection global.modules.emit('irc connecting', {state: this, connection: con}) - .done(function () { + .then(function () { con.connect(); }); }; diff --git a/server/irc/user.js b/server/irc/user.js index 082381e..9987ad3 100755 --- a/server/irc/user.js +++ b/server/irc/user.js @@ -234,7 +234,7 @@ function onNotice(event) { connection: this.irc_connection, irc_event: event }) - .done(function() { + .then(function() { that.irc_connection.clientEvent('message', { type: 'notice', from_server: event.from_server, @@ -266,7 +266,7 @@ function onPrivmsg(event) { connection: this.irc_connection, irc_event: event }) - .done(function() { + .then(function() { that.irc_connection.clientEvent('message', { type: 'message', nick: event.nick, @@ -286,7 +286,7 @@ function onAction(event) { connection: this.irc_connection, irc_event: event }) - .done(function() { + .then(function() { that.irc_connection.clientEvent('message', { type: 'action', nick: event.nick, diff --git a/server/plugininterface.js b/server/plugininterface.js index 070d05d..322efa6 100644 --- a/server/plugininterface.js +++ b/server/plugininterface.js @@ -1,242 +1,136 @@ -/* - * The same functionality as EventEmitter but with the inclusion of callbacks - */ +var _ = require('lodash'), + Promise = require('es6-promise').Promise; - - -function PluginInterface () { - // Holder for all the bound listeners by this module - this._listeners = {}; +function PluginInterface() { + this.listeners = []; } +PluginInterface.prototype.on = PluginInterface.prototype.addListener = function addListener(type, listener) { + if (typeof listener !== 'function') { + throw new TypeError('listener must be a function'); + } - -PluginInterface.prototype.on = function (event_name, fn, scope) { - this._listeners[event_name] = this._listeners[event_name] || []; - this._listeners[event_name].push(['on', fn, scope]); -}; - - - -PluginInterface.prototype.once = function (event_name, fn, scope) { - this._listeners[event_name] = this._listeners[event_name] || []; - this._listeners[event_name].push(['once', fn, scope]); -}; - - - -PluginInterface.prototype.off = function (event_name, fn, scope) { - var idx; - - if (typeof event_name === 'undefined') { - // Remove all listeners - this._listeners = {}; - - } else if (typeof fn === 'undefined') { - // Remove all of 1 event type - delete this._listeners[event_name]; - - } else if (typeof scope === 'undefined') { - // Remove a single event type + callback - for (idx in (this._listeners[event_name] || [])) { - if (this._listeners[event_name][idx][1] === fn) { - delete this._listeners[event_name][idx]; - } + if (this.listeners[type]) { + if (!_.contains(this.listeners[type], listener)) { + this.listeners[type].push(listener); } } else { - // Remove a single event type + callback + scope - for (idx in (this._listeners[event_name] || [])) { - if (this._listeners[event_name][idx][1] === fn && this._listeners[event_name][idx][2] === scope) { - delete this._listeners[event_name][idx]; - } - } + this.listeners[type] = [listener]; } -}; - - -// Call all the listeners for a certain event, passing them some event data that may be changed -PluginInterface.prototype.emit = function (event_name, event_data) { - var emitter = new this.EmitCall(event_name, event_data); - var listeners = this._listeners[event_name] || []; - - // Once emitted, remove any 'once' bound listeners - emitter.done(function () { - var len = listeners.length, - idx; - - for(idx = 0; idx < len; idx++) { - if (listeners[idx][0] === 'once') { - listeners[idx] = undefined; - } - } - }); - - // Emit the event to the listeners and return - emitter.callListeners(listeners); - return emitter; + return this; }; - - -// Promise style object to emit events to listeners -PluginInterface.prototype.EmitCall = function EmitCall (event_name, event_data) { - var that = this, - completed = false, - completed_fn = [], - - // Has event.preventDefault() been called - prevented = false, - prevented_fn = []; - - - // Emit this event to an array of listeners - function callListeners(listeners) { - var current_event_idx = -1; - - // Make sure we have some data to pass to the listeners - event_data = event_data || undefined; - - // If no bound listeners for this event, leave now - if (listeners.length === 0) { - emitComplete(); - return; - } - - - // Call the next listener in our array - function nextListener() { - var listener, event_obj; - - // We want the next listener - current_event_idx++; - - // If we've ran out of listeners end this emit call - if (!listeners[current_event_idx]) { - emitComplete(); - return; - } - - // Object the listener ammends to tell us what it's going to do - event_obj = { - // If changed to true, expect this listener is going to callback - wait: false, - - // If wait is true, this callback must be called to continue running listeners - callback: function () { - // Invalidate this callback incase a listener decides to call it again - event_obj.callback = undefined; - - nextListener.apply(that); - }, - - // Prevents the default 'done' functions from executing - preventDefault: function () { - prevented = true; - } - }; - - - listener = listeners[current_event_idx]; - listener[1].call(listener[2] || that, event_obj, event_data); - - // If the listener hasn't signalled it's going to wait, proceed to next listener - if (!event_obj.wait) { - // Invalidate the callback just incase a listener decides to call it anyway - event_obj.callback = undefined; - - nextListener(); - } - } - - nextListener(); +PluginInterface.prototype.once = function once(type, listener) { + if (typeof listener !== 'function') { + throw new TypeError('listener must be a function'); } + var fired = false; + function g() { + this.removeListener(type, g); - function emitComplete() { - completed = true; - - var funcs = prevented ? prevented_fn : completed_fn; - funcs = funcs || []; - - // Call the completed/prevented functions - for (var idx = 0; idx < funcs.length; idx++) { - if (typeof funcs[idx] === 'function') funcs[idx](); + if (!fired) { + fired = true; + listener.apply(this, arguments); } } + g.listener = listener; + this.on(type, g); + return this; +}; - function addCompletedFunc(fn) { - // Only accept functions - if (typeof fn !== 'function') return false; - - completed_fn.push(fn); - - // If we have already completed the emits, call this now - if (completed && !prevented) fn(); - +PluginInterface.prototype.off = PluginInterface.prototype.removeListener = function removeListener(type, listener) { + if (!this.listeners[type]) { return this; } + this.listeners[type] = _.without(this.listeners[type], listener); + return this; +}; - function addPreventedFunc(fn) { - // Only accept functions - if (typeof fn !== 'function') return false; - - prevented_fn.push(fn); - - // If we have already completed the emits, call this now - if (completed && prevented) fn(); - - return this; - } +PluginInterface.prototype.emit = function emit(type, data) { + var that = this; + return new Promise(function (emit_resolve, emit_reject) { + var rejected = false, + rejected_reasons = []; + if (!that.listeners[type]) { + return emit_resolve(data); + } - return { - callListeners: callListeners, - done: addCompletedFunc, - prevented: addPreventedFunc - }; + (that.listeners[type].reduce(function (listener_promise, listener) { + return listener_promise.then(function (data) { + return new Promise(function (resolve) { + listener({ + callback: function () { + resolve(data); + }, + preventDefault: function (reason) { + rejected = true; + if (reason) { + rejected_reasons.push(reason); + } + } + }, data); + }); + }); + }, Promise.resolve(data))).then(function (data) { + if (rejected) { + emit_reject({data: data, reasons: rejected_reasons}); + } else { + emit_resolve(data); + } + }); + }); }; - - // If running a node module, set the exports if (typeof module === 'object' && typeof module.exports !== 'undefined') { module.exports = PluginInterface; } +/* Test cases - -/* - * Example usage - */ - - -/* -var modules = new PluginInterface(); - - - -// A plugin -modules.on('irc message', function (event, data) { - //event.wait = true; - setTimeout(event.callback, 2000); +var p = new PluginInterface(); +p.on('test', function (event, data) { + data.a += '!'; + event.callback(); }); +p.emit('test', {a: 'hello world'}).then(function (data) { + if (data.a === 'hello world!') { + console.log('Test passed'); + } else { + console.error('Test failed!'); + } +}, function (err) { + console.error('Test failed!'); +}); - -// Core code that is being extended by plugins -var data = { - nick: 'prawnsalald', - command: '/dothis' -}; - -modules.emit('irc message', data).done(function () { - console.log('Your command is: ' + data.command); +var p = new PluginInterface(); +p.on('test', function (event, data) { + data.a += '!'; + event.callback(); }); -*/ \ No newline at end of file +p.on('test', function (event) { + event.preventDefault('testing'); + event.callback(); +}) + +p.emit('test', {a:'hello world'}).then(function (){ + console.error('Test failed!'); +}, function (data, reasons) { + if ((data.data.a === 'hello world!') && (data.reasons.length === 1 && data.reasons[0] === 'testing')) { + console.log('Test passed'); + } else { + console.error('Test failed!'); + } +}); + +*/ diff --git a/server_modules/example.js b/server_modules/example.js index d880c19..a12f8dd 100644 --- a/server_modules/example.js +++ b/server_modules/example.js @@ -6,22 +6,26 @@ var module = new kiwiModules.Module('Example Module'); // A web client is connected module.on('client created', function(event, data) { console.log('[client connection]', data); + event.callback(); }); // The Client recieves a IRC PRIVMSG command module.on('irc message', function(event, data) { console.log('[MESSAGE]', data.irc_event); + event.callback(); }); // The Client recieves a IRC USER NOTICE command module.on('irc user notice', function(event, data) { console.log('[NOTICE]', data.irc_event); + event.callback(); }); // The client recieves an IRC JOIN command module.on('irc channel join', function(event, data) { console.log('[JOIN]', data.irc_event); + event.callback(); }); @@ -32,4 +36,5 @@ module.on('client command', function(event, data) { console.log('[CLIENT COMMAND]', client_method); console.log(' ', client_args); + event.callback(); }); diff --git a/server_modules/proxychecker.js b/server_modules/proxychecker.js index 8a77749..660defc 100644 --- a/server_modules/proxychecker.js +++ b/server_modules/proxychecker.js @@ -83,4 +83,4 @@ function checkForOpenProxies(host, callback) { .on('timeout', portTimeout) .setTimeout(5000); } -} \ No newline at end of file +} diff --git a/server_modules/stats.js b/server_modules/stats.js index 8055b36..6922c0f 100644 --- a/server_modules/stats.js +++ b/server_modules/stats.js @@ -22,9 +22,11 @@ module.on('stat counter', function (event, event_data) { ignored_events.push('http.request'); if (ignored_events.indexOf(stat_name) > -1) { - return; + return event.callback(); } timestamp = Math.floor((new Date()).getTime() / 1000); stats_file.write(timestamp.toString() + ' ' + stat_name + '\n'); -}); \ No newline at end of file + + event.callback(); +}); -- 2.25.1