You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

191 lines
4.8 KiB

import 'regenerator-runtime/runtime'
import path from 'path'
import dotenv from 'dotenv'
dotenv.config({ path: path.join(__dirname, '../.env') })
import http from 'http'
import express from 'express'
import proxy from 'express-http-proxy'
import bodyParser from 'body-parser'
import cookieParser from 'cookie-parser'
import session from 'express-session'
import connectRedis from 'connect-redis'
import { Server as SocketIO } from 'socket.io'
import { createAdapter } from 'socket.io-redis'
import passportSocketIo from 'passport.socketio'
import morgan from 'morgan'
import logger, { morganStream } from './logger'
import passport from 'passport'
import mongoose from 'mongoose'
import UserModel from './models/user'
import redisClient, {
asyncHEXISTS,
asyncHGET,
asyncHSET,
asyncHDEL,
asyncHGETALL,
asyncDEL,
} from './redis'
import { authRouter, initPassport } from './auth/passport'
const RedisStore = connectRedis(session)
const mongoHost = process.env.MONGODB_HOST ?? 'localhost'
mongoose.connect(
`mongodb://${mongoHost}:27017`,
{
useCreateIndex: true,
useNewUrlParser: true,
useUnifiedTopology: true,
user: process.env.MONGODB_USER,
pass: process.env.MONGODB_PASSWORD,
dbName: 'museum',
},
() => {
logger.info('Connection to MongoDB has been established successfully.')
},
)
const app = express()
app.set('trust proxy', 1)
const server = http.createServer(app)
asyncDEL('socket')
asyncDEL('transform')
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 }))
app.use(cookieParser(process.env.SESSION_SECRET))
app.use(
session({
store: new RedisStore({ client: redisClient }),
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
}),
)
app.use(passport.initialize())
app.use(passport.session())
initPassport()
app.use('/auth', authRouter)
app.use(morgan('short', { stream: morganStream }))
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('Client failed to connect to socket.io:', message)
accept(null, false)
}
io.use(
passportSocketIo.authorize({
// @ts-ignore
cookieParser: cookieParser,
secret: process.env.SESSION_SECRET,
store: new RedisStore({ client: redisClient }),
success: onAuthorizeSuccess,
fail: onAuthorizeFail,
}),
)
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('transform', async (payload) => {
await asyncHSET('transform', String(socket.id), JSON.stringify(payload))
})
socket.on('disconnect', async () => {
logger.debug(`User with id ${socket.id} disconnected`)
const user = await UserModel.findById(socket.request.user._id)
if (user) {
const transform = await asyncHGET('transform', String(socket.id))
user.lastLocation = JSON.parse(transform)
user.save()
}
await asyncHDEL('transform', String(socket.id))
await asyncHDEL('socket', String(socket.request.user._id))
})
const user = await UserModel.findById(socket.request.user._id)
if (user) {
socket.emit('initial-transform', user.lastLocation)
}
})
async function broadcastTransforms() {
const transforms = await asyncHGETALL('transform')
if (transforms) {
let parsed = {}
Object.entries(transforms).forEach(([key, value]) => {
parsed = { ...parsed, [key]: { ...JSON.parse(value) } }
})
io.sockets.emit('broadcast-transforms', parsed)
}
setTimeout(broadcastTransforms, 30)
}
broadcastTransforms()
if (process.env.NODE_ENV !== 'production') {
app.use('/', proxy('http://localhost:4000/'))
} else {
const buildPath = path.resolve(__dirname, '../../client/build')
const indexHtml = path.join(buildPath, 'index.html')
app.use(express.static(buildPath))
app.get('*', (_, res) => res.sendFile(indexHtml))
}
const port = process.env.PORT ?? 3000
server.listen(port, () => {
logger.info(`Server ready on port ${port}`)
})