Modify PluginInterface to use promises on server
authorJack Allnutt <jack@allnutt.eu>
Thu, 21 Aug 2014 10:54:17 +0000 (11:54 +0100)
committerJack Allnutt <jack@allnutt.eu>
Thu, 21 Aug 2014 11:21:39 +0000 (12:21 +0100)
package.json
server/clientcommands.js
server/irc/channel.js
server/irc/connection.js
server/irc/state.js
server/irc/user.js
server/plugininterface.js
server_modules/example.js
server_modules/proxychecker.js
server_modules/stats.js

index 4a0dfab78a97c3a7c95d143480b5bd5e92e6f9c6..a8b038195aaa8ccc28e25e07898c4a6d4f0eed32 100644 (file)
     "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": {
index e8abdb587a531e5e3624595b577dc19003348715..11375ea6369e32af19b2b9ffa1059078e19a79c4 100644 (file)
@@ -46,15 +46,14 @@ ClientCommands.prototype.addRpcEvents = function(client, rpc) {
             client: client,\r
             connection: connection\r
         })\r
-        .done(function() {\r
+        .then(function() {\r
             // Listeners expect arguments in a (connection, callback, args..n) format, so preppend\r
             // the connection + callback\r
             fn_args = rpc_args.slice(0);\r
             fn_args.unshift(connection, callback);\r
 \r
             the_fn.apply(client, fn_args);\r
-        })\r
-        .prevented(function() {\r
+        }, function() {\r
             // The RPC call was prevented from running by a module\r
         });\r
     };\r
index b95dd1f1c1673e8870d531b4cbee9e01daed66d1..089aefc79d9cef8304ff74ee4791716a9a36a70b 100644 (file)
@@ -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,
index da5f17346ba129b47058e064e01c928d9d0a3d9c..db81fa94a61656290d49cd67e6578965dc85cb71 100644 (file)
@@ -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) {
index 9ae2df031ff0cc3e251013eda573e3f14f7804a2..f1a6d5121211ac05472c844a4687ba86a28e2544 100755 (executable)
@@ -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();
         });
 };
index 082381e13f63695d939bf4f900e2e899a38b1152..9987ad36ba088a96c5972a35effa87f7d5f6f796 100755 (executable)
@@ -234,7 +234,7 @@ function onNotice(event) {
         connection: this.irc_connection,\r
         irc_event: event\r
     })\r
-    .done(function() {\r
+    .then(function() {\r
         that.irc_connection.clientEvent('message', {\r
             type: 'notice',\r
             from_server: event.from_server,\r
@@ -266,7 +266,7 @@ function onPrivmsg(event) {
         connection: this.irc_connection,\r
         irc_event: event\r
     })\r
-    .done(function() {\r
+    .then(function() {\r
         that.irc_connection.clientEvent('message', {\r
             type: 'message',\r
             nick: event.nick,\r
@@ -286,7 +286,7 @@ function onAction(event) {
         connection: this.irc_connection,\r
         irc_event: event\r
     })\r
-    .done(function() {\r
+    .then(function() {\r
         that.irc_connection.clientEvent('message', {\r
             type: 'action',\r
             nick: event.nick,\r
index 070d05d381a12bc6f6250fe1277850dc50548f10..322efa6ad74f499cba9097269558d2a377f7940d 100644 (file)
-/*
- * 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!');
+    }
+});
+
+*/
index d880c1914423709e6d9dbcb469d2eeae5e328ce6..a12f8ddb62b119de689b45502e2282d789e413a5 100644 (file)
@@ -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();
 });
index 8a777490fa5585086cca7135f6b323b02aad2c91..660defc1eb21f65a01a301751b45ea172fd6453b 100644 (file)
@@ -83,4 +83,4 @@ function checkForOpenProxies(host, callback) {
             .on('timeout', portTimeout)
             .setTimeout(5000);
     }
-}
\ No newline at end of file
+}
index 8055b36cec0686484797513fe2954591b8987559..6922c0fab9e4c2c595a74dbf49d389bc5a8987b5 100644 (file)
@@ -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();
+});