UNPKG

nextgen-events

Version:

The next generation of events handling for javascript! New: abstract away the network!

1,743 lines (1,259 loc) 215 kB
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ (function (global){ /* Next Gen Events Copyright (c) 2015 - 2016 Cédric Ronvel The MIT License (MIT) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ "use strict" ; function NextGenEvents() { return Object.create( NextGenEvents.prototype ) ; } module.exports = NextGenEvents ; NextGenEvents.prototype.__prototypeUID__ = 'nextgen-events/NextGenEvents' ; NextGenEvents.prototype.__prototypeVersion__ = require( '../package.json' ).version ; /* Basic features, more or less compatible with Node.js */ NextGenEvents.SYNC = -Infinity ; // Not part of the prototype, because it should not pollute userland's prototype. // It has an eventEmitter as 'this' anyway (always called using call()). NextGenEvents.init = function init() { Object.defineProperty( this , '__ngev' , { configurable: true , value: { nice: NextGenEvents.SYNC , interruptible: false , recursion: 0 , contexts: {} , // States by events states: {} , // State groups by events stateGroups: {} , // Listeners by events listeners: { // Special events error: [] , interrupt: [] , newListener: [] , removeListener: [] } } } ) ; } ; // Use it with .bind() NextGenEvents.filterOutCallback = function( what , currentElement ) { return what !== currentElement ; } ; // .addListener( eventName , [fn] , [options] ) NextGenEvents.prototype.addListener = function addListener( eventName , fn , options ) { var listener = {} , newListenerListeners ; if ( ! this.__ngev ) { NextGenEvents.init.call( this ) ; } if ( ! this.__ngev.listeners[ eventName ] ) { this.__ngev.listeners[ eventName ] = [] ; } if ( ! eventName || typeof eventName !== 'string' ) { throw new TypeError( ".addListener(): argument #0 should be a non-empty string" ) ; } if ( typeof fn !== 'function' ) { options = fn ; fn = undefined ; } if ( ! options || typeof options !== 'object' ) { options = {} ; } listener.fn = fn || options.fn ; listener.id = options.id !== undefined ? options.id : listener.fn ; listener.once = !! options.once ; listener.async = !! options.async ; listener.eventObject = !! options.eventObject ; listener.nice = options.nice !== undefined ? Math.floor( options.nice ) : NextGenEvents.SYNC ; listener.context = typeof options.context === 'string' ? options.context : null ; if ( typeof listener.fn !== 'function' ) { throw new TypeError( ".addListener(): a function or an object with a 'fn' property which value is a function should be provided" ) ; } // Implicit context creation if ( listener.context && typeof listener.context === 'string' && ! this.__ngev.contexts[ listener.context ] ) { this.addListenerContext( listener.context ) ; } // Note: 'newListener' and 'removeListener' event return an array of listener, but not the event name. // So the event's name can be retrieved in the listener itself. listener.event = eventName ; if ( this.__ngev.listeners.newListener.length ) { // Extra care should be taken with the 'newListener' event, we should avoid recursion // in the case that eventName === 'newListener', but inside a 'newListener' listener, // .listenerCount() should report correctly newListenerListeners = this.__ngev.listeners.newListener.slice() ; this.__ngev.listeners[ eventName ].push( listener ) ; // Return an array, because one day, .addListener() may support multiple event addition at once, // e.g.: .addListener( { request: onRequest, close: onClose, error: onError } ) ; NextGenEvents.emitEvent( { emitter: this , name: 'newListener' , args: [ [ listener ] ] , listeners: newListenerListeners } ) ; if ( this.__ngev.states[ eventName ] ) { NextGenEvents.emitToOneListener( this.__ngev.states[ eventName ] , listener ) ; } return this ; } this.__ngev.listeners[ eventName ].push( listener ) ; if ( this.__ngev.states[ eventName ] ) { NextGenEvents.emitToOneListener( this.__ngev.states[ eventName ] , listener ) ; } return this ; } ; NextGenEvents.prototype.on = NextGenEvents.prototype.addListener ; // Shortcut // .once( eventName , [fn] , [options] ) NextGenEvents.prototype.once = function once( eventName , fn , options ) { if ( fn && typeof fn === 'object' ) { fn.once = true ; } else if ( options && typeof options === 'object' ) { options.once = true ; } else { options = { once: true } ; } return this.addListener( eventName , fn , options ) ; } ; NextGenEvents.prototype.removeListener = function removeListener( eventName , id ) { var i , length , newListeners = [] , removedListeners = [] ; if ( ! eventName || typeof eventName !== 'string' ) { throw new TypeError( ".removeListener(): argument #0 should be a non-empty string" ) ; } if ( ! this.__ngev ) { NextGenEvents.init.call( this ) ; } if ( ! this.__ngev.listeners[ eventName ] ) { this.__ngev.listeners[ eventName ] = [] ; } length = this.__ngev.listeners[ eventName ].length ; // It's probably faster to create a new array of listeners for ( i = 0 ; i < length ; i ++ ) { if ( this.__ngev.listeners[ eventName ][ i ].id === id ) { removedListeners.push( this.__ngev.listeners[ eventName ][ i ] ) ; } else { newListeners.push( this.__ngev.listeners[ eventName ][ i ] ) ; } } this.__ngev.listeners[ eventName ] = newListeners ; if ( removedListeners.length && this.__ngev.listeners.removeListener.length ) { this.emit( 'removeListener' , removedListeners ) ; } return this ; } ; NextGenEvents.prototype.off = NextGenEvents.prototype.removeListener ; NextGenEvents.prototype.removeAllListeners = function removeAllListeners( eventName ) { var removedListeners ; if ( ! this.__ngev ) { NextGenEvents.init.call( this ) ; } if ( eventName ) { // Remove all listeners for a particular event if ( ! eventName || typeof eventName !== 'string' ) { throw new TypeError( ".removeAllListeners(): argument #0 should be undefined or a non-empty string" ) ; } if ( ! this.__ngev.listeners[ eventName ] ) { this.__ngev.listeners[ eventName ] = [] ; } removedListeners = this.__ngev.listeners[ eventName ] ; this.__ngev.listeners[ eventName ] = [] ; if ( removedListeners.length && this.__ngev.listeners.removeListener.length ) { this.emit( 'removeListener' , removedListeners ) ; } } else { // Remove all listeners for any events // 'removeListener' listeners cannot be triggered: they are already deleted this.__ngev.listeners = {} ; } return this ; } ; NextGenEvents.listenerWrapper = function listenerWrapper( listener , event , context ) { var returnValue , serial , listenerCallback ; if ( event.interrupt ) { return ; } if ( listener.async ) { //serial = context && context.serial ; if ( context ) { serial = context.serial ; context.ready = ! serial ; } listenerCallback = function( arg ) { event.listenersDone ++ ; // Async interrupt if ( arg && event.emitter.__ngev.interruptible && ! event.interrupt && event.name !== 'interrupt' ) { event.interrupt = arg ; if ( event.callback ) { event.callback( event.interrupt , event ) ; delete event.callback ; } event.emitter.emit( 'interrupt' , event.interrupt ) ; } else if ( event.listenersDone >= event.listeners.length && event.callback ) { event.callback( undefined , event ) ; delete event.callback ; } // Process the queue if serialized if ( serial ) { NextGenEvents.processQueue.call( event.emitter , listener.context , true ) ; } } ; if ( listener.eventObject ) { listener.fn( event , listenerCallback ) ; } else { returnValue = listener.fn.apply( undefined , event.args.concat( listenerCallback ) ) ; } } else { if ( listener.eventObject ) { listener.fn( event ) ; } else { returnValue = listener.fn.apply( undefined , event.args ) ; } event.listenersDone ++ ; } // Interrupt if non-falsy return value, if the emitter is interruptible, not already interrupted (emit once), // and not within an 'interrupt' event. if ( returnValue && event.emitter.__ngev.interruptible && ! event.interrupt && event.name !== 'interrupt' ) { event.interrupt = returnValue ; if ( event.callback ) { event.callback( event.interrupt , event ) ; delete event.callback ; } event.emitter.emit( 'interrupt' , event.interrupt ) ; } else if ( event.listenersDone >= event.listeners.length && event.callback ) { event.callback( undefined , event ) ; delete event.callback ; } } ; // A unique event ID var nextEventId = 0 ; /* emit( [nice] , eventName , [arg1] , [arg2] , [...] , [emitCallback] ) */ NextGenEvents.prototype.emit = function emit() { var event ; event = { emitter: this } ; // Arguments handling if ( typeof arguments[ 0 ] === 'number' ) { event.nice = Math.floor( arguments[ 0 ] ) ; event.name = arguments[ 1 ] ; if ( ! event.name || typeof event.name !== 'string' ) { throw new TypeError( ".emit(): when argument #0 is a number, argument #1 should be a non-empty string" ) ; } if ( typeof arguments[ arguments.length - 1 ] === 'function' ) { event.callback = arguments[ arguments.length - 1 ] ; event.args = Array.prototype.slice.call( arguments , 2 , -1 ) ; } else { event.args = Array.prototype.slice.call( arguments , 2 ) ; } } else { //event.nice = this.__ngev.nice ; event.name = arguments[ 0 ] ; if ( ! event.name || typeof event.name !== 'string' ) { throw new TypeError( ".emit(): argument #0 should be an number or a non-empty string" ) ; } event.args = Array.prototype.slice.call( arguments , 1 ) ; if ( typeof arguments[ arguments.length - 1 ] === 'function' ) { event.callback = arguments[ arguments.length - 1 ] ; event.args = Array.prototype.slice.call( arguments , 1 , -1 ) ; } else { event.args = Array.prototype.slice.call( arguments , 1 ) ; } } return NextGenEvents.emitEvent( event ) ; } ; /* At this stage, 'event' should be an object having those properties: * emitter: the event emitter * name: the event name * args: array, the arguments of the event * nice: (optional) nice value * callback: (optional) a callback for emit * listeners: (optional) override the listeners array stored in __ngev */ NextGenEvents.emitEvent = function emitEvent( event ) { var self = event.emitter , i , iMax , count = 0 , state , removedListeners ; if ( ! self.__ngev ) { NextGenEvents.init.call( self ) ; } state = self.__ngev.states[ event.name ] ; // This is a state event, register it now! if ( state !== undefined ) { if ( state && event.args.length === state.args.length && event.args.every( function( arg , index ) { return arg === state.args[ index ] ; } ) ) { // The emitter is already in this exact state, skip it now! return ; } // Unset all states of that group self.__ngev.stateGroups[ event.name ].forEach( function( eventName ) { self.__ngev.states[ eventName ] = null ; } ) ; self.__ngev.states[ event.name ] = event ; } if ( ! self.__ngev.listeners[ event.name ] ) { self.__ngev.listeners[ event.name ] = [] ; } event.id = nextEventId ++ ; event.listenersDone = 0 ; event.once = !! event.once ; if ( event.nice === undefined || event.nice === null ) { event.nice = self.__ngev.nice ; } // Trouble arise when a listener is removed from another listener, while we are still in the loop. // So we have to COPY the listener array right now! if ( ! event.listeners ) { event.listeners = self.__ngev.listeners[ event.name ].slice() ; } // Increment self.__ngev.recursion self.__ngev.recursion ++ ; removedListeners = [] ; // Emit the event to all listeners! for ( i = 0 , iMax = event.listeners.length ; i < iMax ; i ++ ) { count ++ ; NextGenEvents.emitToOneListener( event , event.listeners[ i ] , removedListeners ) ; } // Decrement recursion self.__ngev.recursion -- ; // Emit 'removeListener' after calling listeners if ( removedListeners.length && self.__ngev.listeners.removeListener.length ) { self.emit( 'removeListener' , removedListeners ) ; } // 'error' event is a special case: it should be listened for, or it will throw an error if ( ! count ) { if ( event.name === 'error' ) { if ( event.args[ 0 ] ) { throw event.args[ 0 ] ; } else { throw Error( "Uncaught, unspecified 'error' event." ) ; } } if ( event.callback ) { event.callback( undefined , event ) ; delete event.callback ; } } return event ; } ; // If removedListeners is not given, then one-time listener emit the 'removeListener' event, // if given: that's the caller business to do it NextGenEvents.emitToOneListener = function emitToOneListener( event , listener , removedListeners ) { var self = event.emitter , context , currentNice , emitRemoveListener = false ; context = listener.context && self.__ngev.contexts[ listener.context ] ; // If the listener context is disabled... if ( context && context.status === NextGenEvents.CONTEXT_DISABLED ) { return ; } // The nice value for this listener... if ( context ) { currentNice = Math.max( event.nice , listener.nice , context.nice ) ; } else { currentNice = Math.max( event.nice , listener.nice ) ; } if ( listener.once ) { // We should remove the current listener RIGHT NOW because of recursive .emit() issues: // one listener may eventually fire this very same event synchronously during the current loop. self.__ngev.listeners[ event.name ] = self.__ngev.listeners[ event.name ].filter( NextGenEvents.filterOutCallback.bind( undefined , listener ) ) ; if ( removedListeners ) { removedListeners.push( listener ) ; } else { emitRemoveListener = true ; } } if ( context && ( context.status === NextGenEvents.CONTEXT_QUEUED || ! context.ready ) ) { // Almost all works should be done by .emit(), and little few should be done by .processQueue() context.queue.push( { event: event , listener: listener , nice: currentNice } ) ; } else { try { if ( currentNice < 0 ) { if ( self.__ngev.recursion >= - currentNice ) { setImmediate( NextGenEvents.listenerWrapper.bind( self , listener , event , context ) ) ; } else { NextGenEvents.listenerWrapper.call( self , listener , event , context ) ; } } else { setTimeout( NextGenEvents.listenerWrapper.bind( self , listener , event , context ) , currentNice ) ; } } catch ( error ) { // Catch error, just to decrement self.__ngev.recursion, re-throw after that... self.__ngev.recursion -- ; throw error ; } } // Emit 'removeListener' after calling the listener if ( emitRemoveListener && self.__ngev.listeners.removeListener.length ) { self.emit( 'removeListener' , [ listener ] ) ; } } ; NextGenEvents.prototype.listeners = function listeners( eventName ) { if ( ! eventName || typeof eventName !== 'string' ) { throw new TypeError( ".listeners(): argument #0 should be a non-empty string" ) ; } if ( ! this.__ngev ) { NextGenEvents.init.call( this ) ; } if ( ! this.__ngev.listeners[ eventName ] ) { this.__ngev.listeners[ eventName ] = [] ; } // Do not return the array, shallow copy it return this.__ngev.listeners[ eventName ].slice() ; } ; NextGenEvents.listenerCount = function( emitter , eventName ) { if ( ! emitter || ! ( emitter instanceof NextGenEvents ) ) { throw new TypeError( ".listenerCount(): argument #0 should be an instance of NextGenEvents" ) ; } return emitter.listenerCount( eventName ) ; } ; NextGenEvents.prototype.listenerCount = function( eventName ) { if ( ! eventName || typeof eventName !== 'string' ) { throw new TypeError( ".listenerCount(): argument #1 should be a non-empty string" ) ; } if ( ! this.__ngev ) { NextGenEvents.init.call( this ) ; } if ( ! this.__ngev.listeners[ eventName ] ) { this.__ngev.listeners[ eventName ] = [] ; } return this.__ngev.listeners[ eventName ].length ; } ; NextGenEvents.prototype.setNice = function setNice( nice ) { if ( ! this.__ngev ) { NextGenEvents.init.call( this ) ; } //if ( typeof nice !== 'number' ) { throw new TypeError( ".setNice(): argument #0 should be a number" ) ; } this.__ngev.nice = Math.floor( +nice || 0 ) ; } ; NextGenEvents.prototype.setInterruptible = function setInterruptible( value ) { if ( ! this.__ngev ) { NextGenEvents.init.call( this ) ; } //if ( typeof nice !== 'number' ) { throw new TypeError( ".setNice(): argument #0 should be a number" ) ; } this.__ngev.interruptible = !! value ; } ; // Make two objects sharing the same event bus NextGenEvents.share = function( source , target ) { if ( ! ( source instanceof NextGenEvents ) || ! ( target instanceof NextGenEvents ) ) { throw new TypeError( 'NextGenEvents.share() arguments should be instances of NextGenEvents' ) ; } if ( ! source.__ngev ) { NextGenEvents.init.call( source ) ; } Object.defineProperty( target , '__ngev' , { configurable: true , value: source.__ngev } ) ; } ; NextGenEvents.reset = function reset( emitter ) { Object.defineProperty( emitter , '__ngev' , { configurable: true , value: null } ) ; } ; // There is no such thing in NextGenEvents, however, we need to be compatible with node.js events at best NextGenEvents.prototype.setMaxListeners = function() {} ; // Sometime useful as a no-op callback... NextGenEvents.noop = function() {} ; /* Next Gen feature: states! */ // .defineStates( exclusiveState1 , [exclusiveState2] , [exclusiveState3] , ... ) NextGenEvents.prototype.defineStates = function defineStates() { var self = this , states = Array.prototype.slice.call( arguments ) ; if ( ! this.__ngev ) { NextGenEvents.init.call( this ) ; } states.forEach( function( state ) { self.__ngev.states[ state ] = null ; self.__ngev.stateGroups[ state ] = states ; } ) ; } ; NextGenEvents.prototype.hasState = function hasState( state ) { if ( ! this.__ngev ) { NextGenEvents.init.call( this ) ; } return !! this.__ngev.states[ state ] ; } ; NextGenEvents.prototype.getAllStates = function getAllStates() { var self = this ; if ( ! this.__ngev ) { NextGenEvents.init.call( this ) ; } return Object.keys( this.__ngev.states ).filter( function( e ) { return self.__ngev.states[ e ] ; } ) ; } ; /* Next Gen feature: groups! */ NextGenEvents.groupAddListener = function groupAddListener( emitters , eventName , fn , options ) { // Manage arguments if ( typeof fn !== 'function' ) { options = fn ; fn = undefined ; } if ( ! options || typeof options !== 'object' ) { options = {} ; } fn = fn || options.fn ; delete options.fn ; // Preserve the listener ID, so groupRemoveListener() will work as expected options.id = options.id || fn ; emitters.forEach( function( emitter ) { emitter.addListener( eventName , fn.bind( undefined , emitter ) , options ) ; } ) ; } ; NextGenEvents.groupOn = NextGenEvents.groupAddListener ; // Once per emitter NextGenEvents.groupOnce = function groupOnce( emitters , eventName , fn , options ) { if ( fn && typeof fn === 'object' ) { fn.once = true ; } else if ( options && typeof options === 'object' ) { options.once = true ; } else { options = { once: true } ; } return this.groupAddListener( emitters , eventName , fn , options ) ; } ; // Globally once, only one event could be emitted, by the first emitter to emit NextGenEvents.groupGlobalOnce = function groupGlobalOnce( emitters , eventName , fn , options ) { var fnWrapper , triggered = false ; // Manage arguments if ( typeof fn !== 'function' ) { options = fn ; fn = undefined ; } if ( ! options || typeof options !== 'object' ) { options = {} ; } fn = fn || options.fn ; delete options.fn ; // Preserve the listener ID, so groupRemoveListener() will work as expected options.id = options.id || fn ; fnWrapper = function() { if ( triggered ) { return ; } triggered = true ; NextGenEvents.groupRemoveListener( emitters , eventName , options.id ) ; fn.apply( undefined , arguments ) ; } ; emitters.forEach( function( emitter ) { emitter.once( eventName , fnWrapper.bind( undefined , emitter ) , options ) ; } ) ; } ; // Globally once, only one event could be emitted, by the last emitter to emit NextGenEvents.groupGlobalOnceAll = function groupGlobalOnceAll( emitters , eventName , fn , options ) { var fnWrapper , triggered = false , count = emitters.length ; // Manage arguments if ( typeof fn !== 'function' ) { options = fn ; fn = undefined ; } if ( ! options || typeof options !== 'object' ) { options = {} ; } fn = fn || options.fn ; delete options.fn ; // Preserve the listener ID, so groupRemoveListener() will work as expected options.id = options.id || fn ; fnWrapper = function() { if ( triggered ) { return ; } if ( -- count ) { return ; } // So this is the last emitter... triggered = true ; // No need to remove listeners: there are already removed anyway //NextGenEvents.groupRemoveListener( emitters , eventName , options.id ) ; fn.apply( undefined , arguments ) ; } ; emitters.forEach( function( emitter ) { emitter.once( eventName , fnWrapper.bind( undefined , emitter ) , options ) ; } ) ; } ; NextGenEvents.groupRemoveListener = function groupRemoveListener( emitters , eventName , id ) { emitters.forEach( function( emitter ) { emitter.removeListener( eventName , id ) ; } ) ; } ; NextGenEvents.groupOff = NextGenEvents.groupRemoveListener ; NextGenEvents.groupRemoveAllListeners = function groupRemoveAllListeners( emitters , eventName ) { emitters.forEach( function( emitter ) { emitter.removeAllListeners( eventName ) ; } ) ; } ; NextGenEvents.groupEmit = function groupEmit( emitters ) { var eventName , nice , argStart = 2 , argEnd , args , count = emitters.length , callback , callbackWrapper , callbackTriggered = false ; if ( typeof arguments[ arguments.length - 1 ] === 'function' ) { argEnd = -1 ; callback = arguments[ arguments.length - 1 ] ; callbackWrapper = function( interruption ) { if ( callbackTriggered ) { return ; } if ( interruption ) { callbackTriggered = true ; callback( interruption ) ; } else if ( ! -- count ) { callbackTriggered = true ; callback() ; } } ; } if ( typeof arguments[ 1 ] === 'number' ) { argStart = 3 ; nice = typeof arguments[ 1 ] ; } eventName = arguments[ argStart - 1 ] ; args = Array.prototype.slice.call( arguments , argStart , argEnd ) ; emitters.forEach( function( emitter ) { NextGenEvents.emitEvent( { emitter: emitter , name: eventName , args: args , nice: nice , callback: callbackWrapper } ) ; } ) ; } ; NextGenEvents.groupDefineStates = function groupDefineStates( emitters ) { var args = Array.prototype.slice.call( arguments , 1 ) ; emitters.forEach( function( emitter ) { emitter.defineStates.apply( emitter , args ) ; } ) ; } ; /* Next Gen feature: contexts! */ NextGenEvents.CONTEXT_ENABLED = 0 ; NextGenEvents.CONTEXT_DISABLED = 1 ; NextGenEvents.CONTEXT_QUEUED = 2 ; NextGenEvents.prototype.addListenerContext = function addListenerContext( contextName , options ) { if ( ! this.__ngev ) { NextGenEvents.init.call( this ) ; } if ( ! contextName || typeof contextName !== 'string' ) { throw new TypeError( ".addListenerContext(): argument #0 should be a non-empty string" ) ; } if ( ! options || typeof options !== 'object' ) { options = {} ; } if ( ! this.__ngev.contexts[ contextName ] ) { // A context IS an event emitter too! this.__ngev.contexts[ contextName ] = Object.create( NextGenEvents.prototype ) ; this.__ngev.contexts[ contextName ].nice = NextGenEvents.SYNC ; this.__ngev.contexts[ contextName ].ready = true ; this.__ngev.contexts[ contextName ].status = NextGenEvents.CONTEXT_ENABLED ; this.__ngev.contexts[ contextName ].serial = false ; this.__ngev.contexts[ contextName ].queue = [] ; } if ( options.nice !== undefined ) { this.__ngev.contexts[ contextName ].nice = Math.floor( options.nice ) ; } if ( options.status !== undefined ) { this.__ngev.contexts[ contextName ].status = options.status ; } if ( options.serial !== undefined ) { this.__ngev.contexts[ contextName ].serial = !! options.serial ; } return this ; } ; NextGenEvents.prototype.disableListenerContext = function disableListenerContext( contextName ) { if ( ! this.__ngev ) { NextGenEvents.init.call( this ) ; } if ( ! contextName || typeof contextName !== 'string' ) { throw new TypeError( ".disableListenerContext(): argument #0 should be a non-empty string" ) ; } if ( ! this.__ngev.contexts[ contextName ] ) { this.addListenerContext( contextName ) ; } this.__ngev.contexts[ contextName ].status = NextGenEvents.CONTEXT_DISABLED ; return this ; } ; NextGenEvents.prototype.enableListenerContext = function enableListenerContext( contextName ) { if ( ! this.__ngev ) { NextGenEvents.init.call( this ) ; } if ( ! contextName || typeof contextName !== 'string' ) { throw new TypeError( ".enableListenerContext(): argument #0 should be a non-empty string" ) ; } if ( ! this.__ngev.contexts[ contextName ] ) { this.addListenerContext( contextName ) ; } this.__ngev.contexts[ contextName ].status = NextGenEvents.CONTEXT_ENABLED ; if ( this.__ngev.contexts[ contextName ].queue.length > 0 ) { NextGenEvents.processQueue.call( this , contextName ) ; } return this ; } ; NextGenEvents.prototype.queueListenerContext = function queueListenerContext( contextName ) { if ( ! this.__ngev ) { NextGenEvents.init.call( this ) ; } if ( ! contextName || typeof contextName !== 'string' ) { throw new TypeError( ".queueListenerContext(): argument #0 should be a non-empty string" ) ; } if ( ! this.__ngev.contexts[ contextName ] ) { this.addListenerContext( contextName ) ; } this.__ngev.contexts[ contextName ].status = NextGenEvents.CONTEXT_QUEUED ; return this ; } ; NextGenEvents.prototype.serializeListenerContext = function serializeListenerContext( contextName , value ) { if ( ! this.__ngev ) { NextGenEvents.init.call( this ) ; } if ( ! contextName || typeof contextName !== 'string' ) { throw new TypeError( ".serializeListenerContext(): argument #0 should be a non-empty string" ) ; } if ( ! this.__ngev.contexts[ contextName ] ) { this.addListenerContext( contextName ) ; } this.__ngev.contexts[ contextName ].serial = value === undefined ? true : !! value ; return this ; } ; NextGenEvents.prototype.setListenerContextNice = function setListenerContextNice( contextName , nice ) { if ( ! this.__ngev ) { NextGenEvents.init.call( this ) ; } if ( ! contextName || typeof contextName !== 'string' ) { throw new TypeError( ".setListenerContextNice(): argument #0 should be a non-empty string" ) ; } if ( ! this.__ngev.contexts[ contextName ] ) { this.addListenerContext( contextName ) ; } this.__ngev.contexts[ contextName ].nice = Math.floor( nice ) ; return this ; } ; NextGenEvents.prototype.destroyListenerContext = function destroyListenerContext( contextName ) { var i , length , eventName , newListeners , removedListeners = [] ; if ( ! contextName || typeof contextName !== 'string' ) { throw new TypeError( ".disableListenerContext(): argument #0 should be a non-empty string" ) ; } if ( ! this.__ngev ) { NextGenEvents.init.call( this ) ; } // We don't care if a context actually exists, all listeners tied to that contextName will be removed for ( eventName in this.__ngev.listeners ) { newListeners = null ; length = this.__ngev.listeners[ eventName ].length ; for ( i = 0 ; i < length ; i ++ ) { if ( this.__ngev.listeners[ eventName ][ i ].context === contextName ) { newListeners = [] ; removedListeners.push( this.__ngev.listeners[ eventName ][ i ] ) ; } else if ( newListeners ) { newListeners.push( this.__ngev.listeners[ eventName ][ i ] ) ; } } if ( newListeners ) { this.__ngev.listeners[ eventName ] = newListeners ; } } if ( this.__ngev.contexts[ contextName ] ) { delete this.__ngev.contexts[ contextName ] ; } if ( removedListeners.length && this.__ngev.listeners.removeListener.length ) { this.emit( 'removeListener' , removedListeners ) ; } return this ; } ; // To be used with .call(), it should not pollute the prototype NextGenEvents.processQueue = function processQueue( contextName , isCompletionCallback ) { var context , job ; // The context doesn't exist anymore, so just abort now if ( ! this.__ngev.contexts[ contextName ] ) { return ; } context = this.__ngev.contexts[ contextName ] ; if ( isCompletionCallback ) { context.ready = true ; } // Should work on serialization here //console.log( ">>> " , context ) ; // Increment recursion this.__ngev.recursion ++ ; while ( context.ready && context.queue.length ) { job = context.queue.shift() ; // This event has been interrupted, drop it now! if ( job.event.interrupt ) { continue ; } try { if ( job.nice < 0 ) { if ( this.__ngev.recursion >= - job.nice ) { setImmediate( NextGenEvents.listenerWrapper.bind( this , job.listener , job.event , context ) ) ; } else { NextGenEvents.listenerWrapper.call( this , job.listener , job.event , context ) ; } } else { setTimeout( NextGenEvents.listenerWrapper.bind( this , job.listener , job.event , context ) , job.nice ) ; } } catch ( error ) { // Catch error, just to decrement this.__ngev.recursion, re-throw after that... this.__ngev.recursion -- ; throw error ; } } // Decrement recursion this.__ngev.recursion -- ; } ; // Backup for the AsyncTryCatch NextGenEvents.on = NextGenEvents.prototype.on ; NextGenEvents.once = NextGenEvents.prototype.once ; NextGenEvents.off = NextGenEvents.prototype.off ; if ( global.AsyncTryCatch ) { NextGenEvents.prototype.asyncTryCatchId = global.AsyncTryCatch.NextGenEvents.length ; global.AsyncTryCatch.NextGenEvents.push( NextGenEvents ) ; if ( global.AsyncTryCatch.substituted ) { //console.log( 'live subsitute' ) ; global.AsyncTryCatch.substitute() ; } } // Load Proxy AT THE END (circular require) NextGenEvents.Proxy = require( './Proxy.js' ) ; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{"../package.json":5,"./Proxy.js":2}],2:[function(require,module,exports){ /* Next Gen Events Copyright (c) 2015 - 2016 Cédric Ronvel The MIT License (MIT) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ "use strict" ; // Create the object && export it function Proxy() { return Proxy.create() ; } module.exports = Proxy ; var NextGenEvents = require( './NextGenEvents.js' ) ; var MESSAGE_TYPE = 'NextGenEvents/message' ; function noop() {} Proxy.create = function create() { var self = Object.create( Proxy.prototype , { localServices: { value: {} , enumerable: true } , remoteServices: { value: {} , enumerable: true } , nextAckId: { value: 1 , writable: true , enumerable: true } , } ) ; return self ; } ; // Add a local service accessible remotely Proxy.prototype.addLocalService = function addLocalService( id , emitter , options ) { this.localServices[ id ] = LocalService.create( this , id , emitter , options ) ; return this.localServices[ id ] ; } ; // Add a remote service accessible locally Proxy.prototype.addRemoteService = function addRemoteService( id ) { this.remoteServices[ id ] = RemoteService.create( this , id ) ; return this.remoteServices[ id ] ; } ; // Destroy the proxy Proxy.prototype.destroy = function destroy() { var self = this ; Object.keys( this.localServices ).forEach( function( id ) { self.localServices[ id ].destroy() ; delete self.localServices[ id ] ; } ) ; Object.keys( this.remoteServices ).forEach( function( id ) { self.remoteServices[ id ].destroy() ; delete self.remoteServices[ id ] ; } ) ; this.receive = this.send = noop ; } ; // Push an event message. Proxy.prototype.push = function push( message ) { if ( message.__type !== MESSAGE_TYPE || ! message.service || typeof message.service !== 'string' || ! message.event || typeof message.event !== 'string' || ! message.method ) { return ; } switch ( message.method ) { // Those methods target a remote service case 'event' : return this.remoteServices[ message.service ] && this.remoteServices[ message.service ].receiveEvent( message ) ; case 'ackEmit' : return this.remoteServices[ message.service ] && this.remoteServices[ message.service ].receiveAckEmit( message ) ; // Those methods target a local service case 'emit' : return this.localServices[ message.service ] && this.localServices[ message.service ].receiveEmit( message ) ; case 'listen' : return this.localServices[ message.service ] && this.localServices[ message.service ].receiveListen( message ) ; case 'ignore' : return this.localServices[ message.service ] && this.localServices[ message.service ].receiveIgnore( message ) ; case 'ackEvent' : return this.localServices[ message.service ] && this.localServices[ message.service ].receiveAckEvent( message ) ; default: return ; } } ; // This is the method to receive and decode data from the other side of the communication channel, most of time another proxy. // In most case, this should be overwritten. Proxy.prototype.receive = function receive( raw ) { this.push( raw ) ; } ; // This is the method used to send data to the other side of the communication channel, most of time another proxy. // This MUST be overwritten in any case. Proxy.prototype.send = function send() { throw new Error( 'The send() method of the Proxy MUST be extended/overwritten' ) ; } ; /* Local Service */ function LocalService( proxy , id , emitter , options ) { return LocalService.create( proxy , id , emitter , options ) ; } Proxy.LocalService = LocalService ; LocalService.create = function create( proxy , id , emitter , options ) { var self = Object.create( LocalService.prototype , { proxy: { value: proxy , enumerable: true } , id: { value: id , enumerable: true } , emitter: { value: emitter , writable: true , enumerable: true } , internalEvents: { value: Object.create( NextGenEvents.prototype ) , writable: true , enumerable: true } , events: { value: {} , enumerable: true } , canListen: { value: !! options.listen , writable: true , enumerable: true } , canEmit: { value: !! options.emit , writable: true , enumerable: true } , canAck: { value: !! options.ack , writable: true , enumerable: true } , canRpc: { value: !! options.rpc , writable: true , enumerable: true } , destroyed: { value: false , writable: true , enumerable: true } , } ) ; return self ; } ; // Destroy a service LocalService.prototype.destroy = function destroy() { var self = this ; Object.keys( this.events ).forEach( function( eventName ) { self.emitter.off( eventName , self.events[ eventName ] ) ; delete self.events[ eventName ] ; } ) ; this.emitter = null ; this.destroyed = true ; } ; // Remote want to emit on the local service LocalService.prototype.receiveEmit = function receiveEmit( message ) { if ( this.destroyed || ! this.canEmit || ( message.ack && ! this.canAck ) ) { return ; } var self = this ; var event = { emitter: this.emitter , name: message.event , args: message.args || [] } ; if ( message.ack ) { event.callback = function ack( interruption ) { self.proxy.send( { __type: MESSAGE_TYPE , service: self.id , method: 'ackEmit' , ack: message.ack , event: message.event , interruption: interruption } ) ; } ; } NextGenEvents.emitEvent( event ) ; } ; // Remote want to listen to an event of the local service LocalService.prototype.receiveListen = function receiveListen( message ) { if ( this.destroyed || ! this.canListen || ( message.ack && ! this.canAck ) ) { return ; } if ( message.ack ) { if ( this.events[ message.event ] ) { if ( this.events[ message.event ].ack ) { return ; } // There is already an event, but not featuring ack, remove that listener now this.emitter.off( message.event , this.events[ message.event ] ) ; } this.events[ message.event ] = LocalService.forwardWithAck.bind( this ) ; this.events[ message.event ].ack = true ; this.emitter.on( message.event , this.events[ message.event ] , { eventObject: true , async: true } ) ; } else { if ( this.events[ message.event ] ) { if ( ! this.events[ message.event ].ack ) { return ; } // Remote want to downgrade: // there is already an event, but featuring ack so we remove that listener now this.emitter.off( message.event , this.events[ message.event ] ) ; } this.events[ message.event ] = LocalService.forward.bind( this ) ; this.events[ message.event ].ack = false ; this.emitter.on( message.event , this.events[ message.event ] , { eventObject: true } ) ; } } ; // Remote do not want to listen to that event of the local service anymore LocalService.prototype.receiveIgnore = function receiveIgnore( message ) { if ( this.destroyed || ! this.canListen ) { return ; } if ( ! this.events[ message.event ] ) { return ; } this.emitter.off( message.event , this.events[ message.event ] ) ; this.events[ message.event ] = null ; } ; // LocalService.prototype.receiveAckEvent = function receiveAckEvent( message ) { if ( this.destroyed || ! this.canListen || ! this.canAck || ! message.ack || ! this.events[ message.event ] || ! this.events[ message.event ].ack ) { return ; } this.internalEvents.emit( 'ack' , message ) ; } ; // Send an event from the local service to remote LocalService.forward = function forward( event ) { if ( this.destroyed ) { return ; } this.proxy.send( { __type: MESSAGE_TYPE , service: this.id , method: 'event' , event: event.name , args: event.args } ) ; } ; LocalService.forward.ack = false ; // Send an event from the local service to remote, with ACK LocalService.forwardWithAck = function forwardWithAck( event , callback ) { if ( this.destroyed ) { return ; } var self = this ; if ( ! event.callback ) { // There is no emit callback, no need to ack this one this.proxy.send( { __type: MESSAGE_TYPE , service: this.id , method: 'event' , event: event.name , args: event.args } ) ; callback() ; return ; } var triggered = false ; var ackId = this.proxy.nextAckId ++ ; var onAck = function onAck( message ) { if ( triggered || message.ack !== ackId ) { return ; } // Not our ack... //if ( message.event !== event ) { return ; } // Do we care? triggered = true ; self.internalEvents.off( 'ack' , onAck ) ; callback() ; } ; this.internalEvents.on( 'ack' , onAck ) ; this.proxy.send( { __type: MESSAGE_TYPE , service: this.id , method: 'event' , event: event.name , ack: ackId , args: event.args } ) ; } ; LocalService.forwardWithAck.ack = true ; /* Remote Service */ function RemoteService( proxy , id ) { return RemoteService.create( proxy , id ) ; } //RemoteService.prototype = Object.create( NextGenEvents.prototype ) ; //RemoteService.prototype.constructor = RemoteService ; Proxy.RemoteService = RemoteService ; var EVENT_NO_ACK = 1 ; var EVENT_ACK = 2 ; RemoteService.create = function create( proxy , id ) { var self = Object.create( RemoteService.prototype , { proxy: { value: proxy , enumerable: true } , id: { value: id , enumerable: true } , // This is the emitter where everything is routed to emitter: { value: Object.create( NextGenEvents.prototype ) , writable: true , enumerable: true } , internalEvents: { value: Object.create( NextGenEvents.prototype ) , writable: true , enumerable: true } , events: { value: {} , enumerable: true } , destroyed: { value: false , writable: true , enumerable: true } , /* Useless for instance, unless some kind of service capabilities discovery mechanism exists canListen: { value: !! options.listen , writable: true , enumerable: true } , canEmit: { value: !! options.emit , writable: true , enumerable: true } , canAck: { value: !! options.ack , writable: true , enumerable: true } , canRpc: { value: !! options.rpc , writable: true , enumerable: true } , */ } ) ; return self ; } ; // Destroy a service RemoteService.prototype.destroy = function destroy() { var self = this ; this.emitter.removeAllListeners() ; this.emitter = null ; Object.keys( this.events ).forEach( function( eventName ) { delete self.events[ eventName ] ; } ) ; this.destroyed = true ; } ; // Local code want to emit to remote service RemoteService.prototype.emit = function emit( eventName ) { if ( this.destroyed ) { return ; } var self = this , args , callback , ackId , triggered ; if ( typeof eventName === 'number' ) { throw new TypeError( 'Cannot emit with a nice value on a remote service' ) ; } if ( typeof arguments[ arguments.length - 1 ] !== 'function' ) { args = Array.prototype.slice.call( arguments , 1 ) ; this.proxy.send( { __type: MESSAGE_TYPE , service: this.id , method: 'emit' , event: eventName , args: args } ) ; return ; } args = Array.prototype.slice.call( arguments , 1 , -1 ) ; callback = arguments[ arguments.length - 1 ] ; ackId = this.proxy.nextAckId ++ ; triggered = false ; var onAck = function onAck( message ) { if ( triggered || message.ack !== ackId ) { return ; } // Not our ack... //if ( message.event !== event ) { return ; } // Do we care? triggered = true ; self.internalEvents.off( 'ack' , onAck ) ; callback( message.interruption ) ; } ; this.internalEvents.on( 'ack' , onAck ) ; this.proxy.send( { __type: MESSAGE_TYPE , service: this.id , method: 'emit' , ack: ackId , event: eventName , args: args } ) ; } ; // Local code want to listen to an event of remote service RemoteService.prototype.addListener = function addListener( eventName , fn , options ) { if ( this.destroyed ) { return ; } // Manage arguments the same way NextGenEvents#addListener() does if ( typeof fn !== 'function' ) { options = fn ; fn = undefined ; } if ( ! options || typeof options !== 'object' ) { options = {} ; } options.fn = fn || options.fn ; this.emitter.addListener( eventName , options ) ; // No event was added... if ( ! this.emitter.__ngev.listeners[ eventName ] || ! this.emitter.__ngev.listeners[ eventName ].length ) { return ; } // If the event is successfully listened to and was not remotely listened... if ( options.async && this.events[ eventName ] !== EVENT_ACK ) { // We need to listen to or upgrade this event this.events[ eventName ] = EVENT_ACK ; this.proxy.send( { __type: MESSAGE_TYPE , service: this.id , method: 'listen' , ack: true , event: eventName } ) ; } else if ( ! options.async && ! this.events[ eventName ] ) { // We need to listen to this event this.events[ eventName ] = EVENT_NO_ACK ; this.proxy.send( { __type: MESSAGE_TYPE , service: this.id , method: 'listen' , event: eventName } ) ; } } ; RemoteService.prototype.on = RemoteService.prototype.addListener ; // This is a shortcut to this.addListener() RemoteService.prototype.once = NextGenEvents.prototype.once ; // Local code want to ignore an event of remote service RemoteService.prototype.removeListener = function removeListener( eventName , id ) { if ( this.destroyed ) { return ; } this.emitter.removeListener( eventName , id ) ; // If no more listener are locally tied to with event and the event was remotely listened... if ( ( ! this.emitter.__ngev.listeners[ eventName ] || ! this.emitter.__ngev.listeners[ eventName ].length ) && this.events[ eventName ] ) { this.events[ eventName ] = 0 ; this.proxy.send( { __type: MESSAGE_TYPE , service: this.id , method: 'ignore' , event: eventName } ) ; } } ; RemoteService.prototype.off = RemoteService.prototype.removeListener ; // A remote service sent an event we are listening to, emit on the service representing the remote RemoteService.prototype.receiveEvent = function receiveEvent( message ) { var self = this ; if ( this.destroyed || ! this.events[ message.event ] ) { return ; } var event = { emitter: this.emitter , name: message.event , args: message.args || [] } ; if ( message.ack ) { event.callback = function ack() { self.proxy.send( { __type: MESSAGE_TYPE , service: self.id , method: 'ackEvent' , ack: message.ack , event: message.event } ) ; } ; } NextGenEvents.emitEvent( event ) ; var eventName = event.name ; // Here we should catch if the event is still listened to ('once' type listeners) //if ( this.events[ eventName ] ) // not needed, already checked at the begining of the function if ( ! this.emitter.__ngev.listeners[ eventName ] || ! this.emitter.__ngev.listeners[ eventName ].length ) { this.events[ eventName ] = 0 ; this.proxy.send( { __type: MESSAGE_TYPE , service: this.id , method: 'ignore' , event: eventName } ) ; } } ; // RemoteService.prototype.receiveAckEmit = function receiveAckEmit( message ) { if ( this.destroyed || ! message.ack || this.events[ message.event ] !== EVENT_ACK ) { return ; } this.internalEvents.emit( 'ack' , message ) ; } ; },{"./NextGenEvents.js":1}],3:[function(require,module,exports){ /* Next Gen Events Copyright (c) 2015 - 2016 Cédric Ronvel The MIT License (MIT) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ "use strict" ; /* global window */ if ( typeof window.setImmediate !== 'function' ) { window.setImmediate = function setImmediate( fn ) { setTimeout( fn , 0 ) ; } ; } module.exports = require( './NextGenEvents.js' ) ; module.exports.isBrowser = true ; },{"./NextGenEvents.js":1}],4:[function(require,module,exports){ (function (Buffer){ (function (global, module) { var exports = module.exports; /** * Exports. */ module.exports = expect; expect.Assertion = Assertion; /** * Exports version. */ expect.version = '0.3.1'; /** * Possible assertion flags. */ var flags = { not: ['to', 'be', 'have', 'include', 'only'] , to: ['be', 'have', 'include', 'only', 'not'] , only: ['have'] , have: ['own'] , be: ['an'] }; function expect (obj)