<commit_before>
(function( factory ){

  if( typeof module !== 'undefined' && typeof module.exports !== 'undefined' ){
  	let ele = require('electron')
  	let ipc = ele.ipcMain
  	let proc = 1
  	let remote = { BrowserWindow : ele.BrowserWindow }
  	if( !ipc ){
  		proc = 0
  		ipc = ele.ipcRenderer
  		remote = ele.remote
  	}
    module.exports = factory( require('eventemitter3'), ipc, remote, require('lodash'), require('node-uuid'), proc )
  }
  else{
  	throw new Error('This module only works on an Electron environment!!')
  }

})(function( EventEmitter, ipc, remote, lo, uuid, proc_side ){

'use strict';

// Constants
const DUP_RCV_HEAD = 'DUPLEX::RCV'
const DUP_SND_HEAD = 'DUPLEX::SND'
const VERBS  = ['GET', 'POST', 'UPDATE', 'DELETE']
const PROC_SIDES = { 'RENDERER': 0, 'MAIN': 1, TR: { 0: 'RENDERER', 1: 'MAIN' } }
const SIDE_EVTS = {
	0: { GET: 'ROUTER_REND_SET_ROUTES', SET: 'ROUTER_REND_GET_ROUTES' },
	1: { GET: 'ROUTER_MAIN_SET_ROUTES', SET: 'ROUTER_MAIN_GET_ROUTES' }
}
// Utils
const DEBUG = function( fn /*, ... */ ){
	let args = Array.prototype.slice.call( arguments, 0 )
	let debugFn = args[ 0 ]
	if(
		global.DEBUG && typeof global.DEBUG === 'string' &&
		(
			global.DEBUG === '*' ||
			-1 !== global.DEBUG.split('|').map(a => a.toLowerCase()).indexOf( debugFn.toLowerCase() )
		)
	){
		console.log.apply( console, args )
	}
}

function _extractEvts( evt, allEvts ){

	let ret = []
	// We are on DUPLEX, when a DUPLEX::RCV:: evt is received we cannot go to DUPLEX::RCV,
	// it can create a infinite loop
	if( -1 !== evt.indexOf( DUP_RCV_HEAD ) ){
		allEvts = allEvts.filter( ev => !!ev.indexOf( DUP_SND_HEAD ) )
	}else if( -1 !== evt.indexOf( DUP_SND_HEAD ) ){
		allEvts = allEvts.filter( ev => !!ev.indexOf( DUP_RCV_HEAD ) )
	}

	DEBUG( '_extractEvts', '\n', 1, 'got...', evt, '_allEvts', allEvts )

	if( -1 !== evt.indexOf('*') && evt !== '*' ){
		// It contains a wildcard, check against all events, convert the wildcard to a whatever `.*`
		let regexp = new RegExp(`^${evt.replace(/\*/g,'.*')}$`, 'i')

		ret = ret.concat(allEvts.filter(ev => regexp.test( ev.replace(/\*/g, '') )))
		DEBUG( '_extractEvts', '\n', 2.2, 'regexp', regexp, 'ret', ret )
		regexp = null

	}else if( evt !== '*' ){

		ret = [ evt ]
		DEBUG( '_extractEvts', '\n', 3, ret )

	}else{

		ret = allEvts
		DEBUG( '_extractEvts', '\n', 4, ret )

	}

	DEBUG( '_extractEvts', '\n', 5, ret )
	// Check events containing wildcards
	ret = ret.concat( allEvts.filter( ev => {
		DEBUG( '_extractEvts', '\n', 6, ev, (new RegExp(`^${ev.replace(/\*/g,'.*')}$`, 'i')).test( evt.replace(/\*/g, '' )) )
		return (new RegExp(`^${ev.replace(/\*/g,'.*')}$`, 'i')).test( evt.replace(/\*/g, '' ))
	}))

	DEBUG( '_extractEvts', '\n', 7, ret, '->', lo.uniq( ret ) )

	return lo.uniq( ret )
}

class Router extends EventEmitter {

	constructor( name, proc ){

		super()

		this._procSide = proc
		this._name = name || this._isRenderProcess()
			?'ROUTER_RENDERER'
			:'ROUTER_PROCESS'

		this.routes = {
			post: 	(...args) => { this.route.apply( this, ['post'].concat( args) ) 	},
			get: 		(...args) => { this.route.apply( this, ['get'].concat( args) ) 		},
			update: (...args) => { this.route.apply( this, ['update'].concat( args) ) },
			delete: (...args) => { this.route.apply( this, ['delete'].concat( args) )	}
		}
		this._setup()

	}

	_setup(){

		if( this._isRenderProcess() ){

			let win = this._getWindow()
			// TODO => Check if this really works,
			// is the close evt triggered on the current window?

			win.on('onbeforeunload', this.clean)

		}

	}

	_getWindows(){

		if( remote.getCurrentWindow ){

			// We are on renderer process
			let id = remote.getCurrentWindow().id
			return remote.BrowserWindow.getAllWindows().filter( w => w.id !== id )

		}else{

			// We are on main process
			return remote.BrowserWindow.getAllWindows()

		}

	}

	_getWindow(){

		if( remote.getCurrentWindow ){

			// We are on renderer process
			return remote.getCurrentWindow()

		}else{

			// We are on main process
			return null

		}

	}

	_isRenderProcess(){

		return remote.hasOwnProperty('getCurrentWindow')

	}

	_common( argss, verb ){

		var args = Array.prototype.slice.call( argss, 0 );

		if( 1 === args.length ){
			throw new Error('Bad arguments, MUST provide a route and a callback')
		}

		let route = args.shift()
		let ctx = args.pop()
		let cb = args.pop()

		if( cb === undefined ){
		 	cb = ctx
			ctx = null
		}

		if( typeof cb !== 'function' ){
			throw new Error('Bad arguments, MUST provide a route and a callback')
		}
		DEBUG( '_common',
			'\n_commonreg', 'on', `${DUP_RCV_HEAD}::${route}::${verb.toUpperCase()}`,
			'\n_commonreg', 'send', `${DUP_SND_HEAD}::${route}::${verb.toUpperCase()}`
		)
		this.on(`${DUP_RCV_HEAD}::${route}::${verb.toUpperCase()}`, (function( callback, router, route, verb ){

			return function( evt ){

				let req = { method: verb, params: Array.prototype.slice.call( evt.data, 0 ) }
				let res = {
					json: function( err, obj ){
						if( arguments.length === 1 ){
							obj = err
							err = null
						}
						DEBUG( '_common', 'On the json end', arguments)
						router.sendDuplexBack.apply( router, [`${DUP_SND_HEAD}::${route}::${verb.toUpperCase()}`, { origEvt: evt.origEvt, count: evt.count, total: evt.total }, err, JSON.parse( JSON.stringify( obj ) )] );
					}
				}

				callback( req, res )
				//callback = router = route = verb = req = res = null

			}

		})( cb, this, route, verb ), ctx)

	}

	on( evt, listener, ctx ){

		// TODO => If event has yet been emitted,
		// do not register => trigger directly, Event Queue
		super.on( evt, listener, ctx )
		if( ipc && ipc.on ){

			ipc.on( evt, function( event ){

				let args = Array.prototype.slice.call( arguments, 1 )
				DEBUG('on', 'inside ipc on', evt, args)
				listener.apply( ctx, args )
				args = event = null

			})

		}

	}

	send( evt ){

		let _evt = evt.trim()
		let _args = Array.prototype.slice.call( arguments, 1 )
		let _allEvts = super.eventNames().concat( lo.difference( this._cache, super.eventNames() ) )
		let _evts = _extractEvts( _evt, _allEvts )
		let _wins = this._getWindows()
		let _winsLen = _wins.length

		let len = _evts.length
		for( let i = 0; i < len; i++ ){

			let msgArr = [ _evts[ i ] ].concat( _args )

			DEBUG( 'send', 'sending...', msgArr, msgArr.length )

			// Emit through eventemitter
			super.emit.apply( this, msgArr )

			// Emit through windows
			for( let j = 0; j < _winsLen; j++ ){

				_wins[ j ].send.apply( _wins[ j ], msgArr )

			}

			// Emit through ipc
			ipc && ipc.send && ipc.send.apply( ipc, msgArr );

		}

		_evt = _args = _evts = _wins = null

	}

	sendDuplex( evt, data ){

		let _evt = evt.trim()
		let _args = Array.prototype.slice.call( data.args, 0 )
		let _origEvt = data.origEvt
		let _allEvts = super.eventNames().concat( lo.difference( this._cache, super.eventNames() ) )
		let _evts = _extractEvts( _evt, _allEvts )
		let _wins = this._getWindows()
		let _winsLen = _wins.length

		let len = _evts.length
		for( let i = 0; i < len; i++ ){

			let msgArr = [ _evts[ i ] ].concat({ origEvt: _origEvt, count: i +1, total: len, data: _args })

			DEBUG( 'sendDuplex', 'sending...', msgArr )

			// Emit through eventemitter
			super.emit.apply( this, msgArr )

			// Emit through windows
			for( let j = 0; j < _winsLen; j++ ){

				_wins[ j ].send.apply( _wins[ j ], msgArr )

			}

			// Emit through ipc
			ipc && ipc.send && ipc.send.apply( ipc, msgArr );

		}

		_evt = _args = _evts = _wins = null

	}

	sendDuplexBack( evt, origEvt, err, data ){

		DEBUG( 'sendDuplexBack', '\n', 0, 'raw', arguments )

		let _evt = origEvt.origEvt
		let _iter = origEvt.count
		let _total = origEvt.total
		let _args = Array.prototype.slice.call( arguments, 2 )
		let _wins = this._getWindows()
		let _winsLen = _wins.length

		let msgArr = [ _evt ].concat({ count: _iter, total: _total, data: _args })

		DEBUG( 'sendDuplexBack', 'sending...', msgArr )
		// Emit through eventemitter
		super.emit.apply( this, msgArr )

		// Emit through windows
		for( let j = 0; j < _winsLen; j++ ){

			_wins[ j ].send.apply( _wins[ j ], msgArr )

		}

		// Emit through ipc
		ipc && ipc.send && ipc.send.apply( ipc, msgArr );

		_evt = _iter = _total = _args = _wins = _winsLen = msgArr = null

	}

	// verb, arg1, arg2, arg3..., callback
	route(){

		let args = Array.prototype.slice.call(arguments, 0);
		if( 3 >  args.length ){
			throw new Error('Bad arguments, MUST provide a route, a method and a callback')
		}
		// Extract verb
		let verb = args.shift().toUpperCase()
		// Extract route
		let route = args.shift()
		let transactionId = uuid()
		let params= { origEvt: `${transactionId}`, args: [] }

		// Extract arguments
		let len = args.length-1;
		let i 	= 0;
		while( i++ < len ){
			params.args.push( args.shift() );
		}

		// Extract callback
		let cb = args.pop()
		let caller = (function( router, uuid, cb ){

			let results = []
			let fn = function fn( data ){

				DEBUG( 'route', 'back fn', arguments, JSON.stringify( data, null, 2 ), results )
				results.push( data.data[ 1 ] )
				// Data from caller comes like data: { data: [ err, result ] }
				// If one errored finish immediately
				// Or we are on the last callback, clean aux routes
				if( data.data[ 0 ] || data.count === data.total ){

					!(data.data[ 0 ]) || ( results = [ data.data[ 0 ] ] )
					data.data[ 0 ] || ( results = [ null, results.length > 1?results:results[0] ] )
					router.removeListener( `${uuid}`, fn )
					DEBUG( 'route', 'back fn', 'data', data, 'sending', results)
					cb.apply( cb, results )

					router = cb = results = null
					clearTimeout( timer )

				}

			}
			// If we are not called back within 200 ms
			// trigger error
			let timer = setTimeout(function() {

				cb.apply( cb, [new Error('Timeout - 200ms elapsed')] )
				router.removeListener( `${uuid}`, fn )
				router = cb = null

			}, 200)

			return fn

		})( this, transactionId, cb )

		this.on(`${transactionId}`, caller)

		DEBUG( 'route', '\non', `${DUP_SND_HEAD}::${route}::${verb}`)
		DEBUG( 'route', '\nsend', `${DUP_RCV_HEAD}::${route}::${verb}`, params)

		this.sendDuplex.apply( this, [`${DUP_RCV_HEAD}::${route}::${verb}`].concat( params ) )

	}

	get(){

		this._common( arguments, 'GET' )

	}

	post(){

		this._common( arguments, 'POST' )

	}

	update(){

		this._common( arguments, 'UPDATE' )
	}

	delete(){

		this._common( arguments, 'DELETE' )

	}

	clean( e ){

		let name = `${this._name.toUpperCase()}::CLOSE`
		DEBUG('clean', 'sending close', name, 'pre', 'events', this.eventNames())
		let wins = this._getWindows()
		ipc && ipc.send && ipc.send( name )
		// Communicate we are closing, clear ipc too?
		wins.forEach( w => { w.send( name ) })
		super.removeAllListeners()
		let win = this._getWindow()
		win && win.removeListener('onbeforeunload', this.clean)
		DEBUG('clean', 'sending close', name, 'post', 'events', this.eventNames())
		// This ensures we do not interfere in the close process
		e && (e.returnValue = undefined)

	}

}

var _router = null

return ( name ) => {
	// TODO => Think on name and window setup (ie: registerWindow??)
	// TODO => Queue of sent events for late on'ss
	if( !_router ){
		_router = new Router( name, proc_side )
	}

	return _router

}

})<commit_msg>Added window destroyed handling<commit_after>
(function( factory ){

  if( typeof module !== 'undefined' && typeof module.exports !== 'undefined' ){
  	let ele = require('electron')
  	let ipc = ele.ipcMain
  	let proc = 1
  	let remote = { BrowserWindow : ele.BrowserWindow }
  	if( !ipc ){
  		proc = 0
  		ipc = ele.ipcRenderer
  		remote = ele.remote
  	}
    module.exports = factory( require('eventemitter3'), ipc, remote, require('lodash'), require('node-uuid'), proc )
  }
  else{
  	throw new Error('This module only works on an Electron environment!!')
  }

})(function( EventEmitter, ipc, remote, lo, uuid, proc_side ){

'use strict';

// Constants
const DUP_RCV_HEAD = 'DUPLEX::RCV'
const DUP_SND_HEAD = 'DUPLEX::SND'
const VERBS  = ['GET', 'POST', 'UPDATE', 'DELETE']
const PROC_SIDES = { 'RENDERER': 0, 'MAIN': 1, TR: { 0: 'RENDERER', 1: 'MAIN' } }
const SIDE_EVTS = {
	0: { GET: 'ROUTER_REND_SET_ROUTES', SET: 'ROUTER_REND_GET_ROUTES' },
	1: { GET: 'ROUTER_MAIN_SET_ROUTES', SET: 'ROUTER_MAIN_GET_ROUTES' }
}
// Utils
const DEBUG = function( fn /*, ... */ ){
	let args = Array.prototype.slice.call( arguments, 0 )
	let debugFn = args[ 0 ]
	if(
		global.DEBUG && typeof global.DEBUG === 'string' &&
		(
			global.DEBUG === '*' ||
			-1 !== global.DEBUG.split('|').map(a => a.toLowerCase()).indexOf( debugFn.toLowerCase() )
		)
	){
		console.log.apply( console, args )
	}
}

function _extractEvts( evt, allEvts ){

	let ret = []
	// We are on DUPLEX, when a DUPLEX::RCV:: evt is received we cannot go to DUPLEX::RCV,
	// it can create a infinite loop
	if( -1 !== evt.indexOf( DUP_RCV_HEAD ) ){
		allEvts = allEvts.filter( ev => !!ev.indexOf( DUP_SND_HEAD ) )
	}else if( -1 !== evt.indexOf( DUP_SND_HEAD ) ){
		allEvts = allEvts.filter( ev => !!ev.indexOf( DUP_RCV_HEAD ) )
	}

	DEBUG( '_extractEvts', '\n', 1, 'got...', evt, '_allEvts', allEvts )

	if( -1 !== evt.indexOf('*') && evt !== '*' ){
		// It contains a wildcard, check against all events, convert the wildcard to a whatever `.*`
		let regexp = new RegExp(`^${evt.replace(/\*/g,'.*')}$`, 'i')

		ret = ret.concat(allEvts.filter(ev => regexp.test( ev.replace(/\*/g, '') )))
		DEBUG( '_extractEvts', '\n', 2.2, 'regexp', regexp, 'ret', ret )
		regexp = null

	}else if( evt !== '*' ){

		ret = [ evt ]
		DEBUG( '_extractEvts', '\n', 3, ret )

	}else{

		ret = allEvts
		DEBUG( '_extractEvts', '\n', 4, ret )

	}

	DEBUG( '_extractEvts', '\n', 5, ret )
	// Check events containing wildcards
	ret = ret.concat( allEvts.filter( ev => {
		DEBUG( '_extractEvts', '\n', 6, ev, (new RegExp(`^${ev.replace(/\*/g,'.*')}$`, 'i')).test( evt.replace(/\*/g, '' )) )
		return (new RegExp(`^${ev.replace(/\*/g,'.*')}$`, 'i')).test( evt.replace(/\*/g, '' ))
	}))

	DEBUG( '_extractEvts', '\n', 7, ret, '->', lo.uniq( ret ) )

	return lo.uniq( ret )
}

class Router extends EventEmitter {

	constructor( name, proc ){

		super()

		this._procSide = proc
		this._name = name || this._isRenderProcess()
			?'ROUTER_RENDERER'
			:'ROUTER_PROCESS'

		this.routes = {
			post: 	(...args) => { this.route.apply( this, ['post'].concat( args) ) 	},
			get: 		(...args) => { this.route.apply( this, ['get'].concat( args) ) 		},
			update: (...args) => { this.route.apply( this, ['update'].concat( args) ) },
			delete: (...args) => { this.route.apply( this, ['delete'].concat( args) )	}
		}
		this._setup()

	}

	_setup(){

		if( this._isRenderProcess() ){

			let win = this._getWindow()
			// TODO => Check if this really works,
			// is the close evt triggered on the current window?

			win.on('onbeforeunload', this.clean)

		}

	}

	_getWindows(){

		if( remote.getCurrentWindow ){

			// We are on renderer process
			let id = remote.getCurrentWindow().id
			return remote.BrowserWindow.getAllWindows().filter( w => w.id !== id )

		}else{

			// We are on main process
			return remote.BrowserWindow.getAllWindows()

		}

	}

	_getWindow(){

		if( remote.getCurrentWindow ){

			// We are on renderer process
			return remote.getCurrentWindow()

		}else{

			// We are on main process
			return null

		}

	}

	_isRenderProcess(){

		return remote.hasOwnProperty('getCurrentWindow')

	}

	_common( argss, verb ){

		var args = Array.prototype.slice.call( argss, 0 );

		if( 1 === args.length ){
			throw new Error('Bad arguments, MUST provide a route and a callback')
		}

		let route = args.shift()
		let ctx = args.pop()
		let cb = args.pop()

		if( cb === undefined ){
		 	cb = ctx
			ctx = null
		}

		if( typeof cb !== 'function' ){
			throw new Error('Bad arguments, MUST provide a route and a callback')
		}
		DEBUG( '_common',
			'\n_commonreg', 'on', `${DUP_RCV_HEAD}::${route}::${verb.toUpperCase()}`,
			'\n_commonreg', 'send', `${DUP_SND_HEAD}::${route}::${verb.toUpperCase()}`
		)
		this.on(`${DUP_RCV_HEAD}::${route}::${verb.toUpperCase()}`, (function( callback, router, route, verb ){

			return function( evt ){

				let req = { method: verb, params: Array.prototype.slice.call( evt.data, 0 ) }
				let res = {
					json: function( err, obj ){
						if( arguments.length === 1 ){
							obj = err
							err = null
						}
						DEBUG( '_common', 'On the json end', arguments)
						router.sendDuplexBack.apply( router, [`${DUP_SND_HEAD}::${route}::${verb.toUpperCase()}`, { origEvt: evt.origEvt, count: evt.count, total: evt.total }, err, JSON.parse( JSON.stringify( obj ) )] );
					}
				}

				callback( req, res )
				//callback = router = route = verb = req = res = null

			}

		})( cb, this, route, verb ), ctx)

	}

	on( evt, listener, ctx ){

		// TODO => If event has yet been emitted,
		// do not register => trigger directly, Event Queue
		super.on( evt, listener, ctx )
		if( ipc && ipc.on ){

			ipc.on( evt, function( event ){

				let args = Array.prototype.slice.call( arguments, 1 )
				DEBUG('on', 'inside ipc on', evt, args)
				listener.apply( ctx, args )
				args = event = null

			})

		}

	}

	send( evt ){

		let _evt = evt.trim()
		let _args = Array.prototype.slice.call( arguments, 1 )
		let _allEvts = super.eventNames().concat( lo.difference( this._cache, super.eventNames() ) )
		let _evts = _extractEvts( _evt, _allEvts )
		let _wins = this._getWindows()
		let _winsLen = _wins.length

		let len = _evts.length
		for( let i = 0; i < len; i++ ){

			let msgArr = [ _evts[ i ] ].concat( _args )

			DEBUG( 'send', 'sending...', msgArr, msgArr.length )

			// Emit through eventemitter
			super.emit.apply( this, msgArr )

			// Emit through windows
			for( let j = 0; j < _winsLen; j++ ){
				// we can be overwritten while sending...
				if( -1 !== this._getWindows().indexOf( _wins[ j ] ) ) _wins[ j ].send.apply( _wins[ j ], msgArr )

			}

			// Emit through ipc
			ipc && ipc.send && ipc.send.apply( ipc, msgArr );

		}

		_evt = _args = _evts = _wins = null

	}

	sendDuplex( evt, data ){

		let _evt = evt.trim()
		let _args = Array.prototype.slice.call( data.args, 0 )
		let _origEvt = data.origEvt
		let _allEvts = super.eventNames().concat( lo.difference( this._cache, super.eventNames() ) )
		let _evts = _extractEvts( _evt, _allEvts )
		let _wins = this._getWindows()
		let _winsLen = _wins.length

		let len = _evts.length
		for( let i = 0; i < len; i++ ){

			let msgArr = [ _evts[ i ] ].concat({ origEvt: _origEvt, count: i +1, total: len, data: _args })

			DEBUG( 'sendDuplex', 'sending...', msgArr )

			// Emit through eventemitter
			super.emit.apply( this, msgArr )

			// Emit through windows
			for( let j = 0; j < _winsLen; j++ ){
				// we can be overwritten while sending...
				if( -1 !== this._getWindows().indexOf( _wins[ j ] ) ) _wins[ j ].send.apply( _wins[ j ], msgArr )

			}

			// Emit through ipc
			ipc && ipc.send && ipc.send.apply( ipc, msgArr );

		}

		_evt = _args = _evts = _wins = null

	}

	sendDuplexBack( evt, origEvt, err, data ){

		DEBUG( 'sendDuplexBack', '\n', 0, 'raw', arguments )

		let _evt = origEvt.origEvt
		let _iter = origEvt.count
		let _total = origEvt.total
		let _args = Array.prototype.slice.call( arguments, 2 )
		let _wins = this._getWindows()
		let _winsLen = _wins.length

		let msgArr = [ _evt ].concat({ count: _iter, total: _total, data: _args })

		DEBUG( 'sendDuplexBack', 'sending...', msgArr )
		// Emit through eventemitter
		super.emit.apply( this, msgArr )

		// Emit through windows
		for( let j = 0; j < _winsLen; j++ ){
			// we can be overwritten while sending...
			if( -1 !== this._getWindows().indexOf( _wins[ j ] ) ) _wins[ j ].send.apply( _wins[ j ], msgArr )

		}

		// Emit through ipc
		ipc && ipc.send && ipc.send.apply( ipc, msgArr );

		_evt = _iter = _total = _args = _wins = _winsLen = msgArr = null

	}

	// verb, arg1, arg2, arg3..., callback
	route(){

		let args = Array.prototype.slice.call(arguments, 0);
		if( 3 >  args.length ){
			throw new Error('Bad arguments, MUST provide a route, a method and a callback')
		}
		// Extract verb
		let verb = args.shift().toUpperCase()
		// Extract route
		let route = args.shift()
		let transactionId = uuid()
		let params= { origEvt: `${transactionId}`, args: [] }

		// Extract arguments
		let len = args.length-1;
		let i 	= 0;
		while( i++ < len ){
			params.args.push( args.shift() );
		}

		// Extract callback
		let cb = args.pop()
		let caller = (function( router, uuid, cb ){

			let results = []
			let fn = function fn( data ){

				DEBUG( 'route', 'back fn', arguments, JSON.stringify( data, null, 2 ), results )
				results.push( data.data[ 1 ] )
				// Data from caller comes like data: { data: [ err, result ] }
				// If one errored finish immediately
				// Or we are on the last callback, clean aux routes
				if( data.data[ 0 ] || data.count === data.total ){

					!(data.data[ 0 ]) || ( results = [ data.data[ 0 ] ] )
					data.data[ 0 ] || ( results = [ null, results.length > 1?results:results[0] ] )
					router.removeListener( `${uuid}`, fn )
					DEBUG( 'route', 'back fn', 'data', data, 'sending', results)
					cb.apply( cb, results )

					router = cb = results = null
					clearTimeout( timer )

				}

			}
			// If we are not called back within 200 ms
			// trigger error
			let timer = setTimeout(function() {

				cb.apply( cb, [new Error('Timeout - 200ms elapsed')] )
				router.removeListener( `${uuid}`, fn )
				router = cb = null

			}, 200)

			return fn

		})( this, transactionId, cb )

		this.on(`${transactionId}`, caller)

		DEBUG( 'route', '\non', `${DUP_SND_HEAD}::${route}::${verb}`)
		DEBUG( 'route', '\nsend', `${DUP_RCV_HEAD}::${route}::${verb}`, params)

		this.sendDuplex.apply( this, [`${DUP_RCV_HEAD}::${route}::${verb}`].concat( params ) )

	}

	get(){

		this._common( arguments, 'GET' )

	}

	post(){

		this._common( arguments, 'POST' )

	}

	update(){

		this._common( arguments, 'UPDATE' )
	}

	delete(){

		this._common( arguments, 'DELETE' )

	}

	clean( e ){

		let name = `${this._name.toUpperCase()}::CLOSE`
		DEBUG('clean', 'sending close', name, 'pre', 'events', this.eventNames())
		let wins = this._getWindows()
		ipc && ipc.send && ipc.send( name )
		// Communicate we are closing, clear ipc too?
		wins.forEach( w => { w.send( name ) })
		super.removeAllListeners()
		let win = this._getWindow()
		win && win.removeListener('onbeforeunload', this.clean)
		DEBUG('clean', 'sending close', name, 'post', 'events', this.eventNames())
		// This ensures we do not interfere in the close process
		e && (e.returnValue = undefined)

	}

}

var _router = null

return ( name ) => {
	// TODO => Think on name and window setup (ie: registerWindow??)
	// TODO => Queue of sent events for late on'ss
	if( !_router ){
		_router = new Router( name, proc_side )
	}

	return _router

}

})<|endoftext|>


