{{title}}
+Welcome
+{{title}}
+Welcome {{user.username}}
++ {{#if room.roomname}} + {{room.roomname}} + {{else}} + Enter a room to continue... + {{/if}} +
+-
+ {{#each users as |user|}}
+
- + {{user.username}} + + {{/each}} +
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fb7b616 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/node_modules/ +.env \ No newline at end of file diff --git a/README.md b/README.md index 299362a..84b9298 100644 --- a/README.md +++ b/README.md @@ -1,2 +1 @@ -# project_superchat -Build a realtime multi-room chat application. Make it super. +Andrew Senner and Benny Soung diff --git a/app.js b/app.js new file mode 100644 index 0000000..b52121e --- /dev/null +++ b/app.js @@ -0,0 +1,52 @@ +var express = require('express'); +var path = require('path'); +var favicon = require('serve-favicon'); +var logger = require('morgan'); +var cookieParser = require('cookie-parser'); +var bodyParser = require('body-parser'); +var exphbs = require('express-handlebars'); + +var index = require('./routes/index'); +var login = require('./routes/login'); +var users = require('./routes/users'); +var room = require('./routes/room'); + +var app = express(); + +// view engine setup +app.engine('handlebars', exphbs({ defaultLayout: 'main' })); +app.set('views', path.join(__dirname, 'views')); +app.set('view engine', 'handlebars'); + +// uncomment after placing your favicon in /public +//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); +app.use(logger('dev')); +app.use(bodyParser.json()); +app.use(bodyParser.urlencoded({ extended: false })); +app.use(cookieParser()); +app.use(express.static(path.join(__dirname, 'public'))); + +app.use('/', index); +app.use('/login', login); +app.use('/users', users); +app.use('/room', room); + +// catch 404 and forward to error handler +app.use(function(req, res, next) { + var err = new Error('Not Found'); + err.status = 404; + next(err); +}); + +// error handler +app.use(function(err, req, res, next) { + // set locals, only providing error in development + res.locals.message = err.message; + res.locals.error = req.app.get('env') === 'development' ? err : {}; + + // render the error page + res.status(err.status || 500); + res.render('error'); +}); + +module.exports = app; diff --git a/bin/www b/bin/www new file mode 100755 index 0000000..c57e176 --- /dev/null +++ b/bin/www @@ -0,0 +1,98 @@ +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +const express = require('express'); +var app = require('../app'); +var debug = require('debug')('project-superchat:server'); +var http = require('http'); + +/** + * Get port from environment and store in Express. + */ + +var port = normalizePort(process.env.PORT || '3000'); +app.set('port', port); + +/** + * Create HTTP server. + */ + +var server = http.createServer(app); + +/** + * Create socket server. + */ +app.use( + '/socket.io', + express.static(__dirname + 'node_modules/socket.io-client/dist/') +); +const io = require('socket.io')(server); +const socketWrapper = require('../lib/socket_wrapper'); +io.on('connection', socketWrapper(io)); + +/** + * Listen on provided port, on all network interfaces. + */ +server.listen(port); +server.on('error', onError); +server.on('listening', onListening); + +/** + * Normalize a port into a number, string, or false. + */ + +function normalizePort(val) { + var port = parseInt(val, 10); + + if (isNaN(port)) { + // named pipe + return val; + } + + if (port >= 0) { + // port number + return port; + } + + return false; +} + +/** + * Event listener for HTTP server "error" event. + */ + +function onError(error) { + if (error.syscall !== 'listen') { + throw error; + } + + var bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port; + + // handle specific listen errors with friendly messages + switch (error.code) { + case 'EACCES': + console.error(bind + ' requires elevated privileges'); + process.exit(1); + break; + case 'EADDRINUSE': + console.error(bind + ' is already in use'); + process.exit(1); + break; + default: + console.error(error.stack); + throw error; + } +} + +/** + * Event listener for HTTP server "listening" event. + */ + +function onListening() { + var addr = server.address(); + var bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port; + debug('Listening on ' + bind); +} diff --git a/db.js b/db.js new file mode 100644 index 0000000..5c07fb1 --- /dev/null +++ b/db.js @@ -0,0 +1,10 @@ +const { saveModule } = require('./lib/redis_wrapper'); +const { saveUser, saveMessage, saveRoom } = saveModule; + +const { hashSync: encodePassword } = require('bcryptjs'); + +for (let r = 0; r < 5; r++) { + saveRoom({ + roomname: `An Awesome Room ${r + 1}` + }); +} diff --git a/lib/redis_wrapper/index.js b/lib/redis_wrapper/index.js new file mode 100644 index 0000000..0d39b29 --- /dev/null +++ b/lib/redis_wrapper/index.js @@ -0,0 +1,11 @@ +// Require modules. +const redis = require('redis'); + +// Promisify everything. +const Promise = require('bluebird'); +Promise.promisifyAll(redis.RedisClient.prototype); + +module.exports = { + loadModule: require('./load_module'), + saveModule: require('./save_module') +}; diff --git a/lib/redis_wrapper/load_module/get_messages_by_type_id.js b/lib/redis_wrapper/load_module/get_messages_by_type_id.js new file mode 100644 index 0000000..b66997f --- /dev/null +++ b/lib/redis_wrapper/load_module/get_messages_by_type_id.js @@ -0,0 +1,25 @@ +let redisClient = require('redis'); + +if (process.env.ENV_NODE && process.env.ENV_NODE === 'production') { + redisClient = redisClient.createClient(process.env.REDIS_URL); +} else { + redisClient = redisClient.createClient(process.env.REDIS_URL); +} + +const getModelData = require('./get_model_data'); + +const _filterMessages = (type, typeId) => messages => { + type = type.slice(0, -1); + return messages.filter(msg => { + return +msg[`${type}_id`] === +typeId; + }); +}; + +module.exports = function(type, typeId) { + if (typeId !== undefined && !isNaN(+typeId)) { + // Get all messages by their type id. + return getModelData('messages').then(_filterMessages(type, typeId)); + } else { + return Promise.reject(Error('Invalid type id.')); + } +}; diff --git a/lib/redis_wrapper/load_module/get_model_data.js b/lib/redis_wrapper/load_module/get_model_data.js new file mode 100644 index 0000000..05f7a09 --- /dev/null +++ b/lib/redis_wrapper/load_module/get_model_data.js @@ -0,0 +1,63 @@ +let redisClient = require('redis'); + +if (process.env.ENV_NODE && process.env.ENV_NODE === 'production') { + redisClient = redisClient.createClient(process.env.REDIS_URL); +} else { + redisClient = redisClient.createClient(process.env.REDIS_URL); +} + +// Generic function which handles getting model data from redis. +module.exports = function(type, typeId) { + // Make sure we have a type at the very minimum. + if (type === undefined || typeof type !== 'string') return []; + + // Did the user pass in an id or array of ids? + if (typeId !== undefined) { + if (!isNaN(+typeId)) { + // If typeId is a number, we're fetching + // a single entry of type. + return _fetchSingleType(type, typeId); + } else if (Array.isArray(typeId)) { + // If typeId is an array, we're fetching + // multiple entries of type, that correspond + // to the ids in the array. + return _fetchByTypeWithIds(type, typeId); + } else { + // Invalid parameter passed. + return Promise.reject(Error('Invalid argument type for typeId')); + } + } + // Grab all entries of a certain type. + return _grabAllByType(type); +}; + +// Get a single type from redis. +function _fetchSingleType(type, id) { + return redisClient.hgetallAsync(`${type}:${id}`).then(type => { + return type ? [type] : []; + }); +} + +// Get multiple of type from redis. +function _fetchByTypeWithIds(type, typeIds) { + // Fetch all entries of type. + return _grabAllByType(type).then(entries => { + // Filter through results and only keep + // entries that are in the typeIds array. + return entries.filter(type => { + return typeIds.includes(type.id); + }); + }); +} + +// Grab all by a certain type (helper); +function _grabAllByType(type) { + return redisClient.keysAsync(`${type}:*`).then(_getTypeData); +} + +// Gets corresponding entries in redis from _grabAllByType. +function _getTypeData(typeKeys) { + return Promise.all( + typeKeys.map(typeKey => redisClient.hgetallAsync(typeKey)) + ); +} diff --git a/lib/redis_wrapper/load_module/get_users_by_room_id.js b/lib/redis_wrapper/load_module/get_users_by_room_id.js new file mode 100644 index 0000000..e771808 --- /dev/null +++ b/lib/redis_wrapper/load_module/get_users_by_room_id.js @@ -0,0 +1,20 @@ +let redisClient = require('redis'); + +if (process.env.ENV_NODE && process.env.ENV_NODE === 'production') { + redisClient = redisClient.createClient(process.env.REDIS_URL); +} else { + redisClient = redisClient.createClient(process.env.REDIS_URL); +} + +const getModelData = require('./get_model_data'); + +const _getUsersByRoom = roomId => users => { + if (!users || users.length === 0) return []; + return users.filter(user => +user.room_id === +roomId); +}; + +module.exports = function(roomId) { + if (roomId !== undefined && !isNaN(+roomId)) { + return getModelData('users').then(_getUsersByRoom(roomId)); + } +}; diff --git a/lib/redis_wrapper/load_module/index.js b/lib/redis_wrapper/load_module/index.js new file mode 100644 index 0000000..7813e51 --- /dev/null +++ b/lib/redis_wrapper/load_module/index.js @@ -0,0 +1,12 @@ +const getModelData = require('./get_model_data'); +const getMessagesByTypeId = require('./get_messages_by_type_id'); + +module.exports = { + getUsers: id => getModelData('users', id), + getMessages: id => getModelData('messages', id), + getRooms: id => getModelData('rooms', id), + getMessagesByRoomId: id => getMessagesByTypeId('rooms', id), + getMessagesByUserId: id => getMessagesByTypeId('users', id), + getUsersByRoomId: require('./get_users_by_room_id'), + typeExists: require('./type_exists') +}; diff --git a/lib/redis_wrapper/load_module/type_exists.js b/lib/redis_wrapper/load_module/type_exists.js new file mode 100644 index 0000000..b364a00 --- /dev/null +++ b/lib/redis_wrapper/load_module/type_exists.js @@ -0,0 +1,9 @@ +const getModelData = require('./get_model_data'); + +module.exports = (dataType, name) => { + return getModelData(dataType).then(types => { + return types.some( + type => type[dataType.slice(0, -1).concat('name')] === name + ); + }); +}; diff --git a/lib/redis_wrapper/save_module/index.js b/lib/redis_wrapper/save_module/index.js new file mode 100644 index 0000000..7faf314 --- /dev/null +++ b/lib/redis_wrapper/save_module/index.js @@ -0,0 +1,11 @@ +const saveModelData = require('./save_model_data'); +const updateTypeData = require('./update_type_data'); + +module.exports = { + saveUser: data => saveModelData('users', data), + saveMessage: data => saveModelData('messages', data), + saveRoom: data => saveModelData('rooms', data), + updateUserData: (id, field, value) => { + return updateTypeData('users', id, field, value); + } +}; diff --git a/lib/redis_wrapper/save_module/save_model_data.js b/lib/redis_wrapper/save_module/save_model_data.js new file mode 100644 index 0000000..a5176cc --- /dev/null +++ b/lib/redis_wrapper/save_module/save_model_data.js @@ -0,0 +1,38 @@ +let redisClient = require('redis'); + +if (process.env.ENV_NODE && process.env.ENV_NODE === 'production') { + redisClient = redisClient.createClient(process.env.REDIS_URL); +} else { + redisClient = redisClient.createClient(process.env.REDIS_URL); +} + +const Promise = require('bluebird'); +// Data map to check for valid entries to be saved +const dataMap = { + users: ['username', 'password', 'room_id'], + messages: ['body', 'gmt_created', 'user_id', 'room_id'], + rooms: ['roomname'] +}; + +const _validateEntryData = data => type => { + return Object.keys(data).every(key => dataMap[type].includes(key)); +}; + +// Generic function which handles saving model data to redis. +module.exports = function(type, data) { + // console.log(data, "whats this"); + if (type === undefined || typeof type !== 'string') { + return Promise.reject(Error('Invalid argument type for type')); + } + if (!_validateEntryData(data)(type)) { + return Promise.reject( + Error('Incorrect data structure passed, aborting save.') + ); + } + + // Increment our counter, then save our new entry. + return redisClient.incrAsync(`counts:${type}`).then(newId => { + data.id = newId; + return redisClient.hmsetAsync(`${type}:${data.id}`, data); + }); +}; diff --git a/lib/redis_wrapper/save_module/update_type_data.js b/lib/redis_wrapper/save_module/update_type_data.js new file mode 100644 index 0000000..2b58ded --- /dev/null +++ b/lib/redis_wrapper/save_module/update_type_data.js @@ -0,0 +1,11 @@ +let redisClient = require('redis'); + +if (process.env.ENV_NODE && process.env.ENV_NODE === 'production') { + redisClient = redisClient.createClient(process.env.REDIS_URL); +} else { + redisClient = redisClient.createClient(process.env.REDIS_URL); +} + +module.exports = function(type, id, field, value) { + return redisClient.hsetAsync(`${type}:${id}`, field, value); +}; diff --git a/lib/socket_wrapper/index.js b/lib/socket_wrapper/index.js new file mode 100644 index 0000000..bcd5f0b --- /dev/null +++ b/lib/socket_wrapper/index.js @@ -0,0 +1,83 @@ +const { saveMessage, saveUser } = require('../redis_wrapper').saveModule; +const { userExists, getUsers } = require('../redis_wrapper').loadModule; + +const { + sendUserCreated, + sendMessageCreated, + sendRoomCreated, + sendRoomJoined, + sendRoomLeft +} = require('./send_message'); +const { + handleNewUser, + handleNewMessage, + handleNewRoom, + handleJoinRoom, + handleLeaveRoom +} = require('./receive_message'); + +// Generic constants +const MSG_CONNECTION_CONFIRM = 'New connection request granted'; + +// Event mesages +const EVENT_SEND_MESSAGE = 'send_message'; + +// Event users +const EVENT_NEW_USER = 'new_user'; +const EVENT_ERROR_USER_EXISTS = 'error_user_exists'; + +// Event rooms +const EVENT_NEW_ROOM = 'new_room'; +const EVENT_JOIN_ROOM = 'join_room'; +const EVENT_LEAVE_ROOM = 'leave_room'; +const EVENT_ERROR_ROOM_EXISTS = 'error_room_exists'; + +/* + Main Page + */ +// new room created +// new user created + +/* + Room Page + */ +// user joins room +// user leaves room +// send message + +module.exports = io => client => { + console.log(MSG_CONNECTION_CONFIRM); + + // Assign event handlers. + // Messages + client.on(EVENT_SEND_MESSAGE, msgObj => { + handleNewMessage(msgObj).then(sendMessageCreated(io)); + }); + + // Users + client.on(EVENT_NEW_USER, userObj => { + handleNewUser(userObj).then(sendUserCreated(io), userObj => { + io.emit(EVENT_ERROR_USER_EXISTS, userObj); + }); + }); + + // Rooms + client.on(EVENT_NEW_ROOM, roomObj => { + console.log(roomObj); + handleNewRoom(roomObj).then(sendRoomCreated(io), roomObj => { + io.emit(EVENT_ERROR_ROOM_EXISTS, roomObj); + }); + }); + + client.on(EVENT_JOIN_ROOM, roomObj => { + handleLeaveRoom({ + id: roomObj.old_id, + user_id: roomObj.user_id + }).then(sendRoomLeft(io)); + handleJoinRoom(roomObj).then(sendRoomJoined(io)); + }); + + client.on(EVENT_LEAVE_ROOM, roomObj => { + handleLeaveRoom(roomObj).then(sendRoomLeft(io)); + }); +}; diff --git a/lib/socket_wrapper/receive_message/handle_join_room.js b/lib/socket_wrapper/receive_message/handle_join_room.js new file mode 100644 index 0000000..a881d80 --- /dev/null +++ b/lib/socket_wrapper/receive_message/handle_join_room.js @@ -0,0 +1,15 @@ +const { getUsersByRoomId } = require('../../redis_wrapper').loadModule; +const { updateUserData } = require('../../redis_wrapper').saveModule; + +module.exports = function(roomObj) { + return updateUserData(roomObj.user_id, 'room_id', roomObj.id) + .then(() => roomObj.id) + .then(roomId => { + return getUsersByRoomId(roomId).then(users => { + return { + roomId: roomId, + users: users + }; + }); + }); +}; diff --git a/lib/socket_wrapper/receive_message/handle_leave_room.js b/lib/socket_wrapper/receive_message/handle_leave_room.js new file mode 100644 index 0000000..2e76113 --- /dev/null +++ b/lib/socket_wrapper/receive_message/handle_leave_room.js @@ -0,0 +1,15 @@ +const { getUsersByRoomId } = require('../../redis_wrapper').loadModule; +const { updateUserData } = require('../../redis_wrapper').saveModule; + +module.exports = function(roomObj) { + return updateUserData(roomObj.user_id, 'room_id', -1) + .then(() => roomObj.id) + .then(roomId => { + return getUsersByRoomId(roomId).then(users => { + return { + roomId: roomId, + users: users + }; + }); + }); +}; diff --git a/lib/socket_wrapper/receive_message/handle_new_message.js b/lib/socket_wrapper/receive_message/handle_new_message.js new file mode 100644 index 0000000..c6193e1 --- /dev/null +++ b/lib/socket_wrapper/receive_message/handle_new_message.js @@ -0,0 +1,9 @@ +const { saveMessage } = require('../../redis_wrapper/save_module'); + +// _handleNewMessage +module.exports = function(msgObj) { + // Set the gmt_created property. + msgObj.gmt_created = new Date().getTime(); + + return saveMessage(msgObj).then(() => msgObj); +}; diff --git a/lib/socket_wrapper/receive_message/handle_new_room.js b/lib/socket_wrapper/receive_message/handle_new_room.js new file mode 100644 index 0000000..dd805e7 --- /dev/null +++ b/lib/socket_wrapper/receive_message/handle_new_room.js @@ -0,0 +1,17 @@ +const Promise = require("bluebird"); +const { typeExists } = require("../../redis_wrapper/load_module"); +const { saveRoom } = require("../../redis_wrapper/save_module"); + +const _getRoomId = roomname => rooms => { + return rooms.find(room => room.roomname === roomname).id; +}; + +module.exports = function(roomObj) { + return typeExists("rooms", roomObj.roomname).then(exists => { + if (exists) { + return Promise.reject(roomObj); + } else { + return saveRoom(roomObj).then(() => roomObj); + } + }); +}; diff --git a/lib/socket_wrapper/receive_message/handle_new_user.js b/lib/socket_wrapper/receive_message/handle_new_user.js new file mode 100644 index 0000000..e3c18fe --- /dev/null +++ b/lib/socket_wrapper/receive_message/handle_new_user.js @@ -0,0 +1,15 @@ +const Promise = require("bluebird"); +const { typeExists, getUsers } = require("../../redis_wrapper/load_module"); +const { saveUser } = require("../../redis_wrapper/save_module"); +const { hashSync: encodePassword } = require("bcryptjs"); + +module.exports = function(userObj) { + return typeExists("users", userObj.username).then(exists => { + if (exists) { + return Promise.reject(userObj); + } else { + userObj.password = encodePassword(userObj.password); + return saveUser(userObj).then(() => userObj); + } + }); +}; diff --git a/lib/socket_wrapper/receive_message/index.js b/lib/socket_wrapper/receive_message/index.js new file mode 100644 index 0000000..eee181f --- /dev/null +++ b/lib/socket_wrapper/receive_message/index.js @@ -0,0 +1,7 @@ +module.exports = { + handleNewMessage: require('./handle_new_message'), + handleNewUser: require('./handle_new_user'), + handleNewRoom: require('./handle_new_room'), + handleJoinRoom: require('./handle_join_room'), + handleLeaveRoom: require('./handle_leave_room') +}; diff --git a/lib/socket_wrapper/send_message/index.js b/lib/socket_wrapper/send_message/index.js new file mode 100644 index 0000000..66ff70b --- /dev/null +++ b/lib/socket_wrapper/send_message/index.js @@ -0,0 +1,7 @@ +module.exports = { + sendUserCreated: require('./send_user_created'), + sendMessageCreated: require('./send_message_created'), + sendRoomCreated: require('./send_room_created'), + sendRoomJoined: require('./send_room_joined'), + sendRoomLeft: require('./send_room_left') +}; diff --git a/lib/socket_wrapper/send_message/send_message_created.js b/lib/socket_wrapper/send_message/send_message_created.js new file mode 100644 index 0000000..3314a60 --- /dev/null +++ b/lib/socket_wrapper/send_message/send_message_created.js @@ -0,0 +1,16 @@ +const io = require('socket.io'); +const { getUsers } = require('../../redis_wrapper/load_module'); + +module.exports = io => msgObj => { + getUsers(msgObj.user_id).then(user => { + msgObj.username = user[0].username; + + console.log( + `New message: [ UserID: ${msgObj.user_id}, RoomID: ${msgObj.room_id}, Body: ${msgObj.body} ]` + ); + + io.emit('message_created', msgObj); + }); +}; + +// send stuff diff --git a/lib/socket_wrapper/send_message/send_room_created.js b/lib/socket_wrapper/send_message/send_room_created.js new file mode 100644 index 0000000..65e3eeb --- /dev/null +++ b/lib/socket_wrapper/send_message/send_room_created.js @@ -0,0 +1,18 @@ +const { getRooms } = require("../../redis_wrapper/load_module"); + +const _getRoomId = roomname => rooms => { + return rooms.find(room => room.roomname === roomname).id; +}; + +module.exports = io => roomObj => { + // Get the newly created user's id from redis. + getRooms().then(rooms => { + roomObj.id = _getRoomId(roomObj.roomname)(rooms); + + console.log( + `New message: Room data was saved! [ UserID: ${roomObj.roomname} ]` + ); + + io.emit("room_created", roomObj); + }); +}; diff --git a/lib/socket_wrapper/send_message/send_room_joined.js b/lib/socket_wrapper/send_message/send_room_joined.js new file mode 100644 index 0000000..3fb9447 --- /dev/null +++ b/lib/socket_wrapper/send_message/send_room_joined.js @@ -0,0 +1,3 @@ +module.exports = io => roomData => { + io.emit('room_joined', roomData); +}; diff --git a/lib/socket_wrapper/send_message/send_room_left.js b/lib/socket_wrapper/send_message/send_room_left.js new file mode 100644 index 0000000..7e2297c --- /dev/null +++ b/lib/socket_wrapper/send_message/send_room_left.js @@ -0,0 +1,3 @@ +module.exports = io => roomData => { + io.emit('room_left', roomData); +}; diff --git a/lib/socket_wrapper/send_message/send_user_created.js b/lib/socket_wrapper/send_message/send_user_created.js new file mode 100644 index 0000000..cf2ffe4 --- /dev/null +++ b/lib/socket_wrapper/send_message/send_user_created.js @@ -0,0 +1,18 @@ +const { getUsers } = require("../../redis_wrapper/load_module"); + +const _getUserId = username => users => { + return users.find(user => user.username === username).id; +}; + +module.exports = io => userObj => { + // Get the newly created user's id from redis. + getUsers().then(users => { + userObj.id = _getUserId(userObj.username)(users); + + console.log( + `New message: User data was saved! [ UserID: ${userObj.username}, RoomID: ${userObj.password} ]` + ); + + io.emit("user_created", userObj); + }); +}; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..51aa0b4 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1095 @@ +{ + "name": "project-superchat", + "version": "0.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "accepts": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz", + "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=", + "requires": { + "mime-types": "2.1.16", + "negotiator": "0.6.1" + } + }, + "after": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", + "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" + }, + "align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "requires": { + "kind-of": "3.2.2", + "longest": "1.0.1", + "repeat-string": "1.6.1" + } + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "arraybuffer.slice": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz", + "integrity": "sha1-8zshWfBTKj8xB6JywMz70a0peco=" + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + }, + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + }, + "backo2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "base64-arraybuffer": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", + "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=" + }, + "base64id": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", + "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=" + }, + "basic-auth": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.1.0.tgz", + "integrity": "sha1-RSIe5Cn37h5QNb4/UVM/HN/SmIQ=" + }, + "bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=" + }, + "better-assert": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", + "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", + "requires": { + "callsite": "1.0.0" + } + }, + "blob": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.4.tgz", + "integrity": "sha1-vPEwUspURj8w+fx+lbmkdjCpSSE=" + }, + "bluebird": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.0.tgz", + "integrity": "sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw=" + }, + "body-parser": { + "version": "1.17.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.17.2.tgz", + "integrity": "sha1-+IkqvI+eYn1Crtr7yma/WrmRBO4=", + "requires": { + "bytes": "2.4.0", + "content-type": "1.0.2", + "debug": "2.6.7", + "depd": "1.1.0", + "http-errors": "1.6.1", + "iconv-lite": "0.4.15", + "on-finished": "2.3.0", + "qs": "6.4.0", + "raw-body": "2.2.0", + "type-is": "1.6.15" + }, + "dependencies": { + "debug": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz", + "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4=", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "brace-expansion": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", + "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "busboy": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", + "integrity": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=", + "requires": { + "dicer": "0.2.5", + "readable-stream": "1.1.14" + } + }, + "bytes": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", + "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=" + }, + "callsite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=" + }, + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "optional": true + }, + "center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "optional": true, + "requires": { + "align-text": "0.1.4", + "lazy-cache": "1.0.4" + } + }, + "cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "optional": true, + "requires": { + "center-align": "0.1.3", + "right-align": "0.1.3", + "wordwrap": "0.0.2" + }, + "dependencies": { + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "optional": true + } + } + }, + "component-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", + "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=" + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + }, + "component-inherit": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", + "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" + }, + "content-type": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz", + "integrity": "sha1-t9ETrueo3Se9IRM8TcJSnfFyHu0=" + }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + }, + "cookie-parser": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.3.tgz", + "integrity": "sha1-D+MfoZ0AC5X0qt8fU/3CuKIDuqU=", + "requires": { + "cookie": "0.3.1", + "cookie-signature": "1.0.6" + } + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "debug": { + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "optional": true + }, + "define-properties": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", + "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", + "requires": { + "foreach": "2.0.5", + "object-keys": "1.0.11" + } + }, + "depd": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz", + "integrity": "sha1-4b2Cxqq2ztlluXuIsX7T5SjKGMM=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "dicer": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", + "integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=", + "requires": { + "readable-stream": "1.1.14", + "streamsearch": "0.1.2" + } + }, + "double-ended-queue": { + "version": "2.1.0-0", + "resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz", + "integrity": "sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw=" + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "encodeurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", + "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=" + }, + "engine.io": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.1.0.tgz", + "integrity": "sha1-XKQ4486f28kVxKIcjdnhJmcG5X4=", + "requires": { + "accepts": "1.3.3", + "base64id": "1.0.0", + "cookie": "0.3.1", + "debug": "2.6.8", + "engine.io-parser": "2.1.1", + "uws": "0.14.5", + "ws": "2.3.1" + } + }, + "engine.io-client": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.1.1.tgz", + "integrity": "sha1-QVqYUrrbFPoAj6PvHjFgjbZ2EyU=", + "requires": { + "component-emitter": "1.2.1", + "component-inherit": "0.0.3", + "debug": "2.6.8", + "engine.io-parser": "2.1.1", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "parsejson": "0.0.3", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "ws": "2.3.1", + "xmlhttprequest-ssl": "1.5.3", + "yeast": "0.1.2" + } + }, + "engine.io-parser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.1.tgz", + "integrity": "sha1-4Ps/DgRi9/WLt3waUun1p+JuRmg=", + "requires": { + "after": "0.8.2", + "arraybuffer.slice": "0.0.6", + "base64-arraybuffer": "0.1.5", + "blob": "0.0.4", + "has-binary2": "1.0.2" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "etag": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.0.tgz", + "integrity": "sha1-b2Ma7zNtbEY2K1F2QETOIWvjwFE=" + }, + "express": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/express/-/express-4.15.3.tgz", + "integrity": "sha1-urZdDwOqgMNYQIly/HAPkWlEtmI=", + "requires": { + "accepts": "1.3.3", + "array-flatten": "1.1.1", + "content-disposition": "0.5.2", + "content-type": "1.0.2", + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "debug": "2.6.7", + "depd": "1.1.0", + "encodeurl": "1.0.1", + "escape-html": "1.0.3", + "etag": "1.8.0", + "finalhandler": "1.0.3", + "fresh": "0.5.0", + "merge-descriptors": "1.0.1", + "methods": "1.1.2", + "on-finished": "2.3.0", + "parseurl": "1.3.1", + "path-to-regexp": "0.1.7", + "proxy-addr": "1.1.5", + "qs": "6.4.0", + "range-parser": "1.2.0", + "send": "0.15.3", + "serve-static": "1.12.3", + "setprototypeof": "1.0.3", + "statuses": "1.3.1", + "type-is": "1.6.15", + "utils-merge": "1.0.0", + "vary": "1.1.1" + }, + "dependencies": { + "debug": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz", + "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4=", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "express-handlebars": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/express-handlebars/-/express-handlebars-3.0.0.tgz", + "integrity": "sha1-gKBwu4GbCeSvLKbQeA91zgXnXC8=", + "requires": { + "glob": "6.0.4", + "graceful-fs": "4.1.11", + "handlebars": "4.0.10", + "object.assign": "4.0.4", + "promise": "7.3.1" + } + }, + "finalhandler": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.3.tgz", + "integrity": "sha1-70fneVDpmXgOhgIqVg4yF+DQzIk=", + "requires": { + "debug": "2.6.7", + "encodeurl": "1.0.1", + "escape-html": "1.0.3", + "on-finished": "2.3.0", + "parseurl": "1.3.1", + "statuses": "1.3.1", + "unpipe": "1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz", + "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4=", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "foreach": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", + "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=" + }, + "forwarded": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz", + "integrity": "sha1-Ge+YdMSuHCl7zweP3mOgm2aoQ2M=" + }, + "fresh": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.0.tgz", + "integrity": "sha1-9HTKXmqSRtb9jglTz6m5yAWvp44=" + }, + "function-bind": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.0.tgz", + "integrity": "sha1-FhdnFMgBeY5Ojyz391KUZ7tKV3E=" + }, + "glob": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", + "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", + "requires": { + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" + }, + "handlebars": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.10.tgz", + "integrity": "sha1-PTDHGLCaPZbyPqTMH0A8TTup/08=", + "requires": { + "async": "1.5.2", + "optimist": "0.6.1", + "source-map": "0.4.4", + "uglify-js": "2.8.29" + } + }, + "has-binary2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.2.tgz", + "integrity": "sha1-6D26SfC5vk0CbSc2U1DZ8D9Uvpg=", + "requires": { + "isarray": "2.0.1" + }, + "dependencies": { + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" + } + } + }, + "has-cors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", + "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=" + }, + "http-errors": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.1.tgz", + "integrity": "sha1-X4uO2YrKVFZWv1cplzh/kEpyIlc=", + "requires": { + "depd": "1.1.0", + "inherits": "2.0.3", + "setprototypeof": "1.0.3", + "statuses": "1.3.1" + } + }, + "iconv-lite": { + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz", + "integrity": "sha1-/iZaIYrGpXz+hUkn6dBMGYJe3es=" + }, + "indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ipaddr.js": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.4.0.tgz", + "integrity": "sha1-KWrKh4qCGBbluF0KKFqZvP9FgvA=" + }, + "is-buffer": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz", + "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=" + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "1.1.5" + } + }, + "lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", + "optional": true + }, + "longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz", + "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=" + }, + "mime-db": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.29.0.tgz", + "integrity": "sha1-SNJtI1WJZRcErFkWygYAGRQmaHg=" + }, + "mime-types": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.16.tgz", + "integrity": "sha1-K4WKUuXs1RbbiXrCvodIeDBpjiM=", + "requires": { + "mime-db": "1.29.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "1.1.8" + } + }, + "minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" + }, + "morgan": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.8.2.tgz", + "integrity": "sha1-eErHc05KRTqcbm6GgKkyknXItoc=", + "requires": { + "basic-auth": "1.1.0", + "debug": "2.6.8", + "depd": "1.1.0", + "on-finished": "2.3.0", + "on-headers": "1.0.1" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-component": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", + "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=" + }, + "object-keys": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz", + "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=" + }, + "object.assign": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.0.4.tgz", + "integrity": "sha1-scnMBE7xuf5jYG/BQau7MuFHMMw=", + "requires": { + "define-properties": "1.1.2", + "function-bind": "1.1.0", + "object-keys": "1.0.11" + } + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", + "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1.0.2" + } + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "requires": { + "minimist": "0.0.10", + "wordwrap": "0.0.3" + } + }, + "parsejson": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/parsejson/-/parsejson-0.0.3.tgz", + "integrity": "sha1-q343WfIJ7OmUN5c/fQ8fZK4OZKs=", + "requires": { + "better-assert": "1.0.2" + } + }, + "parseqs": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", + "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", + "requires": { + "better-assert": "1.0.2" + } + }, + "parseuri": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", + "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", + "requires": { + "better-assert": "1.0.2" + } + }, + "parseurl": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz", + "integrity": "sha1-yKuMkiO6NIiKpkopeyiFO+wY2lY=" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "requires": { + "asap": "2.0.6" + } + }, + "proxy-addr": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.5.tgz", + "integrity": "sha1-ccDuOxAt4/IC87ZPYI0XP8uhqRg=", + "requires": { + "forwarded": "0.1.0", + "ipaddr.js": "1.4.0" + } + }, + "qs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", + "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" + }, + "range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" + }, + "raw-body": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.2.0.tgz", + "integrity": "sha1-mUl2z2pQlqQRYoQEkvC9xdbn+5Y=", + "requires": { + "bytes": "2.4.0", + "iconv-lite": "0.4.15", + "unpipe": "1.0.0" + } + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "redis": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/redis/-/redis-2.7.1.tgz", + "integrity": "sha1-fVb3h1uYsgQQtxU58dh47Vjr9Go=", + "requires": { + "double-ended-queue": "2.1.0-0", + "redis-commands": "1.3.1", + "redis-parser": "2.6.0" + } + }, + "redis-commands": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.3.1.tgz", + "integrity": "sha1-gdgm9F+pyLIBH0zXoP5ZfSQdRCs=" + }, + "redis-parser": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-2.6.0.tgz", + "integrity": "sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs=" + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" + }, + "right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "optional": true, + "requires": { + "align-text": "0.1.4" + } + }, + "safe-buffer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz", + "integrity": "sha1-0mPKVGls2KMGtcplUekt5XkY++c=" + }, + "send": { + "version": "0.15.3", + "resolved": "https://registry.npmjs.org/send/-/send-0.15.3.tgz", + "integrity": "sha1-UBP5+ZAj31DRvZiSwZ4979HVMwk=", + "requires": { + "debug": "2.6.7", + "depd": "1.1.0", + "destroy": "1.0.4", + "encodeurl": "1.0.1", + "escape-html": "1.0.3", + "etag": "1.8.0", + "fresh": "0.5.0", + "http-errors": "1.6.1", + "mime": "1.3.4", + "ms": "2.0.0", + "on-finished": "2.3.0", + "range-parser": "1.2.0", + "statuses": "1.3.1" + }, + "dependencies": { + "debug": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz", + "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4=", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "serve-favicon": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/serve-favicon/-/serve-favicon-2.4.3.tgz", + "integrity": "sha1-WYaxewUCZCtkHCH4GLGszjICXSM=", + "requires": { + "etag": "1.8.0", + "fresh": "0.5.0", + "ms": "2.0.0", + "parseurl": "1.3.1", + "safe-buffer": "5.0.1" + } + }, + "serve-static": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.12.3.tgz", + "integrity": "sha1-n0uhni8wMMVH+K+ZEHg47DjVseI=", + "requires": { + "encodeurl": "1.0.1", + "escape-html": "1.0.3", + "parseurl": "1.3.1", + "send": "0.15.3" + } + }, + "setprototypeof": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" + }, + "socket.io": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.0.3.tgz", + "integrity": "sha1-Q1nwaiSTOua9CHeYr3jGgOrjReM=", + "requires": { + "debug": "2.6.8", + "engine.io": "3.1.0", + "object-assign": "4.1.1", + "socket.io-adapter": "1.1.0", + "socket.io-client": "2.0.3", + "socket.io-parser": "3.1.2" + } + }, + "socket.io-adapter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.0.tgz", + "integrity": "sha1-x6pGUB3VVsLLiiivj/lcC14dqkw=", + "requires": { + "debug": "2.3.3" + }, + "dependencies": { + "debug": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", + "requires": { + "ms": "0.7.2" + } + }, + "ms": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=" + } + } + }, + "socket.io-client": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.0.3.tgz", + "integrity": "sha1-bK9K/5+FsZ/ZG2zhPWmttWT4hzs=", + "requires": { + "backo2": "1.0.2", + "base64-arraybuffer": "0.1.5", + "component-bind": "1.0.0", + "component-emitter": "1.2.1", + "debug": "2.6.8", + "engine.io-client": "3.1.1", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "object-component": "0.0.3", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "socket.io-parser": "3.1.2", + "to-array": "0.1.4" + } + }, + "socket.io-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.1.2.tgz", + "integrity": "sha1-28IoIVH8T6675Aru3Ady66YZ9/I=", + "requires": { + "component-emitter": "1.2.1", + "debug": "2.6.8", + "has-binary2": "1.0.2", + "isarray": "2.0.1" + }, + "dependencies": { + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" + } + } + }, + "source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "requires": { + "amdefine": "1.0.1" + } + }, + "statuses": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" + }, + "streamsearch": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", + "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "to-array": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", + "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=" + }, + "type-is": { + "version": "1.6.15", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", + "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", + "requires": { + "media-typer": "0.3.0", + "mime-types": "2.1.16" + } + }, + "uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "optional": true, + "requires": { + "source-map": "0.5.6", + "uglify-to-browserify": "1.0.2", + "yargs": "3.10.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", + "optional": true + } + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "optional": true + }, + "ultron": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.0.tgz", + "integrity": "sha1-sHoualQagV/Go0zNRTO67DB8qGQ=" + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "utils-merge": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", + "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=" + }, + "uws": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/uws/-/uws-0.14.5.tgz", + "integrity": "sha1-Z6rzPEaypYel9mZtAPdpEyjxSdw=", + "optional": true + }, + "vary": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.1.tgz", + "integrity": "sha1-Z1Neu2lMHVIldFeYRmUyP1h+jTc=" + }, + "window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "optional": true + }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "ws": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-2.3.1.tgz", + "integrity": "sha1-a5Sz5EfLajY/eF6vlK9jWejoHIA=", + "requires": { + "safe-buffer": "5.0.1", + "ultron": "1.1.0" + } + }, + "xmlhttprequest-ssl": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz", + "integrity": "sha1-GFqIjATspGw+QHDZn3tJ3jUomS0=" + }, + "yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "optional": true, + "requires": { + "camelcase": "1.2.1", + "cliui": "2.1.0", + "decamelize": "1.2.0", + "window-size": "0.1.0" + } + }, + "yeast": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", + "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..0c8c20c --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "project-superchat", + "version": "0.0.0", + "private": true, + "scripts": { + "start": "node ./bin/www" + }, + "dependencies": { + "bcryptjs": "^2.4.3", + "bluebird": "^3.5.0", + "body-parser": "~1.17.1", + "busboy": "^0.2.14", + "cookie-parser": "~1.4.3", + "debug": "~2.6.3", + "express": "~4.15.2", + "express-handlebars": "^3.0.0", + "morgan": "~1.8.1", + "redis": "^2.7.1", + "serve-favicon": "~2.4.2", + "socket.io": "^2.0.3", + "socket.io-client": "^2.0.3" + } +} diff --git a/public/javascripts/cookie_helper.js b/public/javascripts/cookie_helper.js new file mode 100644 index 0000000..20bc85d --- /dev/null +++ b/public/javascripts/cookie_helper.js @@ -0,0 +1,24 @@ +function createCookie(name, value, days) { + let expires = ''; + if (days) { + let date = new Date(); + date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000); + expires = '; expires=' + date.toUTCString(); + } + document.cookie = name + '=' + value + expires + '; path=/'; +} + +function readCookie(name) { + let nameEQ = name + '='; + let ca = document.cookie.split(';'); + for (let i = 0; i < ca.length; i++) { + let c = ca[i]; + while (c.charAt(0) == ' ') c = c.substring(1, c.length); + if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length); + } + return null; +} + +function eraseCookie(name) { + createCookie(name, '', -1); +} diff --git a/public/javascripts/script_index.js b/public/javascripts/script_index.js new file mode 100644 index 0000000..c6fe48b --- /dev/null +++ b/public/javascripts/script_index.js @@ -0,0 +1,132 @@ +$(_pageInit); + +let _inChatRoom = false; + +function _pageInit() { + // Connect to our backend. + const socket = io.connect('/'); + + // Get user id and current room id. + let userId = readCookie('user_id'); + let currentRoomId = $('#chatroom-panel').data('room-id'); + + const $messageContainer = $('#message-container'); + const $usersContainer = $('#users-container'); + + $('#add-room-form').on('submit', function(e) { + let $roomInput = $('#add-room-input'); + let roomName = $roomInput.val(); + console.log(roomName); + socket.emit('new_room', { + roomname: roomName + }); + + $roomInput.val(''); + e.preventDefault(); + }); + + socket.on('room_created', roomObj => { + let newRoom = `${roomObj.roomname}`; + $('#rooms-block').append(newRoom); + }); + + $('#room-list').on('click', 'a.list-group-item', function(e) { + let $target = $(e.target); + + // Get the id of the room. + let roomId = $target.data('room-id'); + + // Clear active flag on all rooms, + // set active flag on selected room. + $('#room-list a.list-group-item').removeClass('active'); + $target.addClass('active'); + + // Emit event to change and leave rooms. + + if (_inChatRoom) { + _inChatRoom = false; + } + if (!_inChatRoom) { + new Promise(resolve => { + socket.emit('join_room', { + id: roomId, + old_id: currentRoomId, + user_id: userId + }); + currentRoomId = roomId; + $('#message-form').removeClass('hidden'); + _inChatRoom = true; + resolve(); + }).then(() => { + //Ajax our new chat room. + $.ajax(`/room/${currentRoomId}`, { + context: $messageContainer + }).done(function(data) { + this.html(data); + $('#messages-block').animate( + { scrollTop: $('#chatroom-panel').height() }, + 1 + ); + }); + }); + } + }); + + socket.on('room_left', roomData => { + $usersContainer.empty(); + roomData.users.forEach(user => { + $usersContainer.append(`