diff --git a/packages/server/package-lock.json b/packages/server/package-lock.json index 9993d82..d770588 100644 --- a/packages/server/package-lock.json +++ b/packages/server/package-lock.json @@ -1212,6 +1212,15 @@ "@types/express": "*" } }, + "@types/engine.io": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/engine.io/-/engine.io-3.1.4.tgz", + "integrity": "sha512-98rXVukLD6/ozrQ2O80NAlWDGA4INg+tqsEReWJldqyi2fulC9V7Use/n28SWgROXKm6003ycWV4gZHoF8GA6w==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/express": { "version": "4.17.8", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.8.tgz", @@ -1335,6 +1344,16 @@ "@types/node": "*" } }, + "@types/socket.io": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/@types/socket.io/-/socket.io-2.1.11.tgz", + "integrity": "sha512-bVprmqPhJMLb9ZCm8g0Xy8kwBFRbnanOWSxzWkDkkIwxTvud5tKMfAJymXX6LQbizUKCS1yima7JM4BeLqjNqA==", + "dev": true, + "requires": { + "@types/engine.io": "*", + "@types/node": "*" + } + }, "@types/winston": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/@types/winston/-/winston-2.4.4.tgz", @@ -4177,6 +4196,11 @@ "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", "dev": true }, + "notepack.io": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/notepack.io/-/notepack.io-2.2.0.tgz", + "integrity": "sha512-9b5w3t5VSH6ZPosoYnyDONnUTF8o0UkBw7JLA6eBlYJWyGT1Q3vQa8Hmuj1/X6RYvHjjygBDgw6fJhe0JEojfw==" + }, "oauth": { "version": "0.9.15", "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", @@ -5141,11 +5165,6 @@ } } }, - "socket-io": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/socket-io/-/socket-io-1.0.0.tgz", - "integrity": "sha1-/FZvTTCmNwIk0H5l3O84a9j10pY=" - }, "socket.io": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-3.0.1.tgz", @@ -5203,6 +5222,33 @@ } } }, + "socket.io-redis": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/socket.io-redis/-/socket.io-redis-6.0.1.tgz", + "integrity": "sha512-RvxAhVSsDQJfDUEXUER9MvsE99XZurXkAVORjym1FTReqWlvmPVjyAnrpLlH3RxvPFdFa9sN4kmaTtyzjOtRRA==", + "requires": { + "debug": "~4.1.0", + "notepack.io": "~2.2.0", + "redis": "^3.0.0", + "socket.io-adapter": "~2.0.0", + "uid2": "0.0.3" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", diff --git a/packages/server/package.json b/packages/server/package.json index cacbae7..5fabeae 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -26,6 +26,7 @@ "@types/node": "^14.14.3", "@types/passport": "^1.0.4", "@types/redis": "^2.8.28", + "@types/socket.io": "^2.1.11", "@types/winston": "^2.4.4", "eslint": "^7.12.0", "eslint-config-prettier": "^6.14.0", @@ -51,8 +52,8 @@ "passport.socketio": "^3.7.0", "redis": "^3.0.2", "regenerator-runtime": "^0.13.7", - "socket-io": "^1.0.0", "socket.io": "^3.0.1", + "socket.io-redis": "^6.0.1", "winston": "^3.3.3" } } diff --git a/packages/server/src/index.js b/packages/server/src/index.js index 357e49f..261bc3c 100644 --- a/packages/server/src/index.js +++ b/packages/server/src/index.js @@ -10,10 +10,10 @@ import proxy from 'express-http-proxy' import bodyParser from 'body-parser' import cookieParser from 'cookie-parser' import session from 'express-session' -import redis from 'redis' import connectRedis from 'connect-redis' -import connectSocketIO from 'socket.io' +import { Server as SocketIO } from 'socket.io' +import { createAdapter } from 'socket.io-redis' import passportSocketIo from 'passport.socketio' import morgan from 'morgan' @@ -23,23 +23,11 @@ import logger, { morganStream } from './logger' import passport from 'passport' import mongoose from 'mongoose' +import redisClient, { asyncHEXISTS, asyncHGET, asyncHSET, asyncHDEL } from './redis' + import { authRouter, initPassport } from './auth/passport' const RedisStore = connectRedis(session) -const redisClient = redis.createClient({ - host: '127.0.0.1', - port: 6379, - - password: process.env.REDIS_PASSWORD, -}) - -redisClient.on('error', (error) => { - console.error(error) -}) - -redisClient.on('connect', () => { - console.log('Connection to Redis has been established successfully.') -}) mongoose.connect( 'mongodb://localhost:27017', @@ -52,15 +40,23 @@ mongoose.connect( dbName: 'museum', }, () => { - console.log('Connection to MongoDB has been established successfully.') + logger.info('Connection to MongoDB has been established successfully.') }, ) const app = express() app.set('trust proxy', 1) const server = http.createServer(app) -// @ts-ignore -const io = connectSocketIO(server) +const io = new SocketIO(server, { serveClient: false }) + +const pubClient = redisClient +const subClient = pubClient.duplicate() +io.adapter( + createAdapter({ + pubClient, + subClient, + }), +) app.use(bodyParser.json()) app.use(bodyParser.urlencoded({ extended: false })) @@ -81,17 +77,31 @@ app.use('/auth', authRouter) app.use(morgan('short', { stream: morganStream })) -function onAuthorizeSuccess(data, accept) { - // console.log(data.user) - logger.debug( - `Successful connection to socket.io from ${data.user._id} (${data.user.email})`, - ) - accept(null, true) +async function onAuthorizeSuccess(data, accept) { + if (data.user._id) { + const userExists = await asyncHEXISTS('socket', String(data.user._id)) + + if (userExists === 1) { + logger.debug( + `${data.user._id} (${data.user.email}) is already logged in. Kicking...`, + ) + const socketId = await asyncHGET('socket', String(data.user._id)) + const socket = io.of('/').sockets.get(socketId) + if (socket) { + logger.debug(`Disconnecting ${socketId}`) + socket.disconnect() + } + } + logger.debug( + `Successful connection to socket.io from ${data.user._id} (${data.user.email})`, + ) + accept(null, true) + } } function onAuthorizeFail(_, message, error, accept) { if (error) throw new Error(message) - logger.debug('failed connection to socket.io:', message) + logger.debug('Failed to connect to socket.io:', message) accept(null, false) } @@ -106,11 +116,16 @@ io.use( }), ) -io.on('connection', (socket) => { - logger.debug('a user connected') - //console.log(socket.request.user) - socket.on('disconnect', () => { - logger.debug('a user connected') +io.on('connection', async (socket) => { + await asyncHSET('socket', String(socket.request.user._id), String(socket.id)) + logger.debug(`Assigned ${socket.id} to ${socket.request.user._id}`) + + socket.on('kick', () => { + console.log('kicking') + }) + socket.on('disconnect', async () => { + logger.debug(`User with id ${socket.id} disconnected`) + await asyncHDEL('socket', String(socket.request.user._id), String(socket.id)) }) }) @@ -121,5 +136,5 @@ if (process.env.NODE_ENV !== 'PRODUCTION') { } server.listen(3000, () => { - console.log('> Ready on http://localhost:3000') + logger.info('Server ready on http://localhost:3000') }) diff --git a/packages/server/src/logger.js b/packages/server/src/logger.js index 7d51a16..ac64784 100644 --- a/packages/server/src/logger.js +++ b/packages/server/src/logger.js @@ -15,7 +15,7 @@ const logger = createLogger({ level: 'debug', format: combine(format.colorize(), splat(), timestamp(), myFormat), transports: [ - new transports.Console({ level: 'http' }), + new transports.Console({ level: 'info' }), new transports.File({ filename: path.join(__dirname, '../log'), level: 'debug' }), ], diff --git a/packages/server/src/redis.js b/packages/server/src/redis.js new file mode 100644 index 0000000..5e2a709 --- /dev/null +++ b/packages/server/src/redis.js @@ -0,0 +1,29 @@ +import path from 'path' +import dotenv from 'dotenv' + +dotenv.config({ path: path.join(__dirname, '../.env') }) + +import { promisify } from 'util' +import redis from 'redis' +import logger from './logger' + +const client = redis.createClient({ + host: '127.0.0.1', + port: 6379, + password: process.env.REDIS_PASSWORD, +}) + +client.on('error', (error) => { + logger.error(error) +}) + +client.on('connect', () => { + logger.info('Connection to Redis has been established successfully.') +}) + +export const asyncHEXISTS = promisify(client.HEXISTS).bind(client) +export const asyncHGET = promisify(client.HGET).bind(client) +export const asyncHSET = promisify(client.HSET).bind(client) +export const asyncHDEL = promisify(client.HDEL).bind(client) + +export default client