<commit_before>const path = require('path');
const chalk = require('chalk');
const http = require('http');
const server = http.createServer();
const port = process.env.PORT || 1337;
const express = require('express');
const app = express();
const socketio = require('socket.io')
const {
  Maps,
  randomGeneration
} = require('./redux/maps/map');

const {
  updateMap,
  loadMap
} = require('./redux/maps/action-creator')
const {
  updatePlayers,
  removePlayer,
  killPlayer,
  setNickname,
  incrementScore,
  decrementScore
} = require('./redux/players/action-creator');
const {
  addBomb,
  updateBombPositions,
  removePlayerBombs
} = require('./redux/bombs/action-creator')
const {
  setTime,
  getTime
} = require('./redux/timer/action-creator')
const { setWinner } = require('./redux/winner/action-creator')
const {
  roomName,
  convertStateForFrontEnd,
  resetWorld
} = require('./utils')

const store = require('./redux/store')

server.on('request', app);

// creates a new connection server for web sockets and integrates
// it into our HTTP server
const io = socketio(server)

//  use socket server as an event emitter in order to listen for new connections
io.on('connection', (socket) => {
    //manage which room the connected socket is joining (4 sockets max to a room)
  delete socket.adapter.rooms[socket.id]
  let rooms = io.sockets.adapter.rooms;
  let { currentRoomName, createdRoom } = roomName(socket, rooms)

  socket.join(currentRoomName);
  socket.currentRoom = currentRoomName;

  let currState;

  if (createdRoom) {
    //initial setup for a new room
    let randomMap = randomGeneration(Maps)
    store.dispatch(loadMap(randomMap, socket.currentRoom))
    let currentTime = Date.now();

    store.dispatch(setTime(currentTime, socket.currentRoom))
    store.dispatch(getTime(socket.currentRoom))
    currState = convertStateForFrontEnd(store.getState(), socket.currentRoom);
    socket.emit('initial', currState);
  } else {
    //joining a room
    currState = convertStateForFrontEnd(store.getState(), socket.currentRoom);
    socket.emit('initial', currState);
  }

  console.log(chalk.blue('A new client has connected'));
  console.log(chalk.yellow('socket id: ', socket.id));

  socket.on('set_nickname', (nickname) => {
    store.dispatch(setNickname(socket.id, nickname, socket.currentRoom))
  })

  socket.on('get_players', () => {
    socket.emit('get_players', store.getState().players[socket.currentRoom]);
  })

  //the primary socket emission, used 60x a second to update all player and bomb data
  socket.on('update_world', (data) => {
    store.dispatch(updatePlayers({ id: data.playerId, position: data.playerPosition, dead: data.dead, nickname: data.nickname }, socket.currentRoom));
    store.dispatch(updateBombPositions({ userId: data.playerId, bombs: data.playerBombs }, socket.currentRoom))
    store.dispatch(getTime(socket.currentRoom))

    let newState = convertStateForFrontEnd(store.getState(), socket.currentRoom)

    //restart the world if the timer hits 0
    if (newState.timer <= 0) {
      resetWorld(Maps, socket.currentRoom, io)
    } else {
      io.in(socket.currentRoom).emit('update_world', newState)
    }
  })

  //add new bomb to the state when a player clicks
  socket.on('add_bomb', (data) => {
    store.dispatch(addBomb(data, socket.currentRoom))
    io.in(socket.currentRoom).emit('update_bomb_positions', store.getState().bombs[socket.currentRoom])
  })

  //kill player on bomb collision
  socket.on('kill_player', (data) => {
    let room = socket.currentRoom;
    store.dispatch(killPlayer(data.id, room))

    if (data.id !== data.killedBy) {
      store.dispatch(incrementScore(data.killedBy, room))
    } else {
      store.dispatch(decrementScore(data.killedBy, room))
    }

    let currentState = store.getState();
    let currentPlayers = currentState.players[room];
    let killerNickname = currentPlayers[data.killedBy]
    let victimNickname = currentPlayers[data.id];

    data.killerNickname = killerNickname;
    data.victimNickname = victimNickname;
    io.in(room).emit('kill_player', data)

    if (currentPlayers) {
      let currentPlayersIds = Object.keys(currentPlayers)
      let currentPlayersLength = currentPlayersIds.length
      let alivePlayers = []

      for (let player in currentPlayers) {
        if (!currentPlayers[player].dead) {
          currentPlayers[player].socketId = player
          alivePlayers.push(currentPlayers[player])
        }
      }

      //win conditions for if only one player is left alive, or if a solo player dies
      if (currentPlayersLength >= 1 && alivePlayers.length <= 1) {
        if (alivePlayers[0]) store.dispatch(setWinner(alivePlayers[0].socketId, room))
        else {
          let state = store.getState();
          io.in(room).emit('reset_world', {
            players: state.players[room],
            bombs: {},
            map: state.map[room],
            timer: state.timer[room].currTime,
            dead: false
          })
        }
      }
    }

    currentState = store.getState();
    let newState = convertStateForFrontEnd(currentState, room)
    io.in(room).emit('set_winner', newState.winner)
    io.in(room).emit('update_world', newState)
  })

  //chat messages
  socket.on('new_message', (data) => {
    let currentState = store.getState();
    let room = socket.currentRoom;
    let currentPlayers = currentState.players[room];
    let playerNickname;
    if (currentPlayers[data.id] && currentPlayers[data.id].nickname) {
      playerNickname = currentPlayers[data.id].nickname
    }
    else {
      playerNickname = ''
    }
    io.in(socket.currentRoom).emit('new_message', `${playerNickname} : ${data.message}`)
  })

  //change the map on the state when a crate is destroyed
  socket.on('destroy_cube', (data) => {
    store.dispatch(updateMap(data, socket.currentRoom))
  })

  //restart the entire world on game end
  socket.on('reset_world', (data) => {
    resetWorld(Maps, socket.currentRoom, io)
  })

  //remove the player from the state on socket disconnect
  socket.on('disconnect', () => {
    store.dispatch(removePlayer(socket.id, socket.currentRoom))
    store.dispatch(removePlayerBombs(socket.id, socket.currentRoom))
    let currentStatePlayers = store.getState().players[socket.currentRoom];
    if (currentStatePlayers) {
      let currentPlayersLength = Object.keys(currentStatePlayers).length;
      if (!currentPlayersLength) {
        store.dispatch(setWinner(null, socket.currentRoom))
      }
    }
    io.in(socket.currentRoom).emit('remove_player', socket.id)
    console.log('socket id ' + socket.id + ' has disconnected. : (');
  })
})

app.use(express.static(path.join(__dirname, '..', 'public', 'assets')));

app.use(express.static(path.join(__dirname, '..', 'public')));

app.get('/', function(req, res) {
  res.sendFile(path.join(__dirname, '..', 'public', 'index.html'));
});

server.listen(port, function() {
  console.log(`The server is listening on port ${port}!`);
});
<commit_msg>commented index<commit_after>const path = require('path');
const chalk = require('chalk');
const http = require('http');
const server = http.createServer();
const port = process.env.PORT || 1337;
const express = require('express');
const app = express();
const socketio = require('socket.io')
const {
  Maps,
  randomGeneration
} = require('./redux/maps/map');

const {
  updateMap,
  loadMap
} = require('./redux/maps/action-creator')
const {
  updatePlayers,
  removePlayer,
  killPlayer,
  setNickname,
  incrementScore,
  decrementScore
} = require('./redux/players/action-creator');
const {
  addBomb,
  updateBombPositions,
  removePlayerBombs
} = require('./redux/bombs/action-creator')
const {
  setTime,
  getTime
} = require('./redux/timer/action-creator')
const { setWinner } = require('./redux/winner/action-creator')
const {
  roomName,
  convertStateForFrontEnd,
  resetWorld
} = require('./utils')

const store = require('./redux/store')

server.on('request', app);

/* CREATES NEW CONNECTION SERVIER FOR SOCKETS & INTEGRATES HTTP SERVER */
const io = socketio(server)

/*----- USES SOCKET AS EVENT EMITTER TO LISTEN FOR NEW CONNECTIONS -----*/
io.on('connection', (socket) => {
  /*----- MANAGES WHICH ROOM CONNECTED SOCKET IS JOINING (4 MAX) -----*/
  delete socket.adapter.rooms[socket.id]
  let rooms = io.sockets.adapter.rooms;
  let { currentRoomName, createdRoom } = roomName(socket, rooms)

  socket.join(currentRoomName);
  socket.currentRoom = currentRoomName;

  let currState;

  if (createdRoom) {
    /*----- INITIAL SET UP FOR NEW ROOM -----*/
\    let randomMap = randomGeneration(Maps)
    store.dispatch(loadMap(randomMap, socket.currentRoom))
    let currentTime = Date.now();

    store.dispatch(setTime(currentTime, socket.currentRoom))
    store.dispatch(getTime(socket.currentRoom))
    currState = convertStateForFrontEnd(store.getState(), socket.currentRoom);
    socket.emit('initial', currState);
  } else {
    /*----- JOINING ROOM -----*/
    currState = convertStateForFrontEnd(store.getState(), socket.currentRoom);
    socket.emit('initial', currState);
  }

  console.log(chalk.blue('A new client has connected'));
  console.log(chalk.yellow('socket id: ', socket.id));

  socket.on('set_nickname', (nickname) => {
    store.dispatch(setNickname(socket.id, nickname, socket.currentRoom))
  })

  socket.on('get_players', () => {
    socket.emit('get_players', store.getState().players[socket.currentRoom]);
  })

  /*----- PRIMARY SOCKET EMISSION, 60X/SEC TO UPDATE ALL PLAYER & BOMB DATA -----*/
  socket.on('update_world', (data) => {
    store.dispatch(updatePlayers({ id: data.playerId, position: data.playerPosition, dead: data.dead, nickname: data.nickname }, socket.currentRoom));
    store.dispatch(updateBombPositions({ userId: data.playerId, bombs: data.playerBombs }, socket.currentRoom))
    store.dispatch(getTime(socket.currentRoom))

    let newState = convertStateForFrontEnd(store.getState(), socket.currentRoom)

    /*----- RESTART WORLD IF TIMER IS 0 -----*/
    if (newState.timer <= 0) {
      resetWorld(Maps, socket.currentRoom, io)
    } else {
      io.in(socket.currentRoom).emit('update_world', newState)
    }
  })

  /*----- ADD NEW BOMB TO STATE ON CLICK -----*/
  socket.on('add_bomb', (data) => {
    store.dispatch(addBomb(data, socket.currentRoom))
    io.in(socket.currentRoom).emit('update_bomb_positions', store.getState().bombs[socket.currentRoom])
  })

  /*----- KILL PLAYER ON BOMB COLLISION -----*/
  socket.on('kill_player', (data) => {
    let room = socket.currentRoom;
    store.dispatch(killPlayer(data.id, room))

    if (data.id !== data.killedBy) {
      store.dispatch(incrementScore(data.killedBy, room))
    } else {
      store.dispatch(decrementScore(data.killedBy, room))
    }

    let currentState = store.getState();
    let currentPlayers = currentState.players[room];
    let killerNickname = currentPlayers[data.killedBy]
    let victimNickname = currentPlayers[data.id];

    data.killerNickname = killerNickname;
    data.victimNickname = victimNickname;
    io.in(room).emit('kill_player', data)

    if (currentPlayers) {
      let currentPlayersIds = Object.keys(currentPlayers)
      let currentPlayersLength = currentPlayersIds.length
      let alivePlayers = []

      for (let player in currentPlayers) {
        if (!currentPlayers[player].dead) {
          currentPlayers[player].socketId = player
          alivePlayers.push(currentPlayers[player])
        }
      }

      /*----- WIN CONDITIONS FOR WHEN 1 PLAYER LEFT OR IF SOLO PLAYER DIES -----*/
      if (currentPlayersLength >= 1 && alivePlayers.length <= 1) {
        if (alivePlayers[0]) store.dispatch(setWinner(alivePlayers[0].socketId, room))
        else {
          let state = store.getState();
          io.in(room).emit('reset_world', {
            players: state.players[room],
            bombs: {},
            map: state.map[room],
            timer: state.timer[room].currTime,
            dead: false
          })
        }
      }
    }

    currentState = store.getState();
    let newState = convertStateForFrontEnd(currentState, room)
    io.in(room).emit('set_winner', newState.winner)
    io.in(room).emit('update_world', newState)
  })

  /*----- CHAT MESSAGES -----*/
  socket.on('new_message', (data) => {
    let currentState = store.getState();
    let room = socket.currentRoom;
    let currentPlayers = currentState.players[room];
    let playerNickname;
    if (currentPlayers[data.id] && currentPlayers[data.id].nickname) {
      playerNickname = currentPlayers[data.id].nickname
    }
    else {
      playerNickname = ''
    }
    io.in(socket.currentRoom).emit('new_message', `${playerNickname} : ${data.message}`)
  })

  /*----- CHANGES MAP ON STATE WHEN BOX IS DESTROYED -----*/
  socket.on('destroy_cube', (data) => {
    store.dispatch(updateMap(data, socket.currentRoom))
  })

  /*----- RESTART WORLD ON GAME END -----*/
  socket.on('reset_world', (data) => {
    resetWorld(Maps, socket.currentRoom, io)
  })

  /*----- REMOVE PLAYER FROM STATE ON DISCONNECT -----*/
  socket.on('disconnect', () => {
    store.dispatch(removePlayer(socket.id, socket.currentRoom))
    store.dispatch(removePlayerBombs(socket.id, socket.currentRoom))
    let currentStatePlayers = store.getState().players[socket.currentRoom];
    if (currentStatePlayers) {
      let currentPlayersLength = Object.keys(currentStatePlayers).length;
      if (!currentPlayersLength) {
        store.dispatch(setWinner(null, socket.currentRoom))
      }
    }
    io.in(socket.currentRoom).emit('remove_player', socket.id)
    console.log('socket id ' + socket.id + ' has disconnected. : (');
  })
})

app.use(express.static(path.join(__dirname, '..', 'public', 'assets')));

app.use(express.static(path.join(__dirname, '..', 'public')));

app.get('/', function(req, res) {
  res.sendFile(path.join(__dirname, '..', 'public', 'index.html'));
});

server.listen(port, function() {
  console.log(`The server is listening on port ${port}!`);
});
<|endoftext|>


<commit_before>var gpio = require('pi-gpio');

setTimeout(function () {
	gpio.open(7, "in", function (err) {
		gpio.read(7, function (data) {
			console.log(data);
		});
	});
}, 5000);<commit_msg>Accidentally used setTimeout instead of setInterval<commit_after>var gpio = require('pi-gpio');

setInterval(function () {
	gpio.open(7, "in", function (err) {
		gpio.read(7, function (data) {
			console.log(data);
		});
	});
}, 5000);<|endoftext|>


<commit_before>console.log('Welcome to Moustacheminer Server Services');

const config = require('config');
const express = require('express');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const engines = require('consolidate');
const path = require('path');
const request = require('request');
const cors = require('cors');
const i18n = require('i18n');
const discord = require('./discord');
const api = require('./api');
const docs = require('./docs');
const url = require('./url');
const mail = require('./mail');
const lang = require('./lang');
const auth = require('./auth');
const r = require('./db');
const authentication = require('./auth/auth');
const session = require('express-session');

const app = express();

i18n.configure({
	directory: path.join(__dirname, '..', 'locales'),
	cookie: 'lang',
	defaultLocale: 'en-gb',
	fallbacks: {
		'zh-hk': 'en-gb'
	},
	autoReload: true,
	updateFiles: false
});

// Middleware
app.enable('trust proxy')
	.use(bodyParser.json({
		limit: '20mb'
	}))
	.use(bodyParser.urlencoded({
		extended: true,
		limit: '20mb'
	}))
	.use(cookieParser(config.get('webserver').secret))
	.use(session({
		secret: config.get('webserver').secret,
		resave: true,
		saveUninitialized: true,
		proxy: true
	}))
	.use(i18n.init)
	.set('views', path.join(__dirname, '/dynamic'))
	.engine('html', engines.pug)
	.set('view engine', 'html')
	.use(cors())
	.use(authentication.initialize())
	.use(authentication.session())
	.use((req, res, next) => {
		res.locals.domain = config.get('api').mailgun.domain;
		if (req.user) {
			r.table('registrations')
				.get(req.user.id)
				.run(r.conn, (err, result) => {
					if (err) {
						res.status(500).render('error.pug', { status: 500 });
					} else {
						req.user.dmail = result;
						res.locals.user = req.user;
					}
					next();
				});
		} else {
			next();
		}
	})
	.get('/', (req, res) => {
		request({
			method: 'GET',
			url: `https://api:${config.get('api').mailgun.apiKey}@api.mailgun.net/v3/${config.get('api').mailgun.domain}/stats/total?event=accepted&event=delivered&resolution=month`,
			json: true
		}, (err, response, body) => {
			let incoming = null;
			let outgoing = null;

			if (err || response.statusCode !== 200) {
				incoming = 'Error';
				outgoing = 'Error';
			} else {
				incoming = body.stats.reduce((total, time) => total + time.accepted.incoming, 0);
				outgoing = body.stats.reduce((total, time) => total + time.accepted.outgoing, 0);
			}

			res.status(200).render('index.pug', {
				discord,
				incoming,
				outgoing
			});
		});
	})
	.use('/api', api)
	.use('/docs', docs)
	.use('/url', url)
	.use('/mail', mail)
	.use('/lang', lang)
	.use('/auth', auth)
	.use(express.static(path.join(__dirname, '/static')))
	.use('*', (req, res) => res.status(404).render('error.pug', { status: 404 }));

console.log('Listening on', config.get('webserver').port);
app.listen(config.get('webserver').port);
<commit_msg>Fallbacks not needed<commit_after>console.log('Welcome to Moustacheminer Server Services');

const config = require('config');
const express = require('express');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const engines = require('consolidate');
const path = require('path');
const request = require('request');
const cors = require('cors');
const i18n = require('i18n');
const discord = require('./discord');
const api = require('./api');
const docs = require('./docs');
const url = require('./url');
const mail = require('./mail');
const lang = require('./lang');
const auth = require('./auth');
const r = require('./db');
const authentication = require('./auth/auth');
const session = require('express-session');

const app = express();

i18n.configure({
	directory: path.join(__dirname, '..', 'locales'),
	cookie: 'lang',
	defaultLocale: 'en-gb',
	autoReload: true,
	updateFiles: false
});

// Middleware
app.enable('trust proxy')
	.use(bodyParser.json({
		limit: '20mb'
	}))
	.use(bodyParser.urlencoded({
		extended: true,
		limit: '20mb'
	}))
	.use(cookieParser(config.get('webserver').secret))
	.use(session({
		secret: config.get('webserver').secret,
		resave: true,
		saveUninitialized: true,
		proxy: true
	}))
	.use(i18n.init)
	.set('views', path.join(__dirname, '/dynamic'))
	.engine('html', engines.pug)
	.set('view engine', 'html')
	.use(cors())
	.use(authentication.initialize())
	.use(authentication.session())
	.use((req, res, next) => {
		res.locals.domain = config.get('api').mailgun.domain;
		if (req.user) {
			r.table('registrations')
				.get(req.user.id)
				.run(r.conn, (err, result) => {
					if (err) {
						res.status(500).render('error.pug', { status: 500 });
					} else {
						req.user.dmail = result;
						res.locals.user = req.user;
					}
					next();
				});
		} else {
			next();
		}
	})
	.get('/', (req, res) => {
		request({
			method: 'GET',
			url: `https://api:${config.get('api').mailgun.apiKey}@api.mailgun.net/v3/${config.get('api').mailgun.domain}/stats/total?event=accepted&event=delivered&resolution=month`,
			json: true
		}, (err, response, body) => {
			let incoming = null;
			let outgoing = null;

			if (err || response.statusCode !== 200) {
				incoming = 'Error';
				outgoing = 'Error';
			} else {
				incoming = body.stats.reduce((total, time) => total + time.accepted.incoming, 0);
				outgoing = body.stats.reduce((total, time) => total + time.accepted.outgoing, 0);
			}

			res.status(200).render('index.pug', {
				discord,
				incoming,
				outgoing
			});
		});
	})
	.use('/api', api)
	.use('/docs', docs)
	.use('/url', url)
	.use('/mail', mail)
	.use('/lang', lang)
	.use('/auth', auth)
	.use(express.static(path.join(__dirname, '/static')))
	.use('*', (req, res) => res.status(404).render('error.pug', { status: 404 }));

console.log('Listening on', config.get('webserver').port);
app.listen(config.get('webserver').port);
<|endoftext|>


<commit_before>var mongo = require( 'mongoose' ),
	compression = require( 'compression' ),
	mongo = require( 'mongoose' ),
	express = require( 'express' ),
	bodyParser = require( 'body-parser' ),
	globSync = require( 'glob' ).sync,
	morgan = require( 'morgan' );

module.exports = function( app, options ) {

	mongo.createConnection( 'mongodb://localhost/muffin' );
	app.use( compression() );

	var db = mongo.connection;

	db.on( 'error', function( err ) {
		console.log( 'Can\'t connect to the DB: ' + err );
		process.exit();
	});

	db.on( 'disconnected', function() {
		console.log( 'Muffin stopped running (DB error)' );
	});

	process.on( 'SIGINT', function() {

		db.close( function() {
			process.exit();
		});

	});

	app.use( bodyParser.json() );

	app.use( bodyParser.urlencoded({
		extended: false
	}));

	app.get( '/muffin', function( req, res, next ) {

		if( req.path != '/muffin/' ) {
			res.redirect( '/muffin/' );
		} else {
			next();
		}

	});

	options.httpServer.on( 'listening', function() {

		require( 'colors' );

		var current = process.cwd(),
			dir = current.substring( current.lastIndexOf( '/' ) + 1, current.length );

		var host = this.address().address == '::' && 'localhost',
			port = this.address().port + '/muffin';

		var address = 'http://' + host + ':' + port;
		process.stdout.write( '\u001b[2J\u001b[0;0H' );

		var messages = [
			'MUFFIN IS RUNNING!'.green + '\n',
			'You can access your site at ' + address.underline.red + '\n',
			'Use ' + 'cmd'.bgWhite + ' + ' + 'double-click'.bgWhite + ' on the link to open it.' + '\n\n'
		];

		for( var i = 0; i < messages.length; i++ ) {
			console.log( messages[i] );
		}

	});

	var routes = globSync( './routes/*.js', { cwd: __dirname } ).map( require );
	app.use( morgan( 'dev' ) );

	routes.forEach( function( route ) {
		route( app );
	});

}
<commit_msg>Make login work again<commit_after>var mongo = require( 'mongoose' ),
	compression = require( 'compression' ),
	mongo = require( 'mongoose' ),
	express = require( 'express' ),
	bodyParser = require( 'body-parser' ),
	globSync = require( 'glob' ).sync,
	morgan = require( 'morgan' );

module.exports = function( app, options ) {

	mongo.connect( 'mongodb://localhost/muffin' );
	app.use( compression() );

	var db = mongo.connection;

	db.on( 'error', function( err ) {
		console.log( 'Can\'t connect to the DB: ' + err );
		process.exit();
	});

	db.on( 'disconnected', function() {
		console.log( 'Muffin stopped running (DB error)' );
	});

	process.on( 'SIGINT', function() {

		db.close( function() {
			process.exit();
		});

	});

	app.use( bodyParser.json() );

	app.use( bodyParser.urlencoded({
		extended: false
	}));

	app.get( '/muffin', function( req, res, next ) {

		if( req.path != '/muffin/' ) {
			res.redirect( '/muffin/' );
		} else {
			next();
		}

	});

	options.httpServer.on( 'listening', function() {

		require( 'colors' );

		var current = process.cwd(),
			dir = current.substring( current.lastIndexOf( '/' ) + 1, current.length );

		var host = this.address().address == '::' && 'localhost',
			port = this.address().port + '/muffin';

		var address = 'http://' + host + ':' + port;
		process.stdout.write( '\u001b[2J\u001b[0;0H' );

		var messages = [
			'MUFFIN IS RUNNING!'.green + '\n',
			'You can access your site at ' + address.underline.red + '\n',
			'Use ' + 'cmd'.bgWhite + ' + ' + 'double-click'.bgWhite + ' on the link to open it.' + '\n\n'
		];

		for( var i = 0; i < messages.length; i++ ) {
			console.log( messages[i] );
		}

	});

	var routes = globSync( './routes/*.js', { cwd: __dirname } ).map( require );
	app.use( morgan( 'dev' ) );

	routes.forEach( function( route ) {
		route( app );
	});

}
<|endoftext|>


<commit_before>var M = process.mono;

function init (config) {

}

module.exports = init;
<commit_msg>require custom module and init it with the scope of the mono instance<commit_after>var M = process.mono;

function init (config) {
    var self = this;

    // load and init custom module
    if (config.module) {
        try {
            (require(M.paths.APPLICATION_ROOT + module)).call(self, config.config);
        } catch (err) {
            // TODO handle error
        }
    }
}

module.exports = init;
<|endoftext|>


<commit_before>/*
 * If there are non-Latin characters, show transliteration field
 */
function toggleTranslit(id) {
	//Regex for everything outside the standard character set
	var nonroman = /[^\u0000-\u024F\u0263\u02B9\u02BA\u02DD\u0300\u0301\u0302\u0303\u0304\u0306\u0308\u0309\u030A\u030C\u0310\u0313\u0315\u0321\u0322\u0323\u0324\u0325\u0327\u0328\u032E\u0332\u0333\u0351\u0357\u0366\u03B1\u04D4\u04D5\u2020\u2070\u2074\u2075\u2076\u2077\u2078\u2079\u207A\u207B\u207D\u207E\u2080\u2081\u2082\u2083\u2084\u2085\u2086\u2087\u2088\u2089\u20AC\u2113\u2117\u266D\u266F\uFE20\uFE21\uFE22\uFE23]/;
	//True if any of the characters in title is from a different alphabet
	var needsTranslit = nonroman.test($("#"+id).val());

	//The given name needed to be detected, but the visual changes are named after the family_name#
	if (id.substring(0,5) === 'given') {
		id = 'family' + id.substring(5);

		//The given field has been removed, but the family field still needs transliteration
		if (!needsTranslit && nonroman.test($("#"+id).val())) {
			return;
		}
	}

	if (needsTranslit) {
		$(".translit-" + id).show();
		$("#translit-" + id + "-block").show();
		$(".translit-" + id + "-block").css("padding","3px");
	}
	else {
		if (id.substring(0,6) === 'family') {
			//If the family field has been removed, but the given field still needs transliteration
			if (nonroman.test($("#given" + id.substring(6)).val())) {
				return;
			}
		}

		$(".translit-" + id).hide();
		$("#translit-" + id + "-block").hide();
		$(".translit-" + id + "-block").css("padding","0px");
	}
}

/*
 * If a listening field has been changed, send the id to the toggler.
 */
//$("#marc-maker").on('change','.translit-listen',function() {
$("#marc-maker").on('blur','.translit-listen',function() {
	toggleTranslit($(this).attr("id"));
});

/*
 * When the insert menu is open ESC will get rid of it
 */
$(document).keyup(function(e) {
	if (e.keyCode === 27) {
		if (document.getElementById("insert-popup")) {
			$("#insert-popup").remove();
		}
	}
});

/*
 * Inserts selected character at the end of the associated input, not where the cursor is
 */
function insertChar(field,insert_value) {
	var current_contents = $("#" + field).val();
	$("#" + field).val(current_contents + insert_value);
	$("#" + field).focus();
	$("#insert-popup").remove();
}

/*
 * When the Insert button is pressed, create a floating keyboard with characters to insert
 */
function constructMenu(field) {
	var unicodes = ['0301','04D5','04D4','0357','0351','0306','00A3','0310','0327','030A','0325','0302','005E','00A9','0111','0110','0366','0323','00B7','02DD','0324','FE22','FE23','0333','00DF','00F0','00D0','20AC','0060','0300','030C','0313','0315','0328','00A1','00BF','0142','0141','007B','0321','FE20','FE21','0304','02B9','266D','266F','01A1','01A0','00F8','00D8','0153','0152','2117','00B1','0309','007D','0322','2113','2080','2081','2082','2083','2084','2085','2086','2087','2088','2089','2070','00B9','00B2','00B3','2074','2075','2076','2077','2078','2079','207D','207E','207B','207A','00FE','00DE','0303','007E','0131','02BA','01B0','01AF','0308','0332','005F','032E','03B1','2020','0263','00AE'];
	var return_string = "
\n";
	for (var i = 0; i < unicodes.length; i++) {
		var new_tag = "<button value='" + String.fromCharCode(parseInt(unicodes[i],16)) + "' id='" + unicodes[i] + "' class='diacritics ";

		if (i%14 === 13 && i + 14 > unicodes.length) {
			new_tag += 'bottom-right';
		}
		else if (i + 14 >= unicodes.length) {
			new_tag += 'last-row';
		}
		else if (i%14 === 13) {
			new_tag += 'row-end';
		}
		else {
			new_tag += 'normal-button';
		}

		new_tag += "' type='button' onClick='insertChar(\"" + field + "\",\"" + String.fromCharCode(parseInt(unicodes[i],16)) + "\")'>&#x" + unicodes[i] + "</button>";

		if (i%14 === 13) {
			new_tag += "
\n";
		}

		return_string += new_tag;
	};
	return_string += "
";
	return return_string;
}

/*
 * Insert special characters into field
 */
function insertMenu(field) {
	if (document.getElementById("insert-popup")) {
		$("#insert-popup").remove();
	}
	else {
		var newdiv = document.createElement('div');
		newdiv.setAttribute('id','insert-popup');
		newdiv.innerHTML = constructMenu(field);
		$("#insert-" + field).append(newdiv);
	}
}

/*
 * when one of the author fields has been filled in, the other is no longer required
 */
$(".author").change(function() {
	var field = $(this).attr("id");
	if (field === 'family_name') {
		var other = '#given_name';
	}
	else {
		var other = '#family_name';
	}

	if ($(this).val() === '') {
		$(other).attr("required","true");
	}
	else {
		$(other).removeAttr("required");
	}
});

/*
 * Set highest allowed year to next year
 */
var present = new Date().getFullYear();
$(".date").attr("max",present+1);

/*
 * Add keyword fields
 */
var counter = 1;
function addKeyword() {
	if (counter < 50) {
		var newdiv = document.createElement('div');
		newdiv.className = 'added added-keyword';
		newdiv.innerHTML = '
<input type="text" class="keyword" id="keyword' + counter +'">';
		$("#keywords").append(newdiv);
		counter++;
	}
};

/*
 * Adds additional input fields so multiple authors or other contributors can be added. Each input field behaves
 * just like the initial names field, including transliteration capabilities.
 */
var aCounter = 0;
function addAuthor() {
	if (aCounter < 50) {
		var newdiv = document.createElement('div');
		newdiv.className = 'added';
		newdiv.setAttribute('id','family_name' + aCounter + '-block');
		newdiv.innerHTML = 'Insert DiacriticsInsert Diacritics
';
		newdiv.innerHTML += '

';
		newdiv.innerHTML += '<input type="text" class="author translit-listen" id="family_name' + aCounter + '" placeholder="Family Name">, <input type="text" class="author translit-listen" id="given_name' + aCounter + '" placeholder="Given Name"> <select name="role' + aCounter + '" id="role'  + aCounter + '"><option value="art">artist</option><option selected value="aut">author</option><option value="ctb">contributor</option><option value="edt">editor</option><option value="ill">illustrator</option><option value="trl">translator</option></select>';
		$("#author-block").append(newdiv);
		var translit_div = document.createElement('div');
		translit_div.className = 'translit-family_name' + aCounter + '-block translit-block translit-author hidden';
		translit_div.setAttribute('id','translit-family_name' + aCounter + '-block');
		translit_div.innerHTML = 'Insert DiacriticsInsert Diacritics
';
		translit_div.innerHTML += '

';
		translit_div.innerHTML += '<input type="text" id="translit_family_name' + aCounter + '" class="hidden translit translit-family_name' + aCounter + '" placeholder="Transliterated Family Name">, <input type="text" id="translit_given_name' + aCounter + '" class="hidden translit translit-family_name' + aCounter + '" placeholder="Transliterated Given Name">';
		$("#family_name" + aCounter + '-block').append(translit_div);
		aCounter++;
	}
}

/*
 * Make all the conditionally required fields required, hide any fields that were revealed, remove fields that
 * were added
 */
$(":reset").click(function() {
	$(".conditional").attr("required","true");
	$(".conditional").removeAttr("disabled");
	$(".hidden").hide();
	$(".added").remove();
	$(".translit-block").css("padding","0px");
	counter = 1;
});

/*
 * If the attribute is listed, make it not required
 */
$(".listed").click(function() {
	var field = $(this).attr("id");
	field = field.substring(0,field.length-7);

	if (field !== "author") {
		field = '#' + field;
	}
	else {
		field = "." + field;
		if ($("#family_name").val() !== '' || $("#given_name").val() !== '') {
			return;
		}
	}

	if ($(this).is(':checked')) {
		$(field).removeAttr("required");
		$(field).attr("disabled","true");
	}
	else {
		$(field).attr("required","true");
		$(field).removeAttr("disabled");
	}
});

/*
 * Show or hide the literature dropdown based on response to radio button
 */
$("input:radio[name=literature]").click(function() {
	var value = $(this).val();
	if (value === "yes") {
		$("#literature-dropdown").show();
	}
	else {
		$("#literature-dropdown").hide();
	}
});

/*
 * Check that an optional field has been input
 */
function checkExists(attr) {
	if (typeof(attr) !== "undefined" && attr !== '' && attr !== null) {
		return true;
	}
	else {
		return false;
	}
}

/*
 * Once specific processing for a format has been done, create, and then download the resulting record
 */
function downloadFile(text,filetype) {
	var download_file = document.createElement('a');

	if (filetype === 'mrc') {
		var header = 'data:application/marc;charset=utf-8,';
	}
	else if (filetype === 'html') {
		var header = 'data:text/html;charset=utf-8,';
	}
	else {
		var header = 'data:text/plain;charset=utf-8,';
	}

	download_file.setAttribute('href',header + encodeURIComponent(text));
	if (checkExists($("#filename").val())) {
		var filename = $("#filename").val();
	}
	else {
		var filename = 'record';
	}

	if (filetype === 'xml') {
		filename += '_MARCXML';
	}
	else if (filetype === 'mods') {
		filename += '_MODS';
		filetype = 'xml';
	}

	download_file.setAttribute('download', filename + '.' + filetype);
	var clickReplacement = new MouseEvent('click', {
		'view': window,
		'bubbles': true,
		'cancleable': false
	});
	download_file.dispatchEvent(clickReplacement);
}

/*
 * Return the current time in the format yyyymmddhhmmss.s in GMT
 */
function getTimestamp() {
	var date = new Date();
	date = date.toISOString();
	return date.substring(0,4) + date.substring(5,7) + date.substring(8,10) + date.substring(11,13) + date.substring(14,16) + date.substring(17,21);
}<commit_msg>Name now only takes one field<commit_after>/*
 * If there are non-Latin characters, show transliteration field
 */
function toggleTranslit(id) {
	//Regex for everything outside the standard character set
	var nonroman = /[^\u0000-\u024F\u0263\u02B9\u02BA\u02DD\u0300\u0301\u0302\u0303\u0304\u0306\u0308\u0309\u030A\u030C\u0310\u0313\u0315\u0321\u0322\u0323\u0324\u0325\u0327\u0328\u032E\u0332\u0333\u0351\u0357\u0366\u03B1\u04D4\u04D5\u2020\u2070\u2074\u2075\u2076\u2077\u2078\u2079\u207A\u207B\u207D\u207E\u2080\u2081\u2082\u2083\u2084\u2085\u2086\u2087\u2088\u2089\u20AC\u2113\u2117\u266D\u266F\uFE20\uFE21\uFE22\uFE23]/;
	//True if any of the characters in title is from a different alphabet
	var needsTranslit = nonroman.test($("#"+id).val());

	//The given name needed to be detected, but the visual changes are named after the family_name#
	if (id.substring(0,5) === 'given') {
		id = 'family' + id.substring(5);

		//The given field has been removed, but the family field still needs transliteration
		if (!needsTranslit && nonroman.test($("#"+id).val())) {
			return;
		}
	}

	if (needsTranslit) {
		$(".translit-" + id).show();
		$("#translit-" + id + "-block").show();
		$(".translit-" + id + "-block").css("padding","3px");
	}
	else {
		if (id.substring(0,6) === 'family') {
			//If the family field has been removed, but the given field still needs transliteration
			if (nonroman.test($("#given" + id.substring(6)).val())) {
				return;
			}
		}

		$(".translit-" + id).hide();
		$("#translit-" + id + "-block").hide();
		$(".translit-" + id + "-block").css("padding","0px");
	}
}

/*
 * If a listening field has been changed, send the id to the toggler.
 */
$("#marc-maker").on('blur','.translit-listen',function() {
	toggleTranslit($(this).attr("id"));
});

/*
 * When the insert menu is open ESC will get rid of it
 */
$(document).keyup(function(e) {
	if (e.keyCode === 27) {
		if (document.getElementById("insert-popup")) {
			$("#insert-popup").remove();
		}
	}
});

/*
 * Inserts selected character at the end of the associated input, not where the cursor is
 */
function insertChar(field,insert_value) {
	var current_contents = $("#" + field).val();
	$("#" + field).val(current_contents + insert_value);
	$("#" + field).focus();
	$("#insert-popup").remove();
}

/*
 * When the Insert button is pressed, create a floating keyboard with characters to insert
 */
function constructMenu(field) {
	var unicodes = ['0301','04D5','04D4','0357','0351','0306','00A3','0310','0327','030A','0325','0302','005E','00A9','0111','0110','0366','0323','00B7','02DD','0324','FE22','FE23','0333','00DF','00F0','00D0','20AC','0060','0300','030C','0313','0315','0328','00A1','00BF','0142','0141','007B','0321','FE20','FE21','0304','02B9','266D','266F','01A1','01A0','00F8','00D8','0153','0152','2117','00B1','0309','007D','0322','2113','2080','2081','2082','2083','2084','2085','2086','2087','2088','2089','2070','00B9','00B2','00B3','2074','2075','2076','2077','2078','2079','207D','207E','207B','207A','00FE','00DE','0303','007E','0131','02BA','01B0','01AF','0308','0332','005F','032E','03B1','2020','0263','00AE'];
	var return_string = "
\n";
	for (var i = 0; i < unicodes.length; i++) {
		var new_tag = "<button value='" + String.fromCharCode(parseInt(unicodes[i],16)) + "' id='" + unicodes[i] + "' class='diacritics ";

		if (i%14 === 13 && i + 14 > unicodes.length) {
			new_tag += 'bottom-right';
		}
		else if (i + 14 >= unicodes.length) {
			new_tag += 'last-row';
		}
		else if (i%14 === 13) {
			new_tag += 'row-end';
		}
		else {
			new_tag += 'normal-button';
		}

		new_tag += "' type='button' onClick='insertChar(\"" + field + "\",\"" + String.fromCharCode(parseInt(unicodes[i],16)) + "\")'>&#x" + unicodes[i] + "</button>";

		if (i%14 === 13) {
			new_tag += "
\n";
		}

		return_string += new_tag;
	};
	return_string += "
";
	return return_string;
}

/*
 * Insert special characters into field
 */
function insertMenu(field) {
	if (document.getElementById("insert-popup")) {
		$("#insert-popup").remove();
	}
	else {
		var newdiv = document.createElement('div');
		newdiv.setAttribute('id','insert-popup');
		newdiv.innerHTML = constructMenu(field);
		$("#insert-" + field).append(newdiv);
	}
}

/*
 * when one of the author fields has been filled in, the other is no longer required
 */
$(".author").change(function() {
	var field = $(this).attr("id");
	if (field === 'family_name') {
		var other = '#given_name';
	}
	else {
		var other = '#family_name';
	}

	if ($(this).val() === '') {
		$(other).attr("required","true");
	}
	else {
		$(other).removeAttr("required");
	}
});

/*
 * Set highest allowed year to next year
 */
var present = new Date().getFullYear();
$(".date").attr("max",present+1);

/*
 * Add keyword fields
 */
var counter = 1;
function addKeyword() {
	if (counter < 50) {
		var newdiv = document.createElement('div');
		newdiv.className = 'added added-keyword';
		newdiv.innerHTML = '
<input type="text" class="keyword" id="keyword' + counter +'">';
		$("#keywords").append(newdiv);
		counter++;
	}
};

/*
 * Adds additional input fields so multiple authors or other contributors can be added. Each input field behaves
 * just like the initial names field, including transliteration capabilities.
 */
var aCounter = 0;
function addAuthor() {
	if (aCounter < 50) {
		var newdiv = document.createElement('div');
		newdiv.className = 'added';
		newdiv.setAttribute('id','family_name' + aCounter + '-block');
		newdiv.innerHTML = 'Insert Diacritics
';
		newdiv.innerHTML += '

';
		newdiv.innerHTML += '<input type="text" class="author translit-listen" id="family_name' + aCounter + '" placeholder="Family Name, Given Name"> <select name="role' + aCounter + '" id="role'  + aCounter + '"><option value="art">artist</option><option selected value="aut">author</option><option value="ctb">contributor</option><option value="edt">editor</option><option value="ill">illustrator</option><option value="trl">translator</option></select>';
		$("#author-block").append(newdiv);
		var translit_div = document.createElement('div');
		translit_div.className = 'translit-family_name' + aCounter + '-block translit-block translit-author hidden';
		translit_div.setAttribute('id','translit-family_name' + aCounter + '-block');
		translit_div.innerHTML = 'Insert Diacritics
';
		translit_div.innerHTML += '

';
		translit_div.innerHTML += '<input type="text" id="translit_family_name' + aCounter + '" class="hidden translit translit-family_name' + aCounter + '" placeholder="Transliterated Family Name, Given Name">';
		$("#family_name" + aCounter + '-block').append(translit_div);
		aCounter++;
	}
}

/*
 * Make all the conditionally required fields required, hide any fields that were revealed, remove fields that
 * were added
 */
$(":reset").click(function() {
	$(".conditional").attr("required","true");
	$(".conditional").removeAttr("disabled");
	$(".hidden").hide();
	$(".added").remove();
	$(".translit-block").css("padding","0px");
	counter = 1;
});

/*
 * If the attribute is listed, make it not required
 */
$(".listed").click(function() {
	var field = $(this).attr("id");
	field = field.substring(0,field.length-7);

	if (field !== "author") {
		field = '#' + field;
	}
	else {
		field = "." + field;
		if ($("#family_name").val() !== '' || $("#given_name").val() !== '') {
			return;
		}
	}

	if ($(this).is(':checked')) {
		$(field).removeAttr("required");
		$(field).attr("disabled","true");
	}
	else {
		$(field).attr("required","true");
		$(field).removeAttr("disabled");
	}
});

/*
 * Show or hide the literature dropdown based on response to radio button
 */
$("input:radio[name=literature]").click(function() {
	var value = $(this).val();
	if (value === "yes") {
		$("#literature-dropdown").show();
	}
	else {
		$("#literature-dropdown").hide();
	}
});

/*
 * Check that an optional field has been input
 */
function checkExists(attr) {
	if (typeof(attr) !== "undefined" && attr !== '' && attr !== null) {
		return true;
	}
	else {
		return false;
	}
}

/*
 * Once specific processing for a format has been done, create, and then download the resulting record
 */
function downloadFile(text,filetype) {
	var download_file = document.createElement('a');

	if (filetype === 'mrc') {
		var header = 'data:application/marc;charset=utf-8,';
	}
	else if (filetype === 'html') {
		var header = 'data:text/html;charset=utf-8,';
	}
	else {
		var header = 'data:text/plain;charset=utf-8,';
	}

	download_file.setAttribute('href',header + encodeURIComponent(text));
	if (checkExists($("#filename").val())) {
		var filename = $("#filename").val();
	}
	else {
		var filename = 'record';
	}

	if (filetype === 'xml') {
		filename += '_MARCXML';
	}
	else if (filetype === 'mods') {
		filename += '_MODS';
		filetype = 'xml';
	}

	download_file.setAttribute('download', filename + '.' + filetype);
	var clickReplacement = new MouseEvent('click', {
		'view': window,
		'bubbles': true,
		'cancleable': false
	});
	download_file.dispatchEvent(clickReplacement);
}

/*
 * Return the current time in the format yyyymmddhhmmss.s in GMT
 */
function getTimestamp() {
	var date = new Date();
	date = date.toISOString();
	return date.substring(0,4) + date.substring(5,7) + date.substring(8,10) + date.substring(11,13) + date.substring(14,16) + date.substring(17,21);
}<|endoftext|>


<commit_before>
/**
 * Module dependencies.
 */

var Transport = require('../transport')
  , util = require('../util')
  , global = this

/**
 * Module exports.
 */

module.exports = WS;

/**
 * WebSocket transport constructor.
 *
 * @api {Object} connection options
 * @api public
 */

function WS (opts) {
  Transport.call(this, opts);
};

/**
 * Inherits from Transport.
 */

util.inherits(WS, Transport);

/**
 * Transport name.
 *
 * @api public
 */

WS.prototype.name = 'websocket';

/**
 * Opens socket.
 *
 * @api private
 */

WS.prototype.doOpen = function () {
  if (!check()) {
    // let probe timeout
    return;
  }

  var self = this;

  this.socket = new (ws())(this.uri());
  this.socket.onopen = function () {
    self.onOpen();
  };
  this.socket.onclose = function () {
    self.onClose();
  };
  this.socket.onmessage = function (ev) {
    self.onData(ev.data);
  };
  this.socket.onerror = function (e) {
    self.onError('websocket error', e);
  };
};

/**
 * Writes data to socket.
 *
 * @param {String} data.
 * @param {Function} flush callback.
 * @api private
 */

WS.prototype.write = function (data, fn) {
  this.socket.send(data);
  fn();
};

/**
 * Writes a packets payload.
 *
 * @param {Array} data packets
 * @api private
 */

WS.prototype.writeMany = function (packets) {
  for (var i = 0, l = packets.length; i < l; i++) {
    this.write(packets[0]);
  }
};

/**
 * Closes socket.
 *
 * @api private
 */

WS.prototype.doClose = function () {
  this.socket.close();
};

/**
 * Generates uri for connection.
 *
 * @api private
 */

WS.prototype.uri = function () {
  var opts = this.options
    , query = util.qs(opts.query)
    , schema = opts.secure ? 'wss' : 'ws'
    , port = ''

  // avoid port if default for schema
  if (('wss' == schema && opts.port != 443)
    || ('ws' == schema && opts.port != 80)) {
    port = ':' + opts.port;
  }

  // prepend ? to query
  if (query.length) {
    query = '?' + query;
  }

  return schema + opts.host + port + opts.path + query;
};

/**
 * Getter for WS constructor.
 *
 * @api private
 */

function ws () {
  // if node
  return require('ws');
  // end

  return global.WebSocket || global.MozWebSocket;
}

/**
 * Feature detection for WebSocket.
 *
 * @return {Boolean} whether this transport is available.
 * @api public
 */

function check () {
  return !!ws();
}
<commit_msg>Fixed websocket uri<commit_after>
/**
 * Module dependencies.
 */

var Transport = require('../transport')
  , util = require('../util')
  , global = this

/**
 * Module exports.
 */

module.exports = WS;

/**
 * WebSocket transport constructor.
 *
 * @api {Object} connection options
 * @api public
 */

function WS (opts) {
  Transport.call(this, opts);
};

/**
 * Inherits from Transport.
 */

util.inherits(WS, Transport);

/**
 * Transport name.
 *
 * @api public
 */

WS.prototype.name = 'websocket';

/**
 * Opens socket.
 *
 * @api private
 */

WS.prototype.doOpen = function () {
  if (!check()) {
    // let probe timeout
    return;
  }

  var self = this;

  this.socket = new (ws())(this.uri());
  this.socket.onopen = function () {
    self.onOpen();
  };
  this.socket.onclose = function () {
    self.onClose();
  };
  this.socket.onmessage = function (ev) {
    self.onData(ev.data);
  };
  this.socket.onerror = function (e) {
    self.onError('websocket error', e);
  };
};

/**
 * Writes data to socket.
 *
 * @param {String} data.
 * @param {Function} flush callback.
 * @api private
 */

WS.prototype.write = function (data, fn) {
  this.socket.send(data);
  fn();
};

/**
 * Writes a packets payload.
 *
 * @param {Array} data packets
 * @api private
 */

WS.prototype.writeMany = function (packets) {
  for (var i = 0, l = packets.length; i < l; i++) {
    this.write(packets[0]);
  }
};

/**
 * Closes socket.
 *
 * @api private
 */

WS.prototype.doClose = function () {
  this.socket.close();
};

/**
 * Generates uri for connection.
 *
 * @api private
 */

WS.prototype.uri = function () {
  var query = util.qs(this.query)
    , schema = this.secure ? 'wss' : 'ws'
    , port = ''

  // avoid port if default for schema
  if (this.port && (('wss' == schema && this.port != 443)
    || ('ws' == schema && this.port != 80))) {
    port = ':' + this.port;
  }

  // prepend ? to query
  if (query.length) {
    query = '?' + query;
  }

  return schema + '://' + this.host + port + this.path + query;
};

/**
 * Getter for WS constructor.
 *
 * @api private
 */

function ws () {
  // if node
  return require('ws');
  // end

  return global.WebSocket || global.MozWebSocket;
}

/**
 * Feature detection for WebSocket.
 *
 * @return {Boolean} whether this transport is available.
 * @api public
 */

function check () {
  return !!ws();
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const EXPORTED_SYMBOLS = ['HighlightManager'];

function HighlightManager(opts) {
  /*
   The Highlight Manager abstracts out the task of adjusting the blue highlight
   in the autocomplete popup in response to key and mouse events, as well as
   in response to the recommendation being shown.
   */
  this.win = opts.win;
  this.events = opts.events;
  this.popup = opts.popup;
  // this is a recommendation view, because the highlight
  // class manages passing focus between it and the list of results
  this.recommendation = opts.recommendation;

  this.adjustHighlight = this.adjustHighlight.bind(this);
  this.clearRecommendationHighlight = this.clearRecommendationHighlight.bind(this);
  this.initMutationObserver = this.initMutationObserver.bind(this);
  this.mutationHandler = this.mutationHandler.bind(this);
  this.onRecommendationMouseEnter = this.onRecommendationMouseEnter.bind(this);
  this.onRecommendationMouseLeave = this.onRecommendationMouseLeave.bind(this);
  this.onRecommendationMouseMove = this.onRecommendationMouseMove.bind(this);
  this.stealHighlight = this.stealHighlight.bind(this);
}

HighlightManager.prototype = {
  init: function() {
    this.events.subscribe('navigational-key', this.adjustHighlight);
    // It seems that the recommendation is rarely in the XUL DOM when this init
    // function runs, so listen for the recommendation to be found in the DOM,
    // then try a second time to attach the MutationObserver to the element.
    this.events.subscribe('recommendation-created', this.initMutationObserver);
    this.events.subscribe('recommendation-shown', this.stealHighlight);

    this.attachMouseListeners();
    this.initMutationObserver();
  },
  destroy: function() {
    this.resultsObserver.disconnect();
    this.detachMouseListeners();

    this.events.unsubscribe('recommendation-shown', this.stealHighlight);
    this.events.unsubscribe('navigational-key', this.adjustHighlight);

    delete this.popup;
    delete this.recommendation;
    delete this.win;
  },
  attachMouseListeners: function() {
    // This function is important because it gathers docs for all the mouse
    // event handlers in a single place.

    // When the user mouses into the recommendation, the results list might
    // have had the focus, so swap the highlight. No need to resort to stealing
    // the highlight, because there's no race to update the DOM.
    this.events.subscribe('recommendation-mouseenter', this.onRecommendationMouseEnter);

    // The mouseenter listener fails to apply the highlight to the
    // recommendation in edge cases where the highlight is applied to the
    // results list when the mouse pointer is inside the recommendation. When
    // the user starts moving the mouse, no mouseenter event is fired, because
    // the mouse pointer was already inside the element. This most often
    // happens when the stealHighlight() method fails to steal the highlight
    // (stealHighlight is non-deterministic), but can also reproducibly occur
    // in flows like this example:
    // 1. User mouses over the recommendation, highlighting it
    // 2. User keys away from the recommendation, highlighting a result in the
    //    results list
    // 3. User wiggles the mouse inside the recommendation: no mouseenter is
    //    fired, so no highlight is applied without a mousemove listener.
    //
    // While this is an edge case, the listener is simple, and makes the mouse
    // highlight management code very robust against timing errors.
    this.events.subscribe('recommendation-mousemove', this.onRecommendationMouseMove);

    // When the user mouses out of the recommendation, if the mouse is leaving
    // the popup, keep the recommendation highlighted, matching the mouseout
    // behavior of the results list. If the mouse is moving to the results
    // list, remove the highlight from the recommendation.
    this.events.subscribe('recommendation-mouseleave', this.onRecommendationMouseLeave);

    // When the user mouses over the results list, the recommendation may have
    // the highlight, and should lose the highlight. This happens if the user
    // mouses directly into the results list from outside the popup.
    this.events.subscribe('results-list-mouse-highlighted', this.clearRecommendationHighlight);
  },
  detachMouseListeners: function() {
    this.events.unsubscribe('recommendation-mouseenter', this.stealHighlight);
    this.events.unsubscribe('recommendation-mouseleave', this.onRecommendationMouseLeave);
    this.events.unsubscribe('recommendation-mousemove', this.onRecommendationMouseMove);
    this.events.unsubscribe('results-list-mouse-highlighted', this.clearRecommendationHighlight);
  },
  onRecommendationMouseEnter: function() {
    // See attachMouseListeners() for documentation.
    this.popup.el.richlistbox.selectedIndex = -1;
    this.recommendation.isHighlighted = true;
  },
  onRecommendationMouseMove: function() {
    // See attachMouseListeners() for documentation.
    this.popup.el.richlistbox.selectedIndex = -1;
    this.recommendation.isHighlighted = true;
  },
  onRecommendationMouseLeave: function(evt) {
    // See attachMouseListeners() for documentation.

    // relatedTarget seems to be null when the user mouses out of the popup,
    // even though the pointer may be entering other parts of the XUL DOM.
    if (evt.relatedTarget && evt.relatedTarget.closest('.autocomplete-richlistbox')) {
      this.recommendation.isHighlighted = false;
    }
  },
  initMutationObserver: function() {
    // Use a MutationObserver to detect when results are inserted into the
    // popup. Unfortunately, while the history and suggestions searches return
    // results async, there's no clean way to detect new results in pure JS.
    //
    // Listen for changes to the `url` attribute on the rows inside the results
    // richlistbox. This ensures changes are detected, even when rows are
    // reused, rather than created (see the use of maxRows in _adjustAcItem,
    // inside autocomplete.xml).
    this.resultsObserver = new this.win.MutationObserver(this.mutationHandler);
    const results = this.win.document.getAnonymousElementByAttribute(this.popup.el, 'anonid', 'richlistbox');
    const resultsObserverConfig = {
      childList: true,
      subtree: true,
      attributes: true,
      attributeFilter: ['url']
    };
    this.resultsObserver.observe(results, resultsObserverConfig);
  },
  mutationHandler: function() {
    // If the recommendation exists and is visible, steal the highlight.
    if (this.recommendation.el && !this.recommendation.el.collapsed) {
      this.stealHighlight();
    }
  },
  stealHighlight: function() {
    // If the recommendation is shown, the default behavior is for the urlbar
    // to have the highlight. That is, neither the recommendation nor any items
    // in the results list should be highlighted.
    //
    // We need to retake control of the highlight under two circumstances:
    // 1. When the recommendation has just been shown, if the user isn't keying
    //    through the list already, then move the highlight from the top results
    //    list item to the urlbar.
    // 2. After (1), when a few more results have been inserted into the DOM,
    //    the Gecko code will take the highlight again; steal it back.
    //
    // The existing XBL code batches insertions in timeouts. By default,
    // the code invokes setTimeout 5 times, inserting 6 rows per turn, up
    // to the default max of 30 rows.
    //
    // Because the event loop already has up to 5 timers waiting, we can't
    // rely on setTimeout to steal the highlight: our request will be queued
    // behind all those other timers, causing a noticeably long flicker of the
    // blue highlight on the top result row.
    //
    // Our workaround relies on using requestAnimationFrame to cut in line,
    // interleaving our highlight stealing between the setTimeouts.
    //
    // Specifically, each time stealHighlight is called (in response to a DOM
    // mutation fired when rows are inserted into the list), we ask the browser
    // to steal the highlight three times: immediately, just before the next
    // frame is painted (the outer rAF), and just before the frame after that
    // (the inner rAF).
    //
    // This is a weird hack, but it works fairly well. Because setTimeout makes
    // no guarantees about precisely when a callback will be executed, and
    // because the row insertion timeouts are called closely together, ours is
    // a pretty good, though nondeterministic, solution.
    if (!this.recommendation.el || this.recommendation.el.collapsed) {
      return;
    }
    this.clearHighlight();
    this.win.requestAnimationFrame(() => {
      this.clearHighlight();
      this.win.requestAnimationFrame(() => {
        this.clearHighlight();
      });
    });
  },
  clearHighlight: function() {
    this.popup.el.richlistbox.selectedIndex = -1;
    this.recommendation.isHighlighted = false;
  },
  clearRecommendationHighlight: function() {
    // See attachMouseListeners() for documentation.
    this.recommendation.isHighlighted = false;
  },
  adjustHighlight: function(evt) {
    // Due to constraints in combining XBL and JS, we have to totally take over
    // highlight management for the list of results as well as the recommendation.
    //
    // adjustHighlight resets the DOM highlight state to what we want.
    // It runs in three phases: first, checking the old state; second, clearing
    // the highlight off of all results (putting the DOM into a dirty state);
    // third, modifying the DOM. The idea is to avoid triggering repaints by
    // batching reads and writes, rather than interleaving them.

    // Batch all DOM access here at the start of the function.
    const resultsContainer = this.win.document.getAnonymousElementByAttribute(this.popup.el, 'anonid', 'richlistbox');
    const resultRows = resultsContainer.querySelectorAll('.autocomplete-richlistitem:not([collapsed])');
    const recommendationUrl = this.recommendation.el.querySelector('#universal-search-recommendation-url');
    const listLength = resultRows.length;
    const selectedIndex = this.popup.el.selectedIndex;
    const recommendationVisible = this.recommendation.el && !this.recommendation.el.collapsed;
    const recommendationHighlighted = this.recommendation.el && this.recommendation.isHighlighted;

    // Here's where we will temporarily store the state updates that we'll
    // actually make at the end:
    //
    // newIndex gives the new index that should be highlighted in the results
    // list, or -1 if the list shouldn't be highlighted.
    let newIndex;
    // newUrl is the URL of the new item. It's set in the urlbar as part of
    // the highlight update.
    let newUrl;
    // Also keep track of what we want to do next with the recommendation.
    let shouldHighlightRecommendation;

    // Clear all highlights. The DOM is now dirty and not trustworthy.
    this.clearHighlight();

    // Now, we look at the state of the recommendation and the results list,
    // and decide what should be highlighted next.

    // These utility functions abstract out the state changes, and make the
    // state transition logic more readable.
    function highlightUrlbar() {
      shouldHighlightRecommendation = false;
      newIndex = -1;
      newUrl = null;
    }

    function highlightRecommendation() {
      shouldHighlightRecommendation = true;
      newIndex = -1;
      newUrl = recommendationUrl.getAttribute('data-url');
    }

    function highlightResult(index) {
      shouldHighlightRecommendation = false;
      newIndex = index;
      newUrl = resultRows[newIndex].getAttribute('url');
    }

    function highlightFirstResult() {
      highlightResult(0);
    }

    function highlightLastResult() {
      highlightResult(resultRows.length - 1);
    }

    // If the urlbar has focus,
    // a 'forward' key moves the highlight to the recommendation, if it's
    // visible, or else the first result;
    // a 'backward' key moves the highlight to the bottom of the list.
    if (!recommendationHighlighted && selectedIndex === -1) {
      if (evt.forward) {
        recommendationVisible ? highlightRecommendation() : highlightFirstResult();
      } else {
        highlightLastResult();
      }

    // If the recommendation was highlighted,
    // a 'forward' key moves the highlight to the first result in the list,
    // a 'backward' key moves the highlight to the urlbar.
    } else if (recommendationHighlighted) {
      if (evt.forward) {
        highlightFirstResult();
      } else {
        highlightUrlbar();
      }

    // If the first result in the list was highlighted,
    // a 'forward' key moves the highlight to the 2nd result in the list,
    // a 'backward' key moves the highlight to the recommendation, if it's
    // visible, or else to the urlbar.
    } else if (selectedIndex === 0) {
      if (evt.forward) {
        highlightResult(1);
      } else {
        recommendationVisible ? highlightRecommendation() : highlightUrlbar();
      }

    // If the last result in the list was highlighted,
    // a 'forward' key moves the highlight to the urlbar, scrolling the
    // results list back to the first item,
    // a 'backward' key moves the highlight to the result 2nd from the end
    // of the list.
    //
    // It turns out that scrolling the results list (the 'forward' case)
    // causes the results list to retake the highlight. If the recommendation
    // is shown, we want to steal the highlight back for the urlbar. (If not,
    // the top result item will be highlighted, the expected behavior.)
    //
    // An additional complication: if the mouse pointer is over the popup when
    // the results list scrolls, a mouseover event will be fired on the result
    // that's scrolled underneath the mouse pointer. We want to ignore this
    // event, whether or not the recommendation is visible, so ask the popup
    // to ignore the very next mouseover event it receives.
    } else if (selectedIndex === listLength - 1) {
      if (evt.forward) {
        if (recommendationVisible) {
          this.stealHighlight();
        }
        this.popup.ignoreNextMouseOver = true;
        resultsContainer.ensureIndexIsVisible(0);
        highlightUrlbar();
      } else {
        highlightResult(selectedIndex - 1);
      }

    // If the highlighted result was in the middle of the list,
    // then move the highlight to one of its neighbors.
    } else {
      if (evt.forward) {
        highlightResult(selectedIndex + 1);
      } else {
        highlightResult(selectedIndex - 1);
      }
    }

    // Now that we've figured out which item will take the highlight,
    // update the DOM, and fire a signal that sets the newUrl in the urlbar.
    this.popup.el.selectedIndex = newIndex;
    this.recommendation.isHighlighted = shouldHighlightRecommendation;
    this.events.publish('selection-change', { newUrl: newUrl });
  }
};
<commit_msg>Don't steal highlight if user is interacting with the popup<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const EXPORTED_SYMBOLS = ['HighlightManager'];

function HighlightManager(opts) {
  /*
   The Highlight Manager abstracts out the task of adjusting the blue highlight
   in the autocomplete popup in response to key and mouse events, as well as
   in response to the recommendation being shown.
   */
  this.win = opts.win;
  this.events = opts.events;
  this.popup = opts.popup;
  // this is a recommendation view, because the highlight
  // class manages passing focus between it and the list of results
  this.recommendation = opts.recommendation;

  this.adjustHighlight = this.adjustHighlight.bind(this);
  this.clearRecommendationHighlight = this.clearRecommendationHighlight.bind(this);
  this.initMutationObserver = this.initMutationObserver.bind(this);
  this.mutationHandler = this.mutationHandler.bind(this);
  this.onRecommendationMouseEnter = this.onRecommendationMouseEnter.bind(this);
  this.onRecommendationMouseLeave = this.onRecommendationMouseLeave.bind(this);
  this.onRecommendationMouseMove = this.onRecommendationMouseMove.bind(this);
  this.stealHighlight = this.stealHighlight.bind(this);
}

HighlightManager.prototype = {
  init: function() {
    this.events.subscribe('navigational-key', this.adjustHighlight);
    // It seems that the recommendation is rarely in the XUL DOM when this init
    // function runs, so listen for the recommendation to be found in the DOM,
    // then try a second time to attach the MutationObserver to the element.
    this.events.subscribe('recommendation-created', this.initMutationObserver);
    this.events.subscribe('recommendation-shown', this.stealHighlight);

    this.attachMouseListeners();
    this.initMutationObserver();
  },
  destroy: function() {
    this.resultsObserver.disconnect();
    this.detachMouseListeners();

    this.events.unsubscribe('recommendation-shown', this.stealHighlight);
    this.events.unsubscribe('navigational-key', this.adjustHighlight);

    delete this.popup;
    delete this.recommendation;
    delete this.win;
  },
  attachMouseListeners: function() {
    // This function is important because it gathers docs for all the mouse
    // event handlers in a single place.

    // When the user mouses into the recommendation, the results list might
    // have had the focus, so swap the highlight. No need to resort to stealing
    // the highlight, because there's no race to update the DOM.
    this.events.subscribe('recommendation-mouseenter', this.onRecommendationMouseEnter);

    // The mouseenter listener fails to apply the highlight to the
    // recommendation in edge cases where the highlight is applied to the
    // results list when the mouse pointer is inside the recommendation. When
    // the user starts moving the mouse, no mouseenter event is fired, because
    // the mouse pointer was already inside the element. This most often
    // happens when the stealHighlight() method fails to steal the highlight
    // (stealHighlight is non-deterministic), but can also reproducibly occur
    // in flows like this example:
    // 1. User mouses over the recommendation, highlighting it
    // 2. User keys away from the recommendation, highlighting a result in the
    //    results list
    // 3. User wiggles the mouse inside the recommendation: no mouseenter is
    //    fired, so no highlight is applied without a mousemove listener.
    //
    // While this is an edge case, the listener is simple, and makes the mouse
    // highlight management code very robust against timing errors.
    this.events.subscribe('recommendation-mousemove', this.onRecommendationMouseMove);

    // When the user mouses out of the recommendation, if the mouse is leaving
    // the popup, keep the recommendation highlighted, matching the mouseout
    // behavior of the results list. If the mouse is moving to the results
    // list, remove the highlight from the recommendation.
    this.events.subscribe('recommendation-mouseleave', this.onRecommendationMouseLeave);

    // When the user mouses over the results list, the recommendation may have
    // the highlight, and should lose the highlight. This happens if the user
    // mouses directly into the results list from outside the popup.
    this.events.subscribe('results-list-mouse-highlighted', this.clearRecommendationHighlight);
  },
  detachMouseListeners: function() {
    this.events.unsubscribe('recommendation-mouseenter', this.stealHighlight);
    this.events.unsubscribe('recommendation-mouseleave', this.onRecommendationMouseLeave);
    this.events.unsubscribe('recommendation-mousemove', this.onRecommendationMouseMove);
    this.events.unsubscribe('results-list-mouse-highlighted', this.clearRecommendationHighlight);
  },
  onRecommendationMouseEnter: function() {
    // See attachMouseListeners() for documentation.
    this.popup.el.richlistbox.selectedIndex = -1;
    this.recommendation.isHighlighted = true;
  },
  onRecommendationMouseMove: function() {
    // See attachMouseListeners() for documentation.
    this.popup.el.richlistbox.selectedIndex = -1;
    this.recommendation.isHighlighted = true;
  },
  onRecommendationMouseLeave: function(evt) {
    // See attachMouseListeners() for documentation.

    // relatedTarget seems to be null when the user mouses out of the popup,
    // even though the pointer may be entering other parts of the XUL DOM.
    if (evt.relatedTarget && evt.relatedTarget.closest('.autocomplete-richlistbox')) {
      this.recommendation.isHighlighted = false;
    }
  },
  initMutationObserver: function() {
    // Use a MutationObserver to detect when results are inserted into the
    // popup. Unfortunately, while the history and suggestions searches return
    // results async, there's no clean way to detect new results in pure JS.
    //
    // Listen for changes to the `url` attribute on the rows inside the results
    // richlistbox. This ensures changes are detected, even when rows are
    // reused, rather than created (see the use of maxRows in _adjustAcItem,
    // inside autocomplete.xml).
    this.resultsObserver = new this.win.MutationObserver(this.mutationHandler);
    const results = this.win.document.getAnonymousElementByAttribute(this.popup.el, 'anonid', 'richlistbox');
    const resultsObserverConfig = {
      childList: true,
      subtree: true,
      attributes: true,
      attributeFilter: ['url']
    };
    this.resultsObserver.observe(results, resultsObserverConfig);
  },
  mutationHandler: function() {
    // If the recommendation exists and is visible, steal the highlight.
    if (this.recommendation.el && !this.recommendation.el.collapsed) {
      this.stealHighlight();
    }
  },
  stealHighlight: function() {
    // If the recommendation is shown, the default behavior is for the urlbar
    // to have the highlight. That is, neither the recommendation nor any items
    // in the results list should be highlighted.
    //
    // We need to retake control of the highlight under two circumstances:
    // 1. When the recommendation has just been shown, if the user isn't keying
    //    through the list already, then move the highlight from the top results
    //    list item to the urlbar.
    // 2. After (1), when a few more results have been inserted into the DOM,
    //    the Gecko code will take the highlight again; steal it back.
    //
    // The existing XBL code batches insertions in timeouts. By default,
    // the code invokes setTimeout 5 times, inserting 6 rows per turn, up
    // to the default max of 30 rows.
    //
    // Because the event loop already has up to 5 timers waiting, we can't
    // rely on setTimeout to steal the highlight: our request will be queued
    // behind all those other timers, causing a noticeably long flicker of the
    // blue highlight on the top result row.
    //
    // Our workaround relies on using requestAnimationFrame to cut in line,
    // interleaving our highlight stealing between the setTimeouts.
    //
    // Specifically, each time stealHighlight is called (in response to a DOM
    // mutation fired when rows are inserted into the list), we ask the browser
    // to steal the highlight three times: immediately, just before the next
    // frame is painted (the outer rAF), and just before the frame after that
    // (the inner rAF).
    //
    // This is a weird hack, but it works fairly well. Because setTimeout makes
    // no guarantees about precisely when a callback will be executed, and
    // because the row insertion timeouts are called closely together, ours is
    // a pretty good, though nondeterministic, solution.
    if (!this.recommendation.el || this.recommendation.el.collapsed) {
      return;
    }
    this.clearHighlight();
    this.win.requestAnimationFrame(() => {
      this.clearHighlight();
      this.win.requestAnimationFrame(() => {
        this.clearHighlight();
      });
    });
  },
  clearHighlight: function() {
    // If the selectedIndex > 0, the user is keying through the list, so don't
    // do anything to the highlight.
    if (this.popup.el.richlistbox.selectedIndex > 0) {
      return;
    }
    this.popup.el.richlistbox.selectedIndex = -1;
    this.recommendation.isHighlighted = false;
  },
  clearRecommendationHighlight: function() {
    // See attachMouseListeners() for documentation.
    this.recommendation.isHighlighted = false;
  },
  adjustHighlight: function(evt) {
    // Due to constraints in combining XBL and JS, we have to totally take over
    // highlight management for the list of results as well as the recommendation.
    //
    // adjustHighlight resets the DOM highlight state to what we want.
    // It runs in three phases: first, checking the old state; second, clearing
    // the highlight off of all results (putting the DOM into a dirty state);
    // third, modifying the DOM. The idea is to avoid triggering repaints by
    // batching reads and writes, rather than interleaving them.

    // Batch all DOM access here at the start of the function.
    const resultsContainer = this.win.document.getAnonymousElementByAttribute(this.popup.el, 'anonid', 'richlistbox');
    const resultRows = resultsContainer.querySelectorAll('.autocomplete-richlistitem:not([collapsed])');
    const recommendationUrl = this.recommendation.el.querySelector('#universal-search-recommendation-url');
    const listLength = resultRows.length;
    const selectedIndex = this.popup.el.selectedIndex;
    const recommendationVisible = this.recommendation.el && !this.recommendation.el.collapsed;
    const recommendationHighlighted = this.recommendation.el && this.recommendation.isHighlighted;

    // Here's where we will temporarily store the state updates that we'll
    // actually make at the end:
    //
    // newIndex gives the new index that should be highlighted in the results
    // list, or -1 if the list shouldn't be highlighted.
    let newIndex;
    // newUrl is the URL of the new item. It's set in the urlbar as part of
    // the highlight update.
    let newUrl;
    // Also keep track of what we want to do next with the recommendation.
    let shouldHighlightRecommendation;

    // Clear all highlights. The DOM is now dirty and not trustworthy.
    this.clearHighlight();

    // Now, we look at the state of the recommendation and the results list,
    // and decide what should be highlighted next.

    // These utility functions abstract out the state changes, and make the
    // state transition logic more readable.
    function highlightUrlbar() {
      shouldHighlightRecommendation = false;
      newIndex = -1;
      newUrl = null;
    }

    function highlightRecommendation() {
      shouldHighlightRecommendation = true;
      newIndex = -1;
      newUrl = recommendationUrl.getAttribute('data-url');
    }

    function highlightResult(index) {
      shouldHighlightRecommendation = false;
      newIndex = index;
      newUrl = resultRows[newIndex].getAttribute('url');
    }

    function highlightFirstResult() {
      highlightResult(0);
    }

    function highlightLastResult() {
      highlightResult(resultRows.length - 1);
    }

    // If the urlbar has focus,
    // a 'forward' key moves the highlight to the recommendation, if it's
    // visible, or else the first result;
    // a 'backward' key moves the highlight to the bottom of the list.
    if (!recommendationHighlighted && selectedIndex === -1) {
      if (evt.forward) {
        recommendationVisible ? highlightRecommendation() : highlightFirstResult();
      } else {
        highlightLastResult();
      }

    // If the recommendation was highlighted,
    // a 'forward' key moves the highlight to the first result in the list,
    // a 'backward' key moves the highlight to the urlbar.
    } else if (recommendationHighlighted) {
      if (evt.forward) {
        highlightFirstResult();
      } else {
        highlightUrlbar();
      }

    // If the first result in the list was highlighted,
    // a 'forward' key moves the highlight to the 2nd result in the list,
    // a 'backward' key moves the highlight to the recommendation, if it's
    // visible, or else to the urlbar.
    } else if (selectedIndex === 0) {
      if (evt.forward) {
        highlightResult(1);
      } else {
        recommendationVisible ? highlightRecommendation() : highlightUrlbar();
      }

    // If the last result in the list was highlighted,
    // a 'forward' key moves the highlight to the urlbar, scrolling the
    // results list back to the first item,
    // a 'backward' key moves the highlight to the result 2nd from the end
    // of the list.
    //
    // It turns out that scrolling the results list (the 'forward' case)
    // causes the results list to retake the highlight. If the recommendation
    // is shown, we want to steal the highlight back for the urlbar. (If not,
    // the top result item will be highlighted, the expected behavior.)
    //
    // An additional complication: if the mouse pointer is over the popup when
    // the results list scrolls, a mouseover event will be fired on the result
    // that's scrolled underneath the mouse pointer. We want to ignore this
    // event, whether or not the recommendation is visible, so ask the popup
    // to ignore the very next mouseover event it receives.
    } else if (selectedIndex === listLength - 1) {
      if (evt.forward) {
        if (recommendationVisible) {
          this.stealHighlight();
        }
        this.popup.ignoreNextMouseOver = true;
        resultsContainer.ensureIndexIsVisible(0);
        highlightUrlbar();
      } else {
        highlightResult(selectedIndex - 1);
      }

    // If the highlighted result was in the middle of the list,
    // then move the highlight to one of its neighbors.
    } else {
      if (evt.forward) {
        highlightResult(selectedIndex + 1);
      } else {
        highlightResult(selectedIndex - 1);
      }
    }

    // Now that we've figured out which item will take the highlight,
    // update the DOM, and fire a signal that sets the newUrl in the urlbar.
    this.popup.el.selectedIndex = newIndex;
    this.recommendation.isHighlighted = shouldHighlightRecommendation;
    this.events.publish('selection-change', { newUrl: newUrl });
  }
};
<|endoftext|>


<commit_before>/**
 *  Manager
 *  =======
 *
 *  A helper for handling connections and delegate communication.
 *
 *  manager - internaly handling communication + tasks || refactor for player
 */

// used for structure
var LATENCY      = {},
    priorityList = [];

var Manager = (function(){

  // checkConnections,
  function check ( remoteList, transport ) {

    if ( !remoteList ) return;

    if ( !Array.isArray(remoteList) ) remoteList = [ remoteList ];

    var localID  = INSTANCE.id,

        remoteID;

    for ( var i = 0, l = remoteList.length; i < l; i++ ) {

      remoteID = remoteList[i];

      if ( remoteID !== localID && !CONNECTIONS[ remoteID ] ) {

        connect( remoteID, true, transport );
      }
    }
  }

  // find partners + connect || can be used by the dev via noServer as well
  function connect ( remoteID, initiator, transport ) {

    if ( CONNECTIONS[ remoteID ] ) return;

    console.log( '[connect] to - "' + remoteID + '"' );

    CONNECTIONS[ remoteID ] = new Connection( INSTANCE.id, remoteID, initiator, transport );

    pg.peers[ remoteID ] = new Peer({ id: remoteID });
  }

  // clears references + triggers callbacks on disconnect
  function disconnect ( remoteID ) {

    INSTANCE.emit( 'disconnect', pg.peers[ remoteID ] );

    pg.data.splice( dataMap[remoteID], 1 );

    delete pg.peers[ remoteID ];
    delete CONNECTIONS[ remoteID ];
  }

  // setCredentials: create new reference + SDP & Candidates
  function set ( msg, transport ) {

    if ( !CONNECTIONS[ msg.local] ) connect( msg.local, false, transport );

    CONNECTIONS[ msg.local ][ msg.action ]( msg.data );
  }

  // change values through a *multicast*
  function update ( key, value ) {

    var ids = Object.keys( CONNECTIONS );

    // share with all
    for ( var i = 0, l = ids.length; i < l; i++ ) {

      CONNECTIONS[ ids[i] ].send( 'update', { key: key, value: value });
    }
  }

  var diff, start;

  function pong ( remoteID, data ) {

    var end = win.performance.now();

    diff = end - start;

    console.log( diff );
  }

  var timer = {};

  // sends a ping - if used with the right parameter | used for starting the benchmark
  function test ( remoteID, index, pong ) {

    var col;

    if ( !pong ) {

      var conn = CONNECTIONS[ remoteID ],

          num  = 100;

      col = timer[ remoteID ] = [ num ];

      for ( var i = 1; i <= num; i++ ) {

        col[i] = win.performance.now();

        conn.send( 'ping', { index: i });
      }

      return;
    }

    col = timer[ remoteID ];

    col[index] = win.performance.now() - col[index];

    if ( --col[0] > 0 ) return;

    var mean = col.reduce( sum ) / ( col.length - 1 );

    // check the latency
    LATENCY[ remoteID ] = mean;

    // sort - ID - times to priority list
    //priorityList
  }

  function sum ( prev, curr ) { return prev + curr; }

  return {

    check      : check,
    connect    : connect,
    disconnect : disconnect,
    set        : set,
    test       : test,
    update     : update
  };

})();
<commit_msg>fix disconnect<commit_after>/**
 *  Manager
 *  =======
 *
 *  A helper for handling connections and delegate communication.
 *
 *  manager - internaly handling communication + tasks || refactor for player
 */

// used for structure
var LATENCY      = {},
    priorityList = [];

var Manager = (function(){

  // checkConnections,
  function check ( remoteList, transport ) {

    if ( !remoteList ) return;

    if ( !Array.isArray(remoteList) ) remoteList = [ remoteList ];

    var localID  = INSTANCE.id,

        remoteID;

    for ( var i = 0, l = remoteList.length; i < l; i++ ) {

      remoteID = remoteList[i];

      if ( remoteID !== localID && !CONNECTIONS[ remoteID ] ) {

        connect( remoteID, true, transport );
      }
    }
  }

  // find partners + connect || can be used by the dev via noServer as well
  function connect ( remoteID, initiator, transport ) {

    if ( CONNECTIONS[ remoteID ] ) return;

    console.log( '[connect] to - "' + remoteID + '"' );

    CONNECTIONS[ remoteID ] = new Connection( INSTANCE.id, remoteID, initiator, transport );

    pg.peers[ remoteID ] = new Peer({ id: remoteID });
  }

  // clears references + triggers callbacks on disconnect
  function disconnect ( remoteID ) {

    var peer = pg.peers[ remoteID ];

    INSTANCE.emit( 'disconnect', peer );
    ROOM    .emit( 'leave'     , peer );

    CONNECTIONS[ remoteID ].close();

    pg.data.splice( dataMap[ remoteID ], 1 );

    delete pg.peers[ remoteID ];
    delete CONNECTIONS[ remoteID ];
  }

  // setCredentials: create new reference + SDP & Candidates
  function set ( msg, transport ) {

    if ( !CONNECTIONS[ msg.local] ) connect( msg.local, false, transport );

    CONNECTIONS[ msg.local ][ msg.action ]( msg.data );
  }

  // change values through a *multicast*
  function update ( key, value ) {

    var ids = Object.keys( CONNECTIONS );

    // share with all
    for ( var i = 0, l = ids.length; i < l; i++ ) {

      CONNECTIONS[ ids[i] ].send( 'update', { key: key, value: value });
    }
  }

  var diff, start;

  function pong ( remoteID, data ) {

    var end = win.performance.now();

    diff = end - start;

    console.log( diff );
  }

  var timer = {};

  // sends a ping - if used with the right parameter | used for starting the benchmark
  function test ( remoteID, index, pong ) {

    var col;

    if ( !pong ) {

      var conn = CONNECTIONS[ remoteID ],

          num  = 100;

      col = timer[ remoteID ] = [ num ];

      for ( var i = 1; i <= num; i++ ) {

        col[i] = win.performance.now();

        conn.send( 'ping', { index: i });
      }

      return;
    }

    col = timer[ remoteID ];

    col[index] = win.performance.now() - col[index];

    if ( --col[0] > 0 ) return;

    var mean = col.reduce( sum ) / ( col.length - 1 );

    // check the latency
    LATENCY[ remoteID ] = mean;

    // sort - ID - times to priority list
    //priorityList
  }

  function sum ( prev, curr ) { return prev + curr; }

  return {

    check      : check,
    connect    : connect,
    disconnect : disconnect,
    set        : set,
    test       : test,
    update     : update
  };

})();
<|endoftext|>


<commit_before>/**
 * jCore v0.2.0
 * (c) 2016 iOnStage
 * Released under the MIT License.
 */

(function(global) {
  'use strict';

  if (!global.requestAnimationFrame) {
    global.requestAnimationFrame = function(callback) {
      return setTimeout(callback, 1000 / 60);
    };
  }

  var inherits = function(ctor, superCtor) {
    ctor.prototype = Object.create(superCtor.prototype, {
      constructor: {
        value: ctor,
        enumerable: false,
        writable: true,
        configurable: true,
      },
    });
  };

  var lastIndexOf = function(array, value) {
    for (var i = array.length - 1; i >= 0; i--) {
      if (array[i] === value) {
        return i;
      }
    }
    return -1;
  };

  var Component = function(props) {
    this.element = this.prop(props.element || this.render());
    this.parentElement = this.prop(this.element().parentNode);
    this.relations = [];
  };

  Component.prototype.prop = function(initialValue, defaultValue, converter) {
    var hasConverter = (typeof converter === 'function');
    var cache = hasConverter ? converter(initialValue, defaultValue) : initialValue;
    return function(value) {
      if (typeof value === 'undefined') {
        return cache;
      }
      if (value === cache) {
        return;
      }
      cache = hasConverter ? converter(value, cache) : value;
      this.markDirty();
    };
  };

  Component.prototype.redraw = function() {};

  Component.prototype.markDirty = function() {
    Component.markDirty(this);
  };

  Component.prototype.render = function() {
    return document.createElement('div');
  };

  Component.prototype.oninit = function() {};

  Component.markDirty = (function() {
    var dirtyComponents = [];
    var requestId = null;

    var updateRelations = function(index) {
      for (var ci = index, clen = dirtyComponents.length; ci < clen; ci++) {
        var component = dirtyComponents[ci];
        var relations = component.relations;
        for (var ri = 0, rlen = relations.length; ri < rlen; ri++) {
          relations[ri].update(component);
        }
      }

      // may be inserted other dirty components by updating relations
      if (dirtyComponents.length > clen) {
        updateRelations(clen);
      }
    };

    var callback = function() {
      updateRelations(0);
      for (var i = 0, len = dirtyComponents.length; i < len; i++) {
        dirtyComponents[i].redraw();
      }
      dirtyComponents = [];
      requestId = null;
    };

    return function(component) {
      if (lastIndexOf(dirtyComponents, component) === -1) {
        dirtyComponents.push(component);
      }
      if (requestId !== null) {
        return;
      }
      requestId = global.requestAnimationFrame(callback);
    };
  })();

  Component.inherits = function(initializer) {
    var superCtor = this;
    var ctor = function() {
      var props = (arguments.length !== 0 ? arguments[0] : {});
      superCtor.call(this, props);
      if (typeof initializer === 'function') {
        initializer.call(this, props);
      }
      if (this.constructor === ctor) {
        this.oninit();
      }
    };
    inherits(ctor, superCtor);
    ctor.inherits = superCtor.inherits;
    return ctor;
  };

  var Relation = function() {};

  Relation.prototype.update = function() {};

  var jCore = {
    Component: Component,
    Relation: Relation
  };

  if (typeof module !== 'undefined' && module.exports) {
    module.exports = jCore;
  } else {
    global.jCore = jCore;
  }
})(this);
<commit_msg>Fix a few minor coding style issues<commit_after>/**
 * jCore v0.2.0
 * (c) 2016 iOnStage
 * Released under the MIT License.
 */

(function(global) {
  'use strict';

  if (!global.requestAnimationFrame) {
    global.requestAnimationFrame = function(callback) {
      return setTimeout(callback, 1000 / 60);
    };
  }

  var inherits = function(ctor, superCtor) {
    ctor.prototype = Object.create(superCtor.prototype, {
      constructor: {
        value: ctor,
        enumerable: false,
        writable: true,
        configurable: true,
      },
    });
  };

  var lastIndexOf = function(array, value) {
    for (var i = array.length - 1; i >= 0; i--) {
      if (array[i] === value) {
        return i;
      }
    }
    return -1;
  };

  var Component = function(props) {
    this.element = this.prop(props.element || this.render());
    this.parentElement = this.prop(this.element().parentNode);
    this.relations = [];
  };

  Component.prototype.prop = function(initialValue, defaultValue, converter) {
    var hasConverter = (typeof converter === 'function');
    var cache = (hasConverter ? converter(initialValue, defaultValue) : initialValue);
    return function(value) {
      if (typeof value === 'undefined') {
        return cache;
      }
      if (value === cache) {
        return;
      }
      cache = (hasConverter ? converter(value, cache) : value);
      this.markDirty();
    };
  };

  Component.prototype.redraw = function() {};

  Component.prototype.markDirty = function() {
    Component.markDirty(this);
  };

  Component.prototype.render = function() {
    return document.createElement('div');
  };

  Component.prototype.oninit = function() {};

  Component.markDirty = (function() {
    var dirtyComponents = [];
    var requestId = null;

    var updateRelations = function(index) {
      for (var ci = index, clen = dirtyComponents.length; ci < clen; ci++) {
        var component = dirtyComponents[ci];
        var relations = component.relations;
        for (var ri = 0, rlen = relations.length; ri < rlen; ri++) {
          relations[ri].update(component);
        }
      }

      // may be inserted other dirty components by updating relations
      if (dirtyComponents.length > clen) {
        updateRelations(clen);
      }
    };

    var callback = function() {
      updateRelations(0);
      for (var i = 0, len = dirtyComponents.length; i < len; i++) {
        dirtyComponents[i].redraw();
      }
      dirtyComponents = [];
      requestId = null;
    };

    return function(component) {
      if (lastIndexOf(dirtyComponents, component) === -1) {
        dirtyComponents.push(component);
      }
      if (requestId !== null) {
        return;
      }
      requestId = global.requestAnimationFrame(callback);
    };
  })();

  Component.inherits = function(initializer) {
    var superCtor = this;
    var ctor = function() {
      var props = (arguments.length !== 0 ? arguments[0] : {});
      superCtor.call(this, props);
      if (typeof initializer === 'function') {
        initializer.call(this, props);
      }
      if (this.constructor === ctor) {
        this.oninit();
      }
    };
    inherits(ctor, superCtor);
    ctor.inherits = superCtor.inherits;
    return ctor;
  };

  var Relation = function() {};

  Relation.prototype.update = function() {};

  var jCore = {
    Component: Component,
    Relation: Relation,
  };

  if (typeof module !== 'undefined' && module.exports) {
    module.exports = jCore;
  } else {
    global.jCore = jCore;
  }
})(this);
<|endoftext|>