From 2a89ba11c6ceb4f38ab4261ea3e746a58950f6c4 Mon Sep 17 00:00:00 2001 From: Darren Date: Thu, 15 May 2014 16:14:24 +0100 Subject: [PATCH] Server side PluginInterface ported into the browser --- client/src/app.js | 6 +- client/src/helpers/plugininterface.js | 243 ++++++++++++++++++++++++++ server/plugininterface.js | 172 +++++++++--------- server/settingsgenerator.js | 3 +- 4 files changed, 339 insertions(+), 85 deletions(-) create mode 100644 client/src/helpers/plugininterface.js diff --git a/client/src/app.js b/client/src/app.js index d65887a..748a28b 100644 --- a/client/src/app.js +++ b/client/src/app.js @@ -17,7 +17,8 @@ _kiwi.applets = {}; _kiwi.global = { build_version: '', // Kiwi IRC version this is built from (Set from index.html) settings: undefined, // Instance of _kiwi.model.DataStore - plugins: undefined, + plugins: undefined, // Instance of _kiwi.model.PluginManager + events: undefined, // Instance of PluginInterface utils: {}, // TODO: Re-usable methods addMediaMessageType: function(match, buildHtml) { @@ -116,6 +117,9 @@ _kiwi.global = { // Start the client up _kiwi.app.initializeInterfaces(); + // Event emitter to let plugins interface with parts of kiwi + _kiwi.global.events = new PluginInterface(); + // Now everything has started up, load the plugin manager for third party plugins _kiwi.global.plugins = new _kiwi.model.PluginManager(); diff --git a/client/src/helpers/plugininterface.js b/client/src/helpers/plugininterface.js new file mode 100644 index 0000000..16d3d96 --- /dev/null +++ b/client/src/helpers/plugininterface.js @@ -0,0 +1,243 @@ +/* + * The same functionality as EventEmitter but with the inclusion of callbacks + */ + + + +function PluginInterface () { + // Holder for all the bound listeners by this module + this._listeners = {}; +} + + + +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]; + } + } + } 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]; + } + } + } +}; + + + +// 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; +}; + + + +// 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(); + } + + + + 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](); + } + } + + + + 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(); + + 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; + } + + + return { + callListeners: callListeners, + done: addCompletedFunc, + prevented: addPreventedFunc + }; +}; + + + +// If running a node module, set the exports +if (typeof module === 'object' && typeof module.exports !== 'undefined') { + module.exports = PluginInterface; +} + + + +/* + * Example usage + */ + + +/* +var modules = new PluginInterface(); + + + +// A plugin +modules.on('client:command', function (event, data) { + //event.wait = true; + setTimeout(event.callback, 2000); +}); + + + + +// Core code that is being extended by plugins +var data = { + nick: 'prawnsalald', + command: '/dothis' +}; + + +modules.emit('client:command', data).done(function () { + console.log('Your command is: ' + data.command); +}); +*/ \ No newline at end of file diff --git a/server/plugininterface.js b/server/plugininterface.js index 3a02e39..16d3d96 100644 --- a/server/plugininterface.js +++ b/server/plugininterface.js @@ -2,10 +2,85 @@ * The same functionality as EventEmitter but with the inclusion of callbacks */ -/* - * Promise style object to emit events to listeners - */ -function EmitCall (event_name, event_data) { + + +function PluginInterface () { + // Holder for all the bound listeners by this module + this._listeners = {}; +} + + + +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]; + } + } + } 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]; + } + } + } +}; + + + +// 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; +}; + + + +// Promise style object to emit events to listeners +PluginInterface.prototype.EmitCall = function EmitCall (event_name, event_data) { var that = this, completed = false, completed_fn = [], @@ -18,10 +93,10 @@ function EmitCall (event_name, event_data) { // 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(); @@ -83,11 +158,12 @@ function EmitCall (event_name, event_data) { completed = true; var funcs = prevented ? prevented_fn : completed_fn; + funcs = funcs || []; // Call the completed/prevented functions - (funcs || []).forEach(function (fn) { - if (typeof fn === 'function') fn(); - }); + for (var idx = 0; idx < funcs.length; idx++) { + if (typeof funcs[idx] === 'function') funcs[idx](); + } } @@ -124,84 +200,14 @@ function EmitCall (event_name, event_data) { done: addCompletedFunc, prevented: addPreventedFunc }; -} - - - - - - -function PluginInterface () { - // Holder for all the bound listeners by this module - this._listeners = {}; -} - - -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]; - } - } - } 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]; - } - } - } -}; - - - -// 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 EmitCall(event_name, event_data); - var listeners = this._listeners[event_name] || []; - - // Once emitted, remove any 'once' bound listeners - emitter.done(function () { - listeners.forEach(function (listener, idx) { - if (listener[0] === 'once') { - listeners[idx] = undefined; - } - }); - }); - - // Emit the event to the listeners and return - emitter.callListeners(listeners); - return emitter; -}; - - -module.exports = PluginInterface; +// If running a node module, set the exports +if (typeof module === 'object' && typeof module.exports !== 'undefined') { + module.exports = PluginInterface; +} diff --git a/server/settingsgenerator.js b/server/settingsgenerator.js index e6a3c4a..ce09159 100644 --- a/server/settingsgenerator.js +++ b/server/settingsgenerator.js @@ -221,7 +221,8 @@ function addScripts(vars, debug) { 'src/models/pluginmanager.js', 'src/models/datastore.js', 'src/helpers/utils.js', - 'src/helpers/formatdate.js' + 'src/helpers/formatdate.js', + 'src/helpers/plugininterface.js' ], // Some views extend these, so make sure they're loaded beforehand -- 2.25.1