<commit_before>(function() {
  // Adding a trailing space for ease of combining with other selectors.
  var TOP_SELECTOR = '#new-integration ';

  var Draft = draftFactory();
  var Finalization = finalizationFactory('finalize-integration');
  var RecipientListChoice = filteredChoiceFactory({
    id: 'choice-recipient-list',
    configuredServiceId: function() {
      return GmailAccountChoice.val();
    },
    nextElement: Finalization
  });
  var GmailAccountChoice = choiceFactory({
    id: 'choice-gmail-account',
    type: 'radio',
    nextElement: RecipientListChoice
  });
  var GmailChoices = choiceListFactory({
    id: 'choices-gmail',
    children: [GmailAccountChoice, RecipientListChoice]
  });
  var DataDestinationServiceChoice = serviceChoiceFactory({
    choiceId: 'choice-data-destination-service',
    serviceId: 'service-gmail',
    nextChoices: GmailChoices
  });
  var DataDestinationChoices = choiceListFactory({
    id: 'choices-data-destination',
    children: [DataDestinationServiceChoice, GmailChoices]
  });
  var MessageChoice = choiceFactory({
    id: 'choice-message',
    nextElement: DataDestinationChoices,
    hidden: true
  });
  var PatternChoice = choiceFactory({
    id: 'choice-pattern',
    nextElement: MessageChoice
  });
  var AlertChoices = choiceListFactory({
    id: 'choices-alert',
    children: [PatternChoice, MessageChoice]
  });
  var FormChoice = filteredChoiceFactory({
    id: 'choice-form',
    configuredServiceId: function() {
      return ServerChoice.val();
    },
    nextElement: AlertChoices
  });
  var ServerChoice = choiceFactory({
    id: 'choice-server',
    type: 'radio',
    nextElement: FormChoice
  });
  var ODKChoices = choiceListFactory({
    id: 'choices-odk',
    children: [ServerChoice, FormChoice]
  });
  var DataSourceServiceChoice = serviceChoiceFactory({
    choiceId: 'choice-data-source-service',
    serviceId: 'service-odk',
    nextChoices: ODKChoices
  });
  var DataSourceChoices = listenerFactory([
    DataSourceServiceChoice,
    ODKChoices
  ]);
  var CongenialEnigma = listenerFactory([
    DataSourceChoices,
    AlertChoices,
    DataDestinationChoices,
    Draft
  ]);
  CongenialEnigma.listen();
  return;

  /* ------------------------------------------------------------------------ */
                    /* UI helper functions */
  /* ------------------------------------------------------------------------ */

  // Activates a step title (for example, "1 Choose a Data Source") and
  // deactivates the others. $descendant is an element nested under the step.
  function activateStepTitle($descendant) {
    var $h4 = $descendant.closest('tr').find('h4');
    if ($h4.hasClass('text-muted')) {
      $(TOP_SELECTOR + '> tbody > tr h4').addClass('text-muted');
      $h4.removeClass('text-muted');
    }
  }

  /* ------------------------------------------------------------------------ */
                    /* component factories */
  /* ------------------------------------------------------------------------ */

  // Returns an object that listens by calling other listeners.
  function listenerFactory(listeners) {
    return {
      listen: function() {
        listeners.forEach(function(listener) {
          listener.listen();
        });
      }
    };
  }

  // nextChoices must be a choice list.
  function serviceChoiceFactory(options) {
    return {
      nextElement: function() {
        return options.nextChoices;
      },
      hasDraft: function() {
        return options.nextChoices.children[0].hasDraft();
      },
      restoreDraft: function() {
        var draft = this.hasDraft();
        if (draft)
          options.nextChoices.show();
        return draft;
      },
      listen: function() {
        // Adding a trailing space for ease of combining with other selectors.
        var choiceSelector = TOP_SELECTOR + '#' + options.choiceId + ' ';
        var serviceSelector = TOP_SELECTOR + '#' + options.serviceId + ' ';
        var otherServicesSelector = choiceSelector +
          '.service:not(#' + options.serviceId + ') ';
        $(document)
          .on('click', serviceSelector, function(e) {
            e.preventDefault();
            $(otherServicesSelector).fadeTo('slow', 0, function() {
              $(this).remove();
              options.nextChoices.show();
            });
            $(serviceSelector).fadeTo('slow', 1);
          })
          .on('click', otherServicesSelector, function(e) {
            e.preventDefault();
            options.nextChoices.hide();
            activateStepTitle($(choiceSelector));
            var id = '#' + $(this).attr('id');
            $(choiceSelector + '.service:not(' + id + ')').fadeTo('fast', 0.5);
            $(id).fadeTo('fast', 1);
          });
      }
    };
  }

  /*
  choiceListFactory() returns a "choice list," which groups together one or more
  other choices and/or choice lists. Think of a choice list as a tree, where
  choice lists are branches and choices are leaves. Options:

    id        HTML id of the choice list element
    children  Array of the choices and choice lists contained in the list
              (immediate children only)
  */
  function choiceListFactory(options) {
    var lastChild = options.children[options.children.length - 1];
    var $choices, hidden;
    return {
      children: options.children,
      nextElement: function() {
        return lastChild.nextElement();
      },
      state: function() {
        if (hidden)
          return 'hidden';
        return lastChild.state() === 'complete' ? 'complete' : 'active';
      },
      hasDraft: function() {
        return options.children[0].hasDraft();
      },
      restoreDraft: function() {
        for (var i = 0; i < options.children.length; i++) {
          if (!options.children[i].restoreDraft())
            return false;
        }
        return true;
      },
      show: function() {
        $choices.show();
        hidden = false;
        if (this.state() === 'complete')
          this.nextElement().show();
        else
          activateStepTitle($choices);
        hidden = false;
      },
      hide: function() {
        this.nextElement().hide();
        $choices.hide();
        hidden = true;
      },
      listen: function() {
        $(document).on('turbolinks:load', function() {
          $choices = $('#' + options.id);
          hidden = true;
        });
        options.children.forEach(function(child) {
          child.listen();
        });
      }
    };
  }

  function choiceFactory(options) {
    var $panel, state;
    var choice = {
      nextElement: nextElement,
      state: state,
      hasDraft: hasDraft,
      restoreDraft: restoreDraft,
      complete: complete,
      revisit: revisit,
      listen: listen
    };
    if (options.type === 'radio')
      choice.val = valRadio;
    if (options.hidden) {
      choice.show = show;
      choice.hide = hide;
    }
    return choice;

    function nextElement() {
      return options.nextElement;
    }

    function state() {
      return state;
    }

    function hasDraft() {
      return $panel.data('hasDraft');
    }

    function restoreDraft() {
      var draft = hasDraft();
      if (draft)
        complete(true);
      return draft;
    }

    // Activates select radio buttons:
    //
    //   1. Shows radio buttons except those with a `filtered` class
    //   2. Selects/checks the first visible, enabled radio button
    //
    function activateRadio() {
      $panel.find('.radio.filtered').hide();
      var $visibleRadio = $panel.find('.radio:not(.filtered)');
      var $enabledRadio = $visibleRadio
                             .find('input:not(.permanently-disabled)')
                             .closest('.radio');
      if ($enabledRadio.length > 0) {
        $enabledRadio.removeClass('disabled');
        if ($enabledRadio.find(':checked').length === 0)
          $enabledRadio.find('input').first().prop('checked', true);
        $panel.find('.action-complete').show();
      }
      $visibleRadio.show();
    }

    /*
    Activates the panel by making the following changes:

      1. Invokes the beforeActivate callback
      2. Activates the step title under which the panel is nested
      3. Unmutes text, including the title
      4. Enables inputs except those with a permanently-disabled class
      5. Activates select radio buttons
      6. Hides the revisit action, shows the add action, and shows the complete
         action if appropriate
    */
    function activate() {
      if (state === 'active')
        return;
      if (options.beforeActivate)
        options.beforeActivate($panel);

      activateStepTitle($panel);
      $panel.removeClass('text-muted');
      $panel.find('.panel-title span').removeClass('text-muted');
      var $input = $panel.find('input, select, textarea');
      $input.filter(':not(.permanently-disabled)').prop('disabled', false);
      if ($panel.find('form:not(.form-paragraph)').length > 0)
        activateRadio();
      else
        $panel.find('.action-complete').show();
      $panel.find('.action-add').show();
      $panel.find('.action-revisit').hide();
    }

    function show() {
      if (state !== 'hidden')
        return;
      activate();
      $panel.show();
      state = 'active';
    }

    function hide() {
      if (state === 'hidden')
        return;
      nextElement().hide();
      $panel.hide();
      state = 'hidden';
    }

    function saveDraft() {
      var data = {};
      var selector = 'input[type="radio"]:checked, input[type!="radio"], ' +
        'select, textarea';
      $panel.find(selector).each(function() {
        var $el = $(this);
        var name = $el.data('draftName') || $el.attr('name');
        data[name] = $el.val();
      });
      $.post('/integrations/save_draft', data);
    }

    /*
    Updates the panel's UI after it has been completed:

      1. Mutes text, including the title
      2. Disables inputs
      3. Hides unchecked radio buttons
      4. Hides the add and complete actions and shows the revisit action
    */
    function updateUIAfterComplete() {
      $panel.addClass('text-muted');
      $panel.find('.panel-title span').addClass('text-muted');
      $panel.find('input[type="radio"]:not(:checked)').closest('.radio').hide();
      $panel.find('input, select, textarea').prop('disabled', true);
      $panel.find(':checked').closest('.radio').addClass('disabled');
      $panel.find('.action-add, .action-complete').hide();
      $panel.find('.action-revisit').show();
    }

    function complete(skipDraftSave) {
      if (state !== 'active')
        return;
      if (!skipDraftSave)
        saveDraft();
      updateUIAfterComplete();
      state = 'complete';
      nextElement().show();
    }

    function valRadio() {
      return $panel.find(':checked').val();
    }

    function revisit() {
      if (state !== 'complete')
        return;
      nextElement().hide();
      activate();
      state = 'active';
    }

    function listen() {
      // Adding a trailing space for ease of combining with other selectors.
      var selector = TOP_SELECTOR + '#' + options.id + ' ';
      $(document)
        .on('turbolinks:load', function() {
          $panel = $(selector);
          state = options.hidden ? 'hidden' : 'active';
        })
        .on('submit', selector, function(e) {
          e.preventDefault();
          complete();
        })
        .on('click', selector + '.action-revisit', function(e) {
          e.preventDefault();
          revisit();
        });
    }
  }

  function filteredChoiceFactory(options) {
    var filteredOptions = {
      type: 'radio',
      hidden: true,
      beforeActivate: beforeActivate
    };
    var combinedOptions = $.extend({}, options, filteredOptions);
    return choiceFactory(combinedOptions);

    function updateAddPath($choice) {
      var $addAction = $choice.find('.action-add');
      var path = $addAction
                   .data('path')
                   .replace('0', options.configuredServiceId());
      $addAction.find('a').prop('href', path);
    }

    function filterRadio($choice) {
      var $radio = $choice.find('.radio');
      $radio.addClass('filtered');
      var selector = '[data-configured-service-id="' +
        options.configuredServiceId() + '"]';
      $radio.filter(selector).removeClass('filtered');
    }

    function beforeActivate($choice) {
      updateAddPath($choice);
      filterRadio($choice);
    }
  }

  function finalizationFactory(id) {
    var selector = '#' + id;
    return {
      show: show,
      hide: hide
    };

    function updatePushUrl() {
      var $pushUrl = $('#push-url');
      var url = $pushUrl
                  .data('url')
                  .replace('/0', '/' + ServerChoice.val())
                  .replace('/1', '/' + FormChoice.val());
      $pushUrl.text(url);
    }

    function copyFields() {
      $('#data_source_id').val(FormChoice.val());
      $('#rule_data_field_name').val($('#field_name').val());
      $('#rule_type').val($('#rule_class').val());
      $('#rule_data_value').val($('#rule_value').val());
      $('#message').val($('#alert_message').val());
      $('#data_destination_id').val(RecipientListChoice.val());
    }

    function show() {
      updatePushUrl();
      copyFields();
      activateStepTitle($('#choices-data-destination'));
      $(selector).show();
    }

    function hide() {
      $(selector).hide();
    }
  }

  function draftFactory() {
    return {
      listen: function() {
        $(document).on('turbolinks:load', restoreDraft);
      }
    };

    function restoreDraft() {
      for(var el = DataSourceServiceChoice;
          el && el.restoreDraft && el.restoreDraft();
          el = el.nextElement())
        ;
    }
  }
})();
<commit_msg>Tweak draft<commit_after>(function() {
  // Adding a trailing space for ease of combining with other selectors.
  var TOP_SELECTOR = '#new-integration ';

  var Finalization = finalizationFactory('finalize-integration');
  var RecipientListChoice = filteredChoiceFactory({
    id: 'choice-recipient-list',
    configuredServiceId: function() {
      return GmailAccountChoice.val();
    },
    nextElement: Finalization
  });
  var GmailAccountChoice = choiceFactory({
    id: 'choice-gmail-account',
    type: 'radio',
    nextElement: RecipientListChoice
  });
  var GmailChoices = choiceListFactory({
    id: 'choices-gmail',
    children: [GmailAccountChoice, RecipientListChoice]
  });
  var DataDestinationServiceChoice = serviceChoiceFactory({
    choiceId: 'choice-data-destination-service',
    serviceId: 'service-gmail',
    nextChoices: GmailChoices
  });
  var DataDestinationChoices = choiceListFactory({
    id: 'choices-data-destination',
    children: [DataDestinationServiceChoice, GmailChoices]
  });
  var MessageChoice = choiceFactory({
    id: 'choice-message',
    nextElement: DataDestinationChoices,
    hidden: true
  });
  var PatternChoice = choiceFactory({
    id: 'choice-pattern',
    nextElement: MessageChoice
  });
  var AlertChoices = choiceListFactory({
    id: 'choices-alert',
    children: [PatternChoice, MessageChoice]
  });
  var FormChoice = filteredChoiceFactory({
    id: 'choice-form',
    configuredServiceId: function() {
      return ServerChoice.val();
    },
    nextElement: AlertChoices
  });
  var ServerChoice = choiceFactory({
    id: 'choice-server',
    type: 'radio',
    nextElement: FormChoice
  });
  var ODKChoices = choiceListFactory({
    id: 'choices-odk',
    children: [ServerChoice, FormChoice]
  });
  var DataSourceServiceChoice = serviceChoiceFactory({
    choiceId: 'choice-data-source-service',
    serviceId: 'service-odk',
    nextChoices: ODKChoices
  });
  var DataSourceChoices = choiceListFactory({
    id: 'choices-data-source',
    children: [DataSourceServiceChoice, ODKChoices]
  });
  var Draft = draftFactory({ firstElement: DataSourceChoices });
  var CongenialEnigma = listenerFactory([
    DataSourceChoices,
    AlertChoices,
    DataDestinationChoices,
    Draft
  ]);
  CongenialEnigma.listen();
  return;

  /* ------------------------------------------------------------------------ */
                    /* UI helper functions */
  /* ------------------------------------------------------------------------ */

  // Activates a step title (for example, "1 Choose a Data Source") and
  // deactivates the others. $descendant is an element nested under the step.
  function activateStepTitle($descendant) {
    var $h4 = $descendant.closest('tr').find('h4');
    if ($h4.hasClass('text-muted')) {
      $(TOP_SELECTOR + '> tbody > tr h4').addClass('text-muted');
      $h4.removeClass('text-muted');
    }
  }

  /* ------------------------------------------------------------------------ */
                    /* component factories */
  /* ------------------------------------------------------------------------ */

  // Returns an object that listens by calling other listeners.
  function listenerFactory(listeners) {
    return {
      listen: function() {
        listeners.forEach(function(listener) {
          listener.listen();
        });
      }
    };
  }

  // nextChoices must be a choice list.
  function serviceChoiceFactory(options) {
    return {
      nextElement: function() {
        return options.nextChoices;
      },
      hasDraft: function() {
        return options.nextChoices.children[0].hasDraft();
      },
      restoreDraft: function() {
        var draft = this.hasDraft();
        if (draft)
          options.nextChoices.show();
        return draft;
      },
      listen: function() {
        // Adding a trailing space for ease of combining with other selectors.
        var choiceSelector = TOP_SELECTOR + '#' + options.choiceId + ' ';
        var serviceSelector = TOP_SELECTOR + '#' + options.serviceId + ' ';
        var otherServicesSelector = choiceSelector +
          '.service:not(#' + options.serviceId + ') ';
        $(document)
          .on('click', serviceSelector, function(e) {
            e.preventDefault();
            $(otherServicesSelector).fadeTo('slow', 0, function() {
              $(this).remove();
              options.nextChoices.show();
            });
            $(serviceSelector).fadeTo('slow', 1);
          })
          .on('click', otherServicesSelector, function(e) {
            e.preventDefault();
            options.nextChoices.hide();
            activateStepTitle($(choiceSelector));
            var id = '#' + $(this).attr('id');
            $(choiceSelector + '.service:not(' + id + ')').fadeTo('fast', 0.5);
            $(id).fadeTo('fast', 1);
          });
      }
    };
  }

  /*
  choiceListFactory() returns a "choice list," which groups together one or more
  other choices and/or choice lists. Think of a choice list as a tree, where
  choice lists are branches and choices are leaves. Options:

    id        HTML id of the choice list element
    children  Array of the choices and choice lists contained in the list
              (immediate children only)
  */
  function choiceListFactory(options) {
    var lastChild = options.children[options.children.length - 1];
    var $choices, hidden;
    return {
      children: options.children,
      nextElement: function() {
        return lastChild.nextElement();
      },
      state: function() {
        if (hidden)
          return 'hidden';
        return lastChild.state() === 'complete' ? 'complete' : 'active';
      },
      hasDraft: function() {
        return options.children[0].hasDraft();
      },
      restoreDraft: function() {
        for (var i = 0; i < options.children.length; i++) {
          if (!options.children[i].restoreDraft())
            return false;
        }
        return true;
      },
      show: function() {
        $choices.show();
        hidden = false;
        if (this.state() === 'complete')
          this.nextElement().show();
        else
          activateStepTitle($choices);
        hidden = false;
      },
      hide: function() {
        this.nextElement().hide();
        $choices.hide();
        hidden = true;
      },
      listen: function() {
        $(document).on('turbolinks:load', function() {
          $choices = $('#' + options.id);
          hidden = true;
        });
        options.children.forEach(function(child) {
          child.listen();
        });
      }
    };
  }

  function choiceFactory(options) {
    var $panel, state;
    var choice = {
      nextElement: nextElement,
      state: state,
      hasDraft: hasDraft,
      restoreDraft: restoreDraft,
      complete: complete,
      revisit: revisit,
      listen: listen
    };
    if (options.type === 'radio')
      choice.val = valRadio;
    if (options.hidden) {
      choice.show = show;
      choice.hide = hide;
    }
    return choice;

    function nextElement() {
      return options.nextElement;
    }

    function state() {
      return state;
    }

    function hasDraft() {
      return $panel.data('hasDraft');
    }

    function restoreDraft() {
      var draft = hasDraft();
      if (draft)
        complete(true);
      return draft;
    }

    // Activates select radio buttons:
    //
    //   1. Shows radio buttons except those with a `filtered` class
    //   2. Selects/checks the first visible, enabled radio button
    //
    function activateRadio() {
      $panel.find('.radio.filtered').hide();
      var $visibleRadio = $panel.find('.radio:not(.filtered)');
      var $enabledRadio = $visibleRadio
                             .find('input:not(.permanently-disabled)')
                             .closest('.radio');
      if ($enabledRadio.length > 0) {
        $enabledRadio.removeClass('disabled');
        if ($enabledRadio.find(':checked').length === 0)
          $enabledRadio.find('input').first().prop('checked', true);
        $panel.find('.action-complete').show();
      }
      $visibleRadio.show();
    }

    /*
    Activates the panel by making the following changes:

      1. Invokes the beforeActivate callback
      2. Activates the step title under which the panel is nested
      3. Unmutes text, including the title
      4. Enables inputs except those with a permanently-disabled class
      5. Activates select radio buttons
      6. Hides the revisit action, shows the add action, and shows the complete
         action if appropriate
    */
    function activate() {
      if (state === 'active')
        return;
      if (options.beforeActivate)
        options.beforeActivate($panel);

      activateStepTitle($panel);
      $panel.removeClass('text-muted');
      $panel.find('.panel-title span').removeClass('text-muted');
      var $input = $panel.find('input, select, textarea');
      $input.filter(':not(.permanently-disabled)').prop('disabled', false);
      if ($panel.find('form:not(.form-paragraph)').length > 0)
        activateRadio();
      else
        $panel.find('.action-complete').show();
      $panel.find('.action-add').show();
      $panel.find('.action-revisit').hide();
    }

    function show() {
      if (state !== 'hidden')
        return;
      activate();
      $panel.show();
      state = 'active';
    }

    function hide() {
      if (state === 'hidden')
        return;
      nextElement().hide();
      $panel.hide();
      state = 'hidden';
    }

    function saveDraft() {
      var data = {};
      var selector = 'input[type="radio"]:checked, input[type!="radio"], ' +
        'select, textarea';
      $panel.find(selector).each(function() {
        var $el = $(this);
        var name = $el.data('draftName') || $el.attr('name');
        data[name] = $el.val();
      });
      $.post('/integrations/save_draft', data);
    }

    /*
    Updates the panel's UI after it has been completed:

      1. Mutes text, including the title
      2. Disables inputs
      3. Hides unchecked radio buttons
      4. Hides the add and complete actions and shows the revisit action
    */
    function updateUIAfterComplete() {
      $panel.addClass('text-muted');
      $panel.find('.panel-title span').addClass('text-muted');
      $panel.find('input[type="radio"]:not(:checked)').closest('.radio').hide();
      $panel.find('input, select, textarea').prop('disabled', true);
      $panel.find(':checked').closest('.radio').addClass('disabled');
      $panel.find('.action-add, .action-complete').hide();
      $panel.find('.action-revisit').show();
    }

    function complete(skipDraftSave) {
      if (state !== 'active')
        return;
      if (!skipDraftSave)
        saveDraft();
      updateUIAfterComplete();
      state = 'complete';
      nextElement().show();
    }

    function valRadio() {
      return $panel.find(':checked').val();
    }

    function revisit() {
      if (state !== 'complete')
        return;
      nextElement().hide();
      activate();
      state = 'active';
    }

    function listen() {
      // Adding a trailing space for ease of combining with other selectors.
      var selector = TOP_SELECTOR + '#' + options.id + ' ';
      $(document)
        .on('turbolinks:load', function() {
          $panel = $(selector);
          state = options.hidden ? 'hidden' : 'active';
        })
        .on('submit', selector, function(e) {
          e.preventDefault();
          complete();
        })
        .on('click', selector + '.action-revisit', function(e) {
          e.preventDefault();
          revisit();
        });
    }
  }

  function filteredChoiceFactory(options) {
    var filteredOptions = {
      type: 'radio',
      hidden: true,
      beforeActivate: beforeActivate
    };
    var combinedOptions = $.extend({}, options, filteredOptions);
    return choiceFactory(combinedOptions);

    function updateAddPath($choice) {
      var $addAction = $choice.find('.action-add');
      var path = $addAction
                   .data('path')
                   .replace('0', options.configuredServiceId());
      $addAction.find('a').prop('href', path);
    }

    function filterRadio($choice) {
      var $radio = $choice.find('.radio');
      $radio.addClass('filtered');
      var selector = '[data-configured-service-id="' +
        options.configuredServiceId() + '"]';
      $radio.filter(selector).removeClass('filtered');
    }

    function beforeActivate($choice) {
      updateAddPath($choice);
      filterRadio($choice);
    }
  }

  function finalizationFactory(id) {
    var selector = '#' + id;
    return {
      show: show,
      hide: hide
    };

    function updatePushUrl() {
      var $pushUrl = $('#push-url');
      var url = $pushUrl
                  .data('url')
                  .replace('/0', '/' + ServerChoice.val())
                  .replace('/1', '/' + FormChoice.val());
      $pushUrl.text(url);
    }

    function copyFields() {
      $('#data_source_id').val(FormChoice.val());
      $('#rule_data_field_name').val($('#field_name').val());
      $('#rule_type').val($('#rule_class').val());
      $('#rule_data_value').val($('#rule_value').val());
      $('#message').val($('#alert_message').val());
      $('#data_destination_id').val(RecipientListChoice.val());
    }

    function show() {
      updatePushUrl();
      copyFields();
      activateStepTitle($('#choices-data-destination'));
      $(selector).show();
    }

    function hide() {
      $(selector).hide();
    }
  }

  function draftFactory(options) {
    return {
      listen: function() {
        $(document).on('turbolinks:load', function() {
          for (var element = options.firstElement;
               element && element.restoreDraft && element.restoreDraft();
               element = element.nextElement())
            ;
        });
      }
    };
  }
})();
<|endoftext|>


<commit_before>var httpProxy = require('http-proxy');
var http = require('http');

var proxy = httpProxy.createProxy();

var routes = {
        // Router
        'hook.labeli.org' : 'http://localhost:9000',
        // Api
        'api.labeli.org' : 'http://localhost:9010',
        'hook.labeli.org' : 'http://localhost:9011',
        // Website
        'next.labeli.org' : 'http://localhost:9020',
        'hook.next.labeli.org' : 'http://localhost:9021',
};

var router = http.createServer(function(req, res)
{
    proxy.web(req, res, {target : routes[req.headers.host]});
});

router.listen(80);<commit_msg>Fix route for the api's hook<commit_after>var httpProxy = require('http-proxy');
var http = require('http');

var proxy = httpProxy.createProxy();

var routes = {
        // Router
        'hook.labeli.org' : 'http://localhost:9000',
        // Api
        'api.labeli.org' : 'http://localhost:9010',
        'hook.api.labeli.org' : 'http://localhost:9011',
        // Website
        'next.labeli.org' : 'http://localhost:9020',
        'hook.next.labeli.org' : 'http://localhost:9021',
};

var router = http.createServer(function(req, res)
{
    proxy.web(req, res, {target : routes[req.headers.host]});
});

router.listen(80);<|endoftext|>


<commit_before>//= require ./discourse/helpers/i18n_helpers
//= require ./discourse/mixins/ajax
//= require ./discourse

// Pagedown customizations
//= require ./pagedown_custom.js

//= require highlight.pack.js

// Stuff we need to load first
//= require ./discourse/mixins/scrolling
//= require_tree ./discourse/mixins
//= require ./discourse/lib/markdown
//= require ./discourse/lib/computed
//= require ./discourse/views/view
//= require ./discourse/views/container
//= require ./discourse/lib/debounce
//= require ./discourse/models/model
//= require ./discourse/models/user_action
//= require ./discourse/models/composer
//= require ./discourse/models/topic
//= require ./discourse/controllers/controller
//= require ./discourse/controllers/object_controller
//= require ./discourse/controllers/navigation/default
//= require ./discourse/views/modal_body_view
//= require ./discourse/views/flag
//= require ./discourse/views/combo-box
//= require ./discourse/views/button
//= require ./discourse/views/dropdown-button
//= require ./discourse/views/notifications-button
//= require ./discourse/views/topic-notifications-button
//= require ./discourse/views/pagedown-preview
//= require ./discourse/routes/discourse_route
//= require ./discourse/routes/discourse_restricted_user_route
//= require ./discourse/components/text-field

//= require ./discourse/dialects/dialect
//= require_tree ./discourse/dialects
//= require_tree ./discourse/controllers
//= require_tree ./discourse/lib
//= require_tree ./discourse/models
//= require_tree ./discourse/components
//= require_tree ./discourse/views
//= require_tree ./discourse/helpers
//= require_tree ./discourse/templates
//= require_tree ./discourse/routes
//= require_tree ./discourse/initializers

<commit_msg>FIX: Compatibility layer load order<commit_after>//= require ./discourse/helpers/i18n_helpers
//= require ./discourse/mixins/ajax
//= require ./discourse

// Pagedown customizations
//= require ./pagedown_custom.js

//= require highlight.pack.js

// Stuff we need to load first
//= require ./discourse/mixins/scrolling
//= require_tree ./discourse/mixins
//= require ./discourse/lib/markdown
//= require ./discourse/lib/computed
//= require ./discourse/views/view
//= require ./discourse/views/container
//= require ./discourse/lib/debounce
//= require ./discourse/models/model
//= require ./discourse/models/user_action
//= require ./discourse/models/composer
//= require ./discourse/models/topic
//= require ./discourse/controllers/controller
//= require ./discourse/controllers/object_controller
//= require ./discourse/controllers/navigation/default
//= require ./discourse/views/modal_body_view
//= require ./discourse/views/flag
//= require ./discourse/views/combo-box
//= require ./discourse/views/button
//= require ./discourse/views/dropdown-button
//= require ./discourse/views/notifications-button
//= require ./discourse/views/topic-notifications-button
//= require ./discourse/views/pagedown-preview
//= require ./discourse/routes/discourse_route
//= require ./discourse/routes/build-topic-route
//= require ./discourse/routes/discourse_restricted_user_route
//= require ./discourse/components/text-field

//= require ./discourse/dialects/dialect
//= require_tree ./discourse/dialects
//= require_tree ./discourse/controllers
//= require_tree ./discourse/lib
//= require_tree ./discourse/models
//= require_tree ./discourse/components
//= require_tree ./discourse/views
//= require_tree ./discourse/helpers
//= require_tree ./discourse/templates
//= require_tree ./discourse/routes
//= require_tree ./discourse/initializers

<|endoftext|>


<commit_before>"use strict"
const describe   = require('mocha').describe;
const it         = require('mocha').it
const expect     = require('chai').expect
var Glow       = require('../../build/glow-v1.0.0').Glow

var hex_colours = ['#09C', '#EBEBEB', '#640c5a', '#ea5896', '#359ea7', '#FFF', '#000']

describe('hexToRgb', function () {

  it('should convert hex to rgb values', function () {
    let glow = new Glow()
    let rgb = glow.hexToRgb('#359ea7')
    expect(rgb).to.deep.equal([53, 158, 167])
  })

  it('should expand 3 digit hex values', function () {
    let glow = new Glow()
    let expanded = glow.stringToHex('#09C')
    expect(expanded).to.deep.equal('0099CC')
  })

  it('should not expand 6 digit hex values', function () {
    let glow = new Glow()
    let expanded = glow.stringToHex('#0099CC')
    expect(expanded).to.deep.equal('0099CC')
  })

  it('should throw an error if no hex value given', function () {
    let glow = new Glow()
    var fn = function () {
      glow.hexToRgb('ZZZ')
    }
    expect(fn).to.throw()
  })

});

describe('rgbToHsl', function () {

  it('should convert hex to hsl values', function () {
    let glow = new Glow()
    let hex = glow.hexToHsl('#EBEBEB')
    expect(hex).to.deep.equal([0, 0, 92])
//    hex = glow.hexToHsl('#640c5a')
//    expect(hex).to.deep.equal([0, 0, 92])
  })

  it('should convert hsl to rbg values', function () {
    let glow = new Glow()
    let rgb = glow.hslToRgb([0, 0, 92])
    expect(rgb).to.deep.equal([235, 235, 235])
  })

});

describe('rgbToHex', function () {

  it('should convert rgb to hex values', function () {
    let glow = new Glow()
    let hex = glow.rgbToHex([235, 235, 235])
    expect(hex).to.equal('EBEBEB')
  })

});

describe('hslToHex', function () {

  it('should convert hsl to hex', function () {
    let glow = new Glow()
    let hex = glow.hslToHex([0, 0, 92])
    expect(hex).to.equal('EBEBEB')
  })

});
<commit_msg>update glow reference in tests<commit_after>"use strict"
const describe   = require('mocha').describe;
const it         = require('mocha').it
const expect     = require('chai').expect
var Glow       = require('../../build/glow').Glow

var hex_colours = ['#09C', '#EBEBEB', '#640c5a', '#ea5896', '#359ea7', '#FFF', '#000']

describe('hexToRgb', function () {

  it('should convert hex to rgb values', function () {
    let glow = new Glow()
    let rgb = glow.hexToRgb('#359ea7')
    expect(rgb).to.deep.equal([53, 158, 167])
  })

  it('should expand 3 digit hex values', function () {
    let glow = new Glow()
    let expanded = glow.stringToHex('#09C')
    expect(expanded).to.deep.equal('0099CC')
  })

  it('should not expand 6 digit hex values', function () {
    let glow = new Glow()
    let expanded = glow.stringToHex('#0099CC')
    expect(expanded).to.deep.equal('0099CC')
  })

  it('should throw an error if no hex value given', function () {
    let glow = new Glow()
    var fn = function () {
      glow.hexToRgb('ZZZ')
    }
    expect(fn).to.throw()
  })

});

describe('rgbToHsl', function () {

  it('should convert hex to hsl values', function () {
    let glow = new Glow()
    let hex = glow.hexToHsl('#EBEBEB')
    expect(hex).to.deep.equal([0, 0, 92])
//    hex = glow.hexToHsl('#640c5a')
//    expect(hex).to.deep.equal([0, 0, 92])
  })

  it('should convert hsl to rbg values', function () {
    let glow = new Glow()
    let rgb = glow.hslToRgb([0, 0, 92])
    expect(rgb).to.deep.equal([235, 235, 235])
  })

});

describe('rgbToHex', function () {

  it('should convert rgb to hex values', function () {
    let glow = new Glow()
    let hex = glow.rgbToHex([235, 235, 235])
    expect(hex).to.equal('EBEBEB')
  })

});

describe('hslToHex', function () {

  it('should convert hsl to hex', function () {
    let glow = new Glow()
    let hex = glow.hslToHex([0, 0, 92])
    expect(hex).to.equal('EBEBEB')
  })

});
<|endoftext|>


<commit_before>/**
 * Module dependencies.
 */
var oauth2orize = require('oauth2orize')
  , passport = require('passport')
  , login = require('connect-ensure-login')
  , db = require('./db')
  , utils = require('./utils')
  , scrypt = require('scrypt');

// create OAuth 2.0 server
var server = oauth2orize.createServer();
var ttl = 3600 * 24 * 30; //made their access_token valid for 30 days

// Register serialialization and deserialization functions.
//
// When a client redirects a user to user authorization endpoint, an
// authorization transaction is initiated.  To complete the transaction, the
// user must authenticate and approve the authorization request.  Because this
// may involve multiple HTTP request/response exchanges, the transaction is
// stored in the session.
//
// An application must supply serialization functions, which determine how the
// client object is serialized into the session.  Typically this will be a
// simple matter of serializing the client's ID, and deserializing by finding
// the client by ID from the database.

server.serializeClient(function(client, done) {
  return done(null, client.id);
});

server.deserializeClient(function(id, done) {
  db.clients.find(id, function(err, client) {
    if (err) { return done(err); }
    return done(null, client);
  });
});

// Register supported grant types.
//
// OAuth 2.0 specifies a framework that allows users to grant client
// applications limited access to their protected resources.  It does this
// through a process of the user granting access, and the client exchanging
// the grant for an access token.

// Grant authorization codes.  The callback takes the `client` requesting
// authorization, the `redirectURI` (which is used as a verifier in the
// subsequent exchange), the authenticated `user` granting access, and
// their response, which contains approved scope, duration, etc. as parsed by
// the application.  The application issues a code, which is bound to these
// values, and will be exchanged for an access token.

server.grant(oauth2orize.grant.code(function(client, redirectURI, user, ares, done) {
  var code = utils.uid(16);

  db.authorizationCodes.save(code, client.id, redirectURI, user.id, function(err) {
    if (err) { return done(err); }
    done(null, code);
  });
}));

// Grant implicit authorization.  The callback takes the `client` requesting
// authorization, the authenticated `user` granting access, and
// their response, which contains approved scope, duration, etc. as parsed by
// the application.  The application issues a token, which is bound to these
// values.

server.grant(oauth2orize.grant.token(function(client, user, ares, done) {
    utils.uid(256, function(err, token){
        if(err){ return done(err);}
        utils.uid(256, function(err, refreshToken){
            if(err){ return done(err);}
            var scope = [];
            var expires = new Date();
            expires.setSeconds(expires.getSeconds() + ttl);

            db.accessTokens.save(token, refreshToken, user.id, client.clientId, scope, expires, function(err) {
                if (err) { return done(err); }
                done(null, token, refreshToken, {scope: scope, expires: expires});
            });
        });
    });
}));

// Exchange authorization codes for access tokens.  The callback accepts the
// `client`, which is exchanging `code` and any `redirectURI` from the
// authorization request for verification.  If these values are validated, the
// application issues an access token on behalf of the user who authorized the
// code.

server.exchange(oauth2orize.exchange.code(function(client, code, redirectURI, done) {
  db.authorizationCodes.find(code, function(err, authCode) {
    if (err) { return done(err); }
    if (authCode === null) { return done(null, false); }
    if (client.id !== authCode.clientID) { return done(null, false); }
    if (redirectURI !== authCode.redirectURI) { return done(null, false); }

    utils.uid(256, function(err, token) {
        if (err) {
            return done(err);
        }
        utils.uid(256, function (err, refreshToken) {
            if (err) {
                return done(err);
            }
            var scope = [];
            var expires = new Date();
            expires.setSeconds(expires.getSeconds() + ttl);

            db.accessTokens.save(token, refreshToken, authCode.userID, authCode.clientID, scope, expires, function (err) {
                if (err) {
                    return done(err);
                }
                done(null, token, refreshToken, {scope: scope, expires: expires});
            });
        });
    });
  });
}));

// Exchange user id and password for access tokens.  The callback accepts the
// `client`, which is exchanging the user's name and password from the
// authorization request for verification. If these values are validated, the
// application issues an access token on behalf of the user who authorized the code.

server.exchange(oauth2orize.exchange.password(function(client, username, password, scope, done) {
    console.info(" in password exchange (client:" + client + ", username:" + username + ", password:" + password + ", scope:" + scope + ")");

    //Validate the client
    db.clients.findByClientId(client.clientId, function(err, localClient) {
        if (err) { return done(err); }
        if(localClient === null) {
            return done(null, false);
        }
        if(localClient.clientSecret !== client.clientSecret) {
            return done(null, false);
        }
        //Validate the user
        db.users.findByUsername(username, function(err, user) {
            if (err) { return done(err); }
            if(user === null) {
                return done(null, false);
            }
            if (!scrypt.verifyKdfSync(new Buffer(user.password, 'base64'), password)) {
                return done(null, false);
            }
            //Everything validated, return the token

            utils.uid(256, function(err, token) {
                if (err) {
                    return done(err);
                }
                utils.uid(256, function (err, refreshToken) {
                    if (err) {
                        return done(err);
                    }
                    if (typeof scope == 'undefined'){
                        scope = [];
                    }
                    var expires = new Date();
                    expires.setSeconds(expires.getSeconds() + ttl);

                    db.accessTokens.save(token, refreshToken, user.id, client.clientId, scope, expires, function (err) {
                        if (err) {
                            return done(err);
                        } else {
                            return done(null, token, refreshToken, {'scope': scope, 'expires': expires});
                        }
                    });
                });
            });
        });
    });
}));

// Exchange the client id and password/secret for an access token.  The callback accepts the
// `client`, which is exchanging the client's id and password/secret from the
// authorization request for verification. If these values are validated, the
// application issues an access token on behalf of the client who authorized the code.

server.exchange(oauth2orize.exchange.clientCredentials(function(client, scope, done) {
    console.log("in clientCredentials exchange (client:" + client + ", scope:" + scope + ")");

    //Validate the client
    db.clients.findByClientId(client.clientId, function(err, localClient) {
        if (err) { return done(err); }
        if(localClient === null) {
            return done(null, false);
        }
        if(localClient.clientSecret !== client.clientSecret) {
        //if(!scrypt.verifyKdfSync(new Buffer(localClient.clientSecret, 'base64'), new Buffer(client.clientSecret, 'base64'))){
            return done(null, false);
        }

        utils.uid(256, function(err, token) {
            if (err) {
                return done(err);
            }
            utils.uid(256, function (err, refreshToken) {
                if (err) {
                    return done(err);
                }
                //var scope = [];
                var expires = new Date();
                expires.setSeconds(expires.getSeconds() + ttl);
                //Pass in a null for user id since there is no user with this grant type
                db.accessTokens.save(token, refreshToken, null, client.clientId, scope, expires, function (err) {
                    if (err) {
                        return done(err);
                    }
                    done(null, token, refreshToken, {scope: scope, expires: expires});
                });
            });
        });
    });
}));

/*
 * Refresh token exchange
 * https://github.com/IGZangelsanchez/oauth2orize-example-extended/blob/master/server/oauth2.js
 */

server.exchange(oauth2orize.exchange.refreshToken(function(client, refreshToken, scope, done) {
    console.log("* Refresh access token client[", client, "] refreshToken[" + refreshToken + "] scope[" + scope + "]");

    db.accessTokens.findRefresh(refreshToken, function (err, accessTokenToRefresh) {
        console.log('find refresh token err[' + err + '] accessToenToRefresh[' + JSON.stringify(accessTokenToRefresh) + ']');
        if (err) { return done(err); }
        if (accessTokenToRefresh === null) { return done(new Error("Refresh token not found."));}
        // if (accessTokenToRefresh.expires < new Date()) {
        //     return done(new Error("Refresh Token has expired."));
        // }

        console.log("- refresh token valid");

        db.accessTokens.find(accessTokenToRefresh.accessToken, function (err, accessTokenDB) {
            console.log('find accessToken results: err', err, accessTokenDB);
            if (err) { return done(err); }
            if (accessTokenDB == null) {
                return done(new Error("Access Token not found."));
            }
            // if (accessTokenDB.expires < new Date()) {
            //     return done(new Error("Access Token has expired."));
            // }

            console.log("- old access token found");

            utils.uid(256, function(err, token) {
                if (err) {
                    return done(err);
                }
                utils.uid(256, function (err, refreshToken) {
                    if (err) {
                        return done(err);
                    }
                    var expires = new Date();
                    expires.setSeconds(expires.getSeconds() + ttl);

                    db.accessTokens.save(token, refreshToken, accessTokenDB.userId, accessTokenDB.clientId, accessTokenDB.scopes, expires, function (err) {
                        if (err) {
                            return done(err);
                        }

                        console.log("- new access/refresh token saved");

                        db.accessTokens.delete(accessTokenToRefresh.accessToken, function (err) {
                            if (err) {
                                return done(err);
                            }

                            console.log("- old access token deleted");

                            done(null, token, refreshToken, {scope: accessTokenDB.scope, 'expires': expires});
                        });
                    });
                });
            });
        });
    });
}));

// user authorization endpoint
//
// `authorization` middleware accepts a `validate` callback which is
// responsible for validating the client making the authorization request.  In
// doing so, is recommended that the `redirectURI` be checked against a
// registered value, although security requirements may vary accross
// implementations.  Once validated, the `done` callback must be invoked with
// a `client` instance, as well as the `redirectURI` to which the user will be
// redirected after an authorization decision is obtained.
//
// This middleware simply initializes a new authorization transaction.  It is
// the application's responsibility to authenticate the user and render a dialog
// to obtain their approval (displaying details about the client requesting
// authorization).  We accomplish that here by routing through `ensureLoggedIn()`
// first, and rendering the `dialog` view.

exports.authorization = [
  login.ensureLoggedIn(),
  server.authorization(function(clientID, redirectURI, done) {
    db.clients.findByClientId(clientID, function(err, client) {
      if (err) { return done(err); }
      // WARNING: For security purposes, it is highly advisable to check that
      //          redirectURI provided by the client matches one registered with
      //          the server.  For simplicity, this example does not.  You have
      //          been warned.
        //escape special chars
      if(!redirectURI.match(new RegExp("^" + escapeRegExp(client.redirectURI) + ".*$"))){
          return done(new Error("redirect URI did not match."));
      }
      return done(null, client, redirectURI);
    });
  }),
  function(req, res){
    res.render('dialog', { transactionID: req.oauth2.transactionID, user: req.user, client: req.oauth2.client });
  }
];

function escapeRegExp(str) {
    return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}

// user decision endpoint
//
// `decision` middleware processes a user's decision to allow or deny access
// requested by a client application.  Based on the grant type requested by the
// client, the above grant middleware configured above will be invoked to send
// a response.

exports.decision = [
  login.ensureLoggedIn(),
  server.decision()
];

// token endpoint
//
// `token` middleware handles client requests to exchange authorization grants
// for access tokens.  Based on the grant type being exchanged, the above
// exchange middleware will be invoked to handle the request.  Clients must
// authenticate when making requests to this endpoint.

exports.token = [
  passport.authenticate(['basic', 'oauth2-client-password'], { session: false }),
  server.token(),
  server.errorHandler()
];
<commit_msg>better logging for login issues<commit_after>/**
 * Module dependencies.
 */
var oauth2orize = require('oauth2orize')
  , passport = require('passport')
  , login = require('connect-ensure-login')
  , db = require('./db')
  , utils = require('./utils')
  , scrypt = require('scrypt');

// create OAuth 2.0 server
var server = oauth2orize.createServer();
var ttl = 3600 * 24 * 30; //made their access_token valid for 30 days

// Register serialialization and deserialization functions.
//
// When a client redirects a user to user authorization endpoint, an
// authorization transaction is initiated.  To complete the transaction, the
// user must authenticate and approve the authorization request.  Because this
// may involve multiple HTTP request/response exchanges, the transaction is
// stored in the session.
//
// An application must supply serialization functions, which determine how the
// client object is serialized into the session.  Typically this will be a
// simple matter of serializing the client's ID, and deserializing by finding
// the client by ID from the database.

server.serializeClient(function(client, done) {
  return done(null, client.id);
});

server.deserializeClient(function(id, done) {
  db.clients.find(id, function(err, client) {
    if (err) { return done(err); }
    return done(null, client);
  });
});

// Register supported grant types.
//
// OAuth 2.0 specifies a framework that allows users to grant client
// applications limited access to their protected resources.  It does this
// through a process of the user granting access, and the client exchanging
// the grant for an access token.

// Grant authorization codes.  The callback takes the `client` requesting
// authorization, the `redirectURI` (which is used as a verifier in the
// subsequent exchange), the authenticated `user` granting access, and
// their response, which contains approved scope, duration, etc. as parsed by
// the application.  The application issues a code, which is bound to these
// values, and will be exchanged for an access token.

server.grant(oauth2orize.grant.code(function(client, redirectURI, user, ares, done) {
  var code = utils.uid(16);

  db.authorizationCodes.save(code, client.id, redirectURI, user.id, function(err) {
    if (err) { return done(err); }
    done(null, code);
  });
}));

// Grant implicit authorization.  The callback takes the `client` requesting
// authorization, the authenticated `user` granting access, and
// their response, which contains approved scope, duration, etc. as parsed by
// the application.  The application issues a token, which is bound to these
// values.

server.grant(oauth2orize.grant.token(function(client, user, ares, done) {
    utils.uid(256, function(err, token){
        if(err){ return done(err);}
        utils.uid(256, function(err, refreshToken){
            if(err){ return done(err);}
            var scope = [];
            var expires = new Date();
            expires.setSeconds(expires.getSeconds() + ttl);

            db.accessTokens.save(token, refreshToken, user.id, client.clientId, scope, expires, function(err) {
                if (err) { return done(err); }
                done(null, token, refreshToken, {scope: scope, expires: expires});
            });
        });
    });
}));

// Exchange authorization codes for access tokens.  The callback accepts the
// `client`, which is exchanging `code` and any `redirectURI` from the
// authorization request for verification.  If these values are validated, the
// application issues an access token on behalf of the user who authorized the
// code.

server.exchange(oauth2orize.exchange.code(function(client, code, redirectURI, done) {
  db.authorizationCodes.find(code, function(err, authCode) {
    if (err) { return done(err); }
    if (authCode === null) { return done(null, false); }
    if (client.id !== authCode.clientID) { return done(null, false); }
    if (redirectURI !== authCode.redirectURI) { return done(null, false); }

    utils.uid(256, function(err, token) {
        if (err) {
            return done(err);
        }
        utils.uid(256, function (err, refreshToken) {
            if (err) {
                return done(err);
            }
            var scope = [];
            var expires = new Date();
            expires.setSeconds(expires.getSeconds() + ttl);

            db.accessTokens.save(token, refreshToken, authCode.userID, authCode.clientID, scope, expires, function (err) {
                if (err) {
                    return done(err);
                }
                done(null, token, refreshToken, {scope: scope, expires: expires});
            });
        });
    });
  });
}));

// Exchange user id and password for access tokens.  The callback accepts the
// `client`, which is exchanging the user's name and password from the
// authorization request for verification. If these values are validated, the
// application issues an access token on behalf of the user who authorized the code.

server.exchange(oauth2orize.exchange.password(function(client, username, password, scope, done) {
    console.info(" in password exchange (client:" + JSON.stringify(client) + ", username:" + username + ", password:" + password + ", scope:" + scope + ")");

    //Validate the client
    db.clients.findByClientId(client.clientId, function(err, localClient) {
        console.log('findByClientId:', client.clientId, err, localClient);
        if (err) { return done(err); }
        if(localClient === null) {
            return done(null, false);
        }
        if(localClient.clientSecret !== client.clientSecret) {
            return done(null, false);
        }
        //Validate the user
        db.users.findByUsername(username, function(err, user) {
            console.log('findByUsername', username, err, user);
            if (err) { return done(err); }
            if(user === null) {
                return done(null, false);
            }
            if (!scrypt.verifyKdfSync(new Buffer(user.password, 'base64'), password)) {
                return done(null, false);
            }
            //Everything validated, return the token

            utils.uid(256, function(err, token) {
                if (err) {
                    return done(err);
                }
                utils.uid(256, function (err, refreshToken) {
                    if (err) {
                        return done(err);
                    }
                    if (typeof scope == 'undefined'){
                        scope = [];
                    }
                    var expires = new Date();
                    expires.setSeconds(expires.getSeconds() + ttl);

                    db.accessTokens.save(token, refreshToken, user.id, client.clientId, scope, expires, function (err) {
                        if (err) {
                            return done(err);
                        } else {
                            return done(null, token, refreshToken, {'scope': scope, 'expires': expires});
                        }
                    });
                });
            });
        });
    });
}));

// Exchange the client id and password/secret for an access token.  The callback accepts the
// `client`, which is exchanging the client's id and password/secret from the
// authorization request for verification. If these values are validated, the
// application issues an access token on behalf of the client who authorized the code.

server.exchange(oauth2orize.exchange.clientCredentials(function(client, scope, done) {
    console.log("in clientCredentials exchange (client:" + client + ", scope:" + scope + ")");

    //Validate the client
    db.clients.findByClientId(client.clientId, function(err, localClient) {
        if (err) { return done(err); }
        if(localClient === null) {
            return done(null, false);
        }
        if(localClient.clientSecret !== client.clientSecret) {
        //if(!scrypt.verifyKdfSync(new Buffer(localClient.clientSecret, 'base64'), new Buffer(client.clientSecret, 'base64'))){
            return done(null, false);
        }

        utils.uid(256, function(err, token) {
            if (err) {
                return done(err);
            }
            utils.uid(256, function (err, refreshToken) {
                if (err) {
                    return done(err);
                }
                //var scope = [];
                var expires = new Date();
                expires.setSeconds(expires.getSeconds() + ttl);
                //Pass in a null for user id since there is no user with this grant type
                db.accessTokens.save(token, refreshToken, null, client.clientId, scope, expires, function (err) {
                    if (err) {
                        return done(err);
                    }
                    done(null, token, refreshToken, {scope: scope, expires: expires});
                });
            });
        });
    });
}));

/*
 * Refresh token exchange
 * https://github.com/IGZangelsanchez/oauth2orize-example-extended/blob/master/server/oauth2.js
 */

server.exchange(oauth2orize.exchange.refreshToken(function(client, refreshToken, scope, done) {
    console.log("* Refresh access token client[", client, "] refreshToken[" + refreshToken + "] scope[" + scope + "]");

    db.accessTokens.findRefresh(refreshToken, function (err, accessTokenToRefresh) {
        console.log('find refresh token err[' + err + '] accessToenToRefresh[' + JSON.stringify(accessTokenToRefresh) + ']');
        if (err) { return done(err); }
        if (accessTokenToRefresh === null) { return done(new Error("Refresh token not found."));}
        // if (accessTokenToRefresh.expires < new Date()) {
        //     return done(new Error("Refresh Token has expired."));
        // }

        console.log("- refresh token valid");

        db.accessTokens.find(accessTokenToRefresh.accessToken, function (err, accessTokenDB) {
            console.log('find accessToken results: err', err, accessTokenDB);
            if (err) { return done(err); }
            if (accessTokenDB == null) {
                return done(new Error("Access Token not found."));
            }
            // if (accessTokenDB.expires < new Date()) {
            //     return done(new Error("Access Token has expired."));
            // }

            console.log("- old access token found");

            utils.uid(256, function(err, token) {
                if (err) {
                    return done(err);
                }
                utils.uid(256, function (err, refreshToken) {
                    if (err) {
                        return done(err);
                    }
                    var expires = new Date();
                    expires.setSeconds(expires.getSeconds() + ttl);

                    db.accessTokens.save(token, refreshToken, accessTokenDB.userId, accessTokenDB.clientId, accessTokenDB.scopes, expires, function (err) {
                        if (err) {
                            return done(err);
                        }

                        console.log("- new access/refresh token saved");

                        db.accessTokens.delete(accessTokenToRefresh.accessToken, function (err) {
                            if (err) {
                                return done(err);
                            }

                            console.log("- old access token deleted");

                            done(null, token, refreshToken, {scope: accessTokenDB.scope, 'expires': expires});
                        });
                    });
                });
            });
        });
    });
}));

// user authorization endpoint
//
// `authorization` middleware accepts a `validate` callback which is
// responsible for validating the client making the authorization request.  In
// doing so, is recommended that the `redirectURI` be checked against a
// registered value, although security requirements may vary accross
// implementations.  Once validated, the `done` callback must be invoked with
// a `client` instance, as well as the `redirectURI` to which the user will be
// redirected after an authorization decision is obtained.
//
// This middleware simply initializes a new authorization transaction.  It is
// the application's responsibility to authenticate the user and render a dialog
// to obtain their approval (displaying details about the client requesting
// authorization).  We accomplish that here by routing through `ensureLoggedIn()`
// first, and rendering the `dialog` view.

exports.authorization = [
  login.ensureLoggedIn(),
  server.authorization(function(clientID, redirectURI, done) {
    db.clients.findByClientId(clientID, function(err, client) {
      if (err) { return done(err); }
      // WARNING: For security purposes, it is highly advisable to check that
      //          redirectURI provided by the client matches one registered with
      //          the server.  For simplicity, this example does not.  You have
      //          been warned.
        //escape special chars
      if(!redirectURI.match(new RegExp("^" + escapeRegExp(client.redirectURI) + ".*$"))){
          return done(new Error("redirect URI did not match."));
      }
      return done(null, client, redirectURI);
    });
  }),
  function(req, res){
    res.render('dialog', { transactionID: req.oauth2.transactionID, user: req.user, client: req.oauth2.client });
  }
];

function escapeRegExp(str) {
    return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}

// user decision endpoint
//
// `decision` middleware processes a user's decision to allow or deny access
// requested by a client application.  Based on the grant type requested by the
// client, the above grant middleware configured above will be invoked to send
// a response.

exports.decision = [
  login.ensureLoggedIn(),
  server.decision()
];

// token endpoint
//
// `token` middleware handles client requests to exchange authorization grants
// for access tokens.  Based on the grant type being exchanged, the above
// exchange middleware will be invoked to handle the request.  Clients must
// authenticate when making requests to this endpoint.

exports.token = [
  passport.authenticate(['basic', 'oauth2-client-password'], { session: false }),
  server.token(),
  server.errorHandler()
];
<|endoftext|>


<commit_before>$(function() {
    function OctoPNPViewModel(parameters) {
        var self = this;

        self.settings = parameters[0];
        self.control = parameters[1];
        self.connection = parameters[2];

        var _smdTray = {};
        var _smdTrayCanvas = document.getElementById('trayCanvas');

        self.stateString = ko.observable("No file loaded");
        self.currentOperation = ko.observable("");
        self.debugvar = ko.observable("");
        //white placeholder images
        document.getElementById('headCameraImage').setAttribute( 'src', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wMRCQAfAmB4CgAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAMSURBVAjXY/j//z8ABf4C/tzMWecAAAAASUVORK5CYII=');
        document.getElementById('bedCameraImage').setAttribute( 'src', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wMRCQAfAmB4CgAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAMSURBVAjXY/j//z8ABf4C/tzMWecAAAAASUVORK5CYII=');

        // This will get called before the ViewModel gets bound to the DOM, but after its depedencies have
        // already been initialized. It is especially guaranteed that this method gets called _after_ the settings
        // have been retrieved from the OctoPrint backend and thus the SettingsViewModel been properly populated.
        self.onBeforeBinding = function() {
            self.traySettings = self.settings.settings.plugins.OctoPNP.tray;
            _smdTray = new smdTray(self.traySettings.columns(), self.traySettings.rows(), self.traySettings.boxsize(), _smdTrayCanvas);
            _smdTrayCanvas.addEventListener("click", self.onSmdTrayClick, false); //"click, dblclick"
            _smdTrayCanvas.addEventListener("dblclick", self.onSmdTrayDblclick, false); //"click, dblclick"
        }

        // catch mouseclicks at the tray for interactive part handling
        self.onSmdTrayClick = function(event) {
            var rect = _smdTrayCanvas.getBoundingClientRect();
            var x = Math.floor(event.clientX - rect.left);
            var y = Math.floor(event.clientY - rect.top);
            return _smdTray.selectPart(x, y);
        }

        self.onSmdTrayDblclick = function(event) {
            // highlight part on tray and find partId
            var partId = self.onSmdTrayClick(event);

            // execute pick&place operation
            if(partId) {
                // printer connected and not printing?
                if(self.connection.isOperational() || self.connection.isReady()) {
                    self.control.sendCustomCommand({ command: "M361 P" + partId});
                }
            }
        }

         self.onDataUpdaterPluginMessage = function(plugin, data) {
            if(plugin == "OctoPNP") {
                if(data.event == "FILE") {
                    if(data.data.hasOwnProperty("partCount")) {
                        self.stateString("Loaded file with " + data.data.partCount + " SMD parts");
                        //initialize the tray
                        _smdTray.erase();

						//extract part information
                        if( data.data.hasOwnProperty("parts") ) {
							var parts = data.data.parts;
							for(var i=0; i < parts.length; i++) {
								_smdTray.addPart(parts[i]);
							}
						}
                    }else{
                        self.stateString("No SMD part in this file!");
                    }
                }
                else if(data.event == "OPERATION") {
                    self.currentOperation(data.data.type + " part nr " + data.data.part);
                }
                else if(data.event == "ERROR") {
                    self.stateString("ERROR: \"" + data.data.type + "\"");
                    if(data.data.hasOwnProperty("part")) {
                        self.stateString(self.StateString + "appeared while processing part nr " + data.data.part);
                    }
                }
                else if(data.event == "HEADIMAGE") {
                    document.getElementById('headCameraImage').setAttribute( 'src', data.data.src );
                }
                else if(data.event == "BEDIMAGE") {
                    document.getElementById('bedCameraImage').setAttribute( 'src', data.data.src );
                }
                //self.debugvar("Plugin = OctoPNP");
            }
        };
    }

    // This is how our plugin registers itself with the application, by adding some configuration information to
    // the global variable ADDITIONAL_VIEWMODELS
    ADDITIONAL_VIEWMODELS.push([
        // This is the constructor to call for instantiating the plugin
        OctoPNPViewModel,

        // This is a list of dependencies to inject into the plugin, the order which you request here is the order
        // in which the dependencies will be injected into your view model upon instantiation via the parameters
        // argument
        ["settingsViewModel", "controlViewModel", "connectionViewModel"],

        // Finally, this is the list of all elements we want this view model to be bound to.
        [document.getElementById("tab_plugin_OctoPNP"), document.getElementById("settings_plugin_OctoPNP")]
    ]);
});
<commit_msg>bound tab viewmodell to wrong id<commit_after>$(function() {
    function OctoPNPViewModel(parameters) {
        var self = this;

        self.settings = parameters[0];
        self.control = parameters[1];
        self.connection = parameters[2];

        var _smdTray = {};
        var _smdTrayCanvas = document.getElementById('trayCanvas');

        self.stateString = ko.observable("No file loaded");
        self.currentOperation = ko.observable("");
        self.debugvar = ko.observable("");
        //white placeholder images
        document.getElementById('headCameraImage').setAttribute( 'src', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wMRCQAfAmB4CgAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAMSURBVAjXY/j//z8ABf4C/tzMWecAAAAASUVORK5CYII=');
        document.getElementById('bedCameraImage').setAttribute( 'src', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wMRCQAfAmB4CgAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAMSURBVAjXY/j//z8ABf4C/tzMWecAAAAASUVORK5CYII=');

        // This will get called before the ViewModel gets bound to the DOM, but after its depedencies have
        // already been initialized. It is especially guaranteed that this method gets called _after_ the settings
        // have been retrieved from the OctoPrint backend and thus the SettingsViewModel been properly populated.
        self.onBeforeBinding = function() {
            self.traySettings = self.settings.settings.plugins.OctoPNP.tray;
            _smdTray = new smdTray(self.traySettings.columns(), self.traySettings.rows(), self.traySettings.boxsize(), _smdTrayCanvas);
            _smdTrayCanvas.addEventListener("click", self.onSmdTrayClick, false); //"click, dblclick"
            _smdTrayCanvas.addEventListener("dblclick", self.onSmdTrayDblclick, false); //"click, dblclick"
        }

        // catch mouseclicks at the tray for interactive part handling
        self.onSmdTrayClick = function(event) {
            var rect = _smdTrayCanvas.getBoundingClientRect();
            var x = Math.floor(event.clientX - rect.left);
            var y = Math.floor(event.clientY - rect.top);
            return _smdTray.selectPart(x, y);
        }

        self.onSmdTrayDblclick = function(event) {
            // highlight part on tray and find partId
            var partId = self.onSmdTrayClick(event);

            // execute pick&place operation
            if(partId) {
                // printer connected and not printing?
                if(self.connection.isOperational() || self.connection.isReady()) {
                    self.control.sendCustomCommand({ command: "M361 P" + partId});
                }
            }
        }

         self.onDataUpdaterPluginMessage = function(plugin, data) {
            if(plugin == "OctoPNP") {
                if(data.event == "FILE") {
                    if(data.data.hasOwnProperty("partCount")) {
                        self.stateString("Loaded file with " + data.data.partCount + " SMD parts");
                        //initialize the tray
                        _smdTray.erase();

						//extract part information
                        if( data.data.hasOwnProperty("parts") ) {
							var parts = data.data.parts;
							for(var i=0; i < parts.length; i++) {
								_smdTray.addPart(parts[i]);
							}
						}
                    }else{
                        self.stateString("No SMD part in this file!");
                    }
                }
                else if(data.event == "OPERATION") {
                    self.currentOperation(data.data.type + " part nr " + data.data.part);
                }
                else if(data.event == "ERROR") {
                    self.stateString("ERROR: \"" + data.data.type + "\"");
                    if(data.data.hasOwnProperty("part")) {
                        self.stateString(self.StateString + "appeared while processing part nr " + data.data.part);
                    }
                }
                else if(data.event == "HEADIMAGE") {
                    document.getElementById('headCameraImage').setAttribute( 'src', data.data.src );
                }
                else if(data.event == "BEDIMAGE") {
                    document.getElementById('bedCameraImage').setAttribute( 'src', data.data.src );
                }
                //self.debugvar("Plugin = OctoPNP");
            }
        };
    }

    // This is how our plugin registers itself with the application, by adding some configuration information to
    // the global variable ADDITIONAL_VIEWMODELS
    ADDITIONAL_VIEWMODELS.push([
        // This is the constructor to call for instantiating the plugin
        OctoPNPViewModel,

        // This is a list of dependencies to inject into the plugin, the order which you request here is the order
        // in which the dependencies will be injected into your view model upon instantiation via the parameters
        // argument
        ["settingsViewModel", "controlViewModel", "connectionViewModel"],

        // Finally, this is the list of all elements we want this view model to be bound to.
        [document.getElementById("tab_plugin_OctoPNP")]
    ]);
});
<|endoftext|>