diff --git a/server/middlewares/auth.js b/server/middlewares/auth.js index 5fb727f..8cf8bc0 100644 --- a/server/middlewares/auth.js +++ b/server/middlewares/auth.js @@ -6,8 +6,8 @@ module.exports = (req, res, next) => { res.send( JSON.stringify({ code: 403, - message: "Unauthorized", - }) + message: 'Unauthorized', + }), ); res.end(); } else if (req && req.user && req.user.id && banned.includes(req.user.id)) { @@ -15,8 +15,8 @@ module.exports = (req, res, next) => { res.send( JSON.stringify({ code: 403, - message: "Unauthorized", - }) + message: 'Unauthorized', + }), ); res.end(); } else { diff --git a/server/models/room.js b/server/models/room.js index 7105bdc..a086b22 100644 --- a/server/models/room.js +++ b/server/models/room.js @@ -109,6 +109,10 @@ const Room = new mongoose.Schema({ type: Date, default: undefined, }, + hiddenAt: { + type: Date, + default: undefined, + }, twitchChannel: { type: String, default: undefined, @@ -118,11 +122,19 @@ const Room = new mongoose.Schema({ }); Room.index({ - expireAt: 1, -}, { - expireAfterSeconds: 0, + hiddenAt: 1, }); +Room.statics.visibleQuery = function visibleQuery(now = new Date()) { + return { + $or: [ + { hiddenAt: { $exists: false } }, + { hiddenAt: null }, + { hiddenAt: { $gt: now } }, + ], + }; +}; + Room.virtual('scrambler').get(function () { return new Scrambow().setType(Events.find((e) => e.id === this.event).scrambler); }); @@ -147,6 +159,10 @@ Room.virtual('latestAttempt').get(function () { return this.attempts[this.attempts.length - 1]; }); +Room.methods.isHidden = function isHidden(now = new Date()) { + return this.hiddenAt && this.hiddenAt <= now; +}; + Room.methods.start = function () { this.started = true; @@ -166,15 +182,25 @@ Room.methods.pause = function () { }; Room.methods.updateStale = function updateStale(stale) { + this.expireAt = null; + if (stale) { - this.expireAt = moment().add(10, 'minutes'); + if (!this.hiddenAt) { + this.hiddenAt = moment().add(10, 'minutes').toDate(); + } } else { - this.expireAt = null; + this.hiddenAt = null; } return this.save(); }; +Room.methods.hide = function hide() { + this.expireAt = null; + this.hiddenAt = new Date(); + return this.save(); +}; + Room.methods.addUser = async function (user, spectating, updateAdmin) { if (this.inRoom.get(user.id.toString())) { return false; diff --git a/server/socket/namespaces/rooms.js b/server/socket/namespaces/rooms.js index 23ddf8b..15e5f24 100644 --- a/server/socket/namespaces/rooms.js +++ b/server/socket/namespaces/rooms.js @@ -28,12 +28,13 @@ const joinRoomMask = _.partial(_.pick, _, privateRoomKeys); const fetchRoom = (id) => Room.findById({ _id: id }).populate('users').populate('admin').populate('owner'); -const getRooms = (userId) => Room.find().populate('users').populate('admin').populate('owner') +const getRooms = (userId) => Room.find(Room.visibleQuery()).populate('users').populate('admin').populate('owner') .then((rooms) => rooms.filter((room) => ( userId ? !room.banned.get(userId.toString()) : true )).map(roomMask)); const roomTimerObj = {}; +const roomHideTimerObj = {}; module.exports = (io, middlewares) => { const ns = () => io.of('/rooms'); @@ -51,6 +52,36 @@ module.exports = (io, middlewares) => { return [...sockets].some((socketId) => socketId !== currentSocketId); }; + function clearRoomHideTimer(roomId) { + clearTimeout(roomHideTimerObj[roomId]); + delete roomHideTimerObj[roomId]; + } + + function scheduleRoomHide(room) { + if (!room.hiddenAt) { + clearRoomHideTimer(room._id); + return; + } + + clearRoomHideTimer(room._id); + + const time = new Date(room.hiddenAt).getTime() - Date.now(); + + roomHideTimerObj[room._id] = setTimeout(async () => { + try { + const r = await fetchRoom(room._id); + + if (r && r.isHidden()) { + ns().emit(Protocol.ROOM_DELETED, r._id.toString()); + } + } catch (err) { + logger.error(err); + } finally { + clearRoomHideTimer(room._id); + } + }, Math.max(time, 0)); + } + async function markNormalRoomsStaleOnStartup() { const rooms = await Room.find({ type: 'normal' }) .populate('users') @@ -64,7 +95,8 @@ module.exports = (io, middlewares) => { }); room.admin = null; - await room.updateStale(true); + const r = await room.updateStale(true); + scheduleRoomHide(r); })); logger.info('Marked normal rooms stale on socket startup', { @@ -255,6 +287,7 @@ module.exports = (io, middlewares) => { broadcast(Protocol.USER_LEFT, socket.user.id); ns().emit(Protocol.GLOBAL_ROOM_UPDATED, roomMask(room)); + scheduleRoomHide(room); if (room.doneWithScramble()) { logger.debug('everyone done, sending new scramble'); @@ -294,6 +327,7 @@ module.exports = (io, middlewares) => { } socket.room = r; + clearRoomHideTimer(r._id); socket.emit(Protocol.JOIN, joinRoomMask(r)); cb(null, joinRoomMask(r)); @@ -317,6 +351,13 @@ module.exports = (io, middlewares) => { }); } + if (room.isHidden()) { + return cb({ + statusCode: 404, + message: `Could not find room with id ${id}`, + }); + } + if (room.private && !password) { return cb({ statusCode: 403, @@ -408,15 +449,24 @@ module.exports = (io, middlewares) => { }); } - Room.deleteOne({ _id: id }).then((res) => { - if (res.deletedCount > 0) { - socket.room = undefined; - cb(null); - ns().emit(Protocol.ROOM_DELETED, id); - } else if (res.deletedCount > 1) { - logger.error(168, 'big problemo'); + try { + const room = await Room.findById(id); + + if (!room) { + return cb({ + statusCode: 404, + message: `Could not find room with id ${id}`, + }); } - }); + + await room.hide(); + clearRoomHideTimer(room._id); + socket.room = undefined; + cb(null); + ns().emit(Protocol.ROOM_DELETED, id); + } catch (e) { + logger.error(e); + } }); // Register user for room they are currently in @@ -621,6 +671,7 @@ module.exports = (io, middlewares) => { ns().in(socket.room.accessCode).emit(Protocol.USER_LEFT, userId); ns().emit(Protocol.GLOBAL_ROOM_UPDATED, roomMask(room)); + scheduleRoomHide(room); if (room.doneWithScramble()) { logger.debug('everyone done, sending new scramble'); @@ -654,6 +705,7 @@ module.exports = (io, middlewares) => { ns().in(room.accessCode).emit(Protocol.UPDATE_ROOM, joinRoomMask(room)); ns().emit(Protocol.GLOBAL_ROOM_UPDATED, roomMask(room)); + scheduleRoomHide(room); if (room.doneWithScramble()) { logger.debug('everyone done, sending new scramble');