From c6decf599835ae06f9de9fd1c11f173633ac4598 Mon Sep 17 00:00:00 2001 From: William Gibson Date: Sun, 5 Jan 2020 12:05:53 +0000 Subject: [PATCH 1/8] Use DispatchEvents in classes --- package.json | 4 +- src/config/dispatcher.config.ts | 4 + src/config/redis.config.ts | 43 ++--- src/controllers/internal.controller.ts | 18 +- src/models/message/index.ts | 14 +- src/models/portal/defs.ts | 4 +- src/models/room/index.ts | 33 ++-- src/models/user/index.ts | 20 ++- src/server/index.ts | 22 ++- src/server/websocket/handlers/internal.ts | 32 ---- src/server/websocket/handlers/message.ts | 70 -------- .../websocket/handlers/undeliverable.ts | 32 ---- src/server/websocket/index.ts | 163 ++++++++---------- src/server/websocket/log.ts | 34 ---- src/server/websocket/models/event.ts | 36 ---- src/server/websocket/models/message.ts | 84 --------- src/server/websocket/models/socket.ts | 136 --------------- src/utils/fetchers.utils.ts | 6 + src/utils/validate.utils.ts | 4 +- yarn.lock | 23 +-- 20 files changed, 188 insertions(+), 594 deletions(-) create mode 100644 src/config/dispatcher.config.ts delete mode 100644 src/server/websocket/handlers/internal.ts delete mode 100644 src/server/websocket/handlers/message.ts delete mode 100644 src/server/websocket/handlers/undeliverable.ts delete mode 100644 src/server/websocket/log.ts delete mode 100644 src/server/websocket/models/event.ts delete mode 100644 src/server/websocket/models/message.ts delete mode 100644 src/server/websocket/models/socket.ts create mode 100644 src/utils/fetchers.utils.ts diff --git a/package.json b/package.json index c071631..2f2c25e 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "lint": "tslint src/**/*.ts{,x}" }, "dependencies": { + "@cryb/mesa": "^1.2.6", "axios": "^0.19.0", "biguint-format": "^1.0.1", "chance": "^1.0.18", @@ -34,8 +35,7 @@ "query-string": "^6.8.2", "supertest": "^4.0.2", "tsc-watch": "^2.2.1", - "typescript": "^3.5.3", - "ws": "^7.1.1" + "typescript": "^3.5.3" }, "devDependencies": { "@types/chance": "^1.0.6", diff --git a/src/config/dispatcher.config.ts b/src/config/dispatcher.config.ts new file mode 100644 index 0000000..74d8000 --- /dev/null +++ b/src/config/dispatcher.config.ts @@ -0,0 +1,4 @@ +import { Dispatcher } from '@cryb/mesa' +import { getOptions } from './redis.config' + +export default new Dispatcher(getOptions()) diff --git a/src/config/redis.config.ts b/src/config/redis.config.ts index 508cf1b..a8f2684 100644 --- a/src/config/redis.config.ts +++ b/src/config/redis.config.ts @@ -10,28 +10,29 @@ const parseSentinels = (sentinels: string) => sentinels.split(',').map(uri => ({ host: uri.split(':')[1].replace('//', ''), port: parseInt(uri.split(':')[2]) - } as ISentinel)), // Parse sentinels from process env - getOptions = () => { // Get Options Method - if (!process.env.REDIS_URI && !process.env.REDIS_SENTINELS) - throw new Error('No value was found for REDIS_URI or REDIS_SENTINELS - make sure .env is setup correctly!') - - if (process.env.REDIS_SENTINELS) - return { - sentinels: parseSentinels(process.env.REDIS_SENTINELS), - name: 'mymaster' - } as Redis.RedisOptions - - if (process.env.REDIS_URI) { - const uri = new URL(process.env.REDIS_URI) - - return { - host: uri.hostname || 'localhost', - port: parseInt(uri.port) || 6379, - db: parseInt(uri.pathname) || 0, - password: uri.password ? decodeURIComponent(uri.password) : null - } as Redis.RedisOptions - } + } as ISentinel)) // Parse sentinels from process env + +export const getOptions = () => { // Get Options Method + if (!process.env.REDIS_URI && !process.env.REDIS_SENTINELS) + throw new Error('No value was found for REDIS_URI or REDIS_SENTINELS - make sure .env is setup correctly!') + + if (process.env.REDIS_SENTINELS) + return { + sentinels: parseSentinels(process.env.REDIS_SENTINELS), + name: 'mymaster' + } as Redis.RedisOptions + + if (process.env.REDIS_URI) { + const uri = new URL(process.env.REDIS_URI) + + return { + host: uri.hostname || 'localhost', + port: parseInt(uri.port) || 6379, + db: parseInt(uri.pathname) || 0, + password: uri.password ? decodeURIComponent(uri.password) : null + } as Redis.RedisOptions } +} export const createPubSubClient = () => new Redis(getOptions()) diff --git a/src/controllers/internal.controller.ts b/src/controllers/internal.controller.ts index 9789139..cf89f9e 100644 --- a/src/controllers/internal.controller.ts +++ b/src/controllers/internal.controller.ts @@ -1,14 +1,16 @@ +import { Message } from '@cryb/mesa' import express from 'express' import Room from '../models/room' import StoredRoom from '../schemas/room.schema' import { PortalAllocationStatus } from '../models/room/defs' -import WSMessage from '../server/websocket/models/message' +import dispatcher from '../config/dispatcher.config' import authenticate from '../server/middleware/authenticate.internal.middleware' import { signApertureToken } from '../utils/aperture.utils' import { handleError, RoomNotFound } from '../utils/errors.utils' +import { fetchRoomMemberIds } from '../utils/fetchers.utils' const app = express() @@ -53,14 +55,14 @@ app.put('/portal', authenticate, async (req, res) => { /** * Broadcast allocation to all online clients */ - const updateMessage = new WSMessage(0, allocation, 'PORTAL_UPDATE') - updateMessage.broadcast(online) + const updateMessage = new Message(0, allocation, 'PORTAL_UPDATE') + dispatcher.dispatch(updateMessage, online) if (status === 'open') { const token = signApertureToken(id), - apertureMessage = new WSMessage(0, { ws: process.env.APERTURE_WS_URL, t: token }, 'APERTURE_CONFIG') + apertureMessage = new Message(0, { ws: process.env.APERTURE_WS_URL, t: token }, 'APERTURE_CONFIG') - apertureMessage.broadcast(online) + dispatcher.dispatch(apertureMessage, online) } } @@ -73,12 +75,12 @@ app.put('/portal', authenticate, async (req, res) => { app.post('/queue', authenticate, (req, res) => { const { queue } = req.body as { queue: string[] } - queue.forEach((id, i) => { + queue.forEach(async (id, i) => { try { const op = 0, d = { pos: i, len: queue.length }, t = 'PORTAL_QUEUE_UPDATE', - message = new WSMessage(op, d, t) + message = new Message(op, d, t) - message.broadcastRoom(id) + dispatcher.dispatch(message, await fetchRoomMemberIds(id)) } catch (error) { handleError(error, res) } diff --git a/src/models/message/index.ts b/src/models/message/index.ts index 1029cd7..fcf1c4f 100644 --- a/src/models/message/index.ts +++ b/src/models/message/index.ts @@ -1,14 +1,16 @@ +import { Message as MesaMessage } from '@cryb/mesa' + import Room, { RoomResolvable } from '../room' import User, { UserResolvable } from '../user' -import WSMessage from '../../server/websocket/models/message' - import StoredMessage from '../../schemas/message.schema' import IMessage from './defs' import { MessageNotFound, UserNotInRoom } from '../../utils/errors.utils' import { generateFlake } from '../../utils/generate.utils' import { extractRoomId, extractUserId } from '../../utils/helpers.utils' +import dispatcher from '../../config/dispatcher.config' +import { fetchRoomMemberIds } from '../../utils/fetchers.utils' export type MessageResolvable = Message | string @@ -67,8 +69,8 @@ export default class Message { this.setup(json) - const message = new WSMessage(0, this, 'MESSAGE_CREATE') - message.broadcastRoom(author.room, [author.id]) + const message = new MesaMessage(0, this, 'MESSAGE_CREATE') + dispatcher.dispatch(message, await fetchRoomMemberIds(author.room), [author.id]) resolve(this) } catch (error) { @@ -108,8 +110,8 @@ export default class Message { 'info.id': this.id }) - const message = new WSMessage(0, { id: this.id }, 'MESSAGE_DESTROY') - message.broadcastRoom(this.room, [extractUserId(requester || this.author)]) + const message = new MesaMessage(0, { id: this.id }, 'MESSAGE_DESTROY') + dispatcher.dispatch(message, await fetchRoomMemberIds(this.room), [extractUserId(requester || this.author)]) resolve() } catch (error) { diff --git a/src/models/portal/defs.ts b/src/models/portal/defs.ts index bc3423a..16ee8c8 100644 --- a/src/models/portal/defs.ts +++ b/src/models/portal/defs.ts @@ -1,7 +1,5 @@ -import IWSEvent from '../../server/websocket/models/event' - export type PortalEventType = 'PORTAL_CREATE' | 'PORTAL_OPEN' | 'PORTAL_UPDATE' | 'PORTAL_CLOSE' | 'PORTAL_DESTROY' -export interface IPortalEvent extends IWSEvent { +export interface IPortalEvent { t?: PortalEventType } diff --git a/src/models/room/index.ts b/src/models/room/index.ts index b995008..20d067a 100644 --- a/src/models/room/index.ts +++ b/src/models/room/index.ts @@ -1,3 +1,5 @@ +import { Message as MesaMessage } from '@cryb/mesa' + import Invite from '../invite' import Message from '../message' import User, { UserResolvable } from '../user' @@ -10,8 +12,8 @@ import { createPortal, destroyPortal } from '../../drivers/portals.driver' import StoredRoom from '../../schemas/room.schema' import IRoom, { IPortalAllocation, RoomType } from './defs' +import dispatcher from '../../config/dispatcher.config' import client from '../../config/redis.config' -import WSMessage from '../../server/websocket/models/message' import { ControllerIsNotAvailable, PortalNotOpen, @@ -22,6 +24,7 @@ import { } from '../../utils/errors.utils' import { generateFlake } from '../../utils/generate.utils' import { extractUserId, GroupedMessage, groupMessages } from '../../utils/helpers.utils' +import { fetchRoomMemberIds } from '../../utils/fetchers.utils' export type RoomResolvable = Room | string @@ -126,8 +129,8 @@ export default class Room { this.invites.push(invite) if (system) { - const message = new WSMessage(0, invite, 'INVITE_UPDATE') - message.broadcast([extractUserId(this.owner)]) + const message = new MesaMessage(0, invite, 'INVITE_UPDATE') + dispatcher.dispatch(message, [extractUserId(this.owner)]) } resolve(invite) @@ -148,8 +151,8 @@ export default class Room { } }) - const message = new WSMessage(0, { u: newOwnerId }, 'OWNER_UPDATE') - message.broadcastRoom(this) + const message = new MesaMessage(0, { u: newOwnerId }, 'OWNER_UPDATE') + dispatcher.dispatch(message, await fetchRoomMemberIds(this)) this.owner = to @@ -292,8 +295,8 @@ export default class Room { } }) - const message = new WSMessage(0, allocation, 'PORTAL_UPDATE') - message.broadcastRoom(this) + const message = new MesaMessage(0, allocation, 'PORTAL_UPDATE') + dispatcher.dispatch(message, await fetchRoomMemberIds(this)) this.portal = allocation @@ -346,8 +349,8 @@ export default class Room { client.hset('controller', this.id, fromId) - const message = new WSMessage(0, { u: fromId }, 'CONTROLLER_UPDATE') - message.broadcastRoom(this, [fromId]) + const message = new MesaMessage(0, { u: fromId }, 'CONTROLLER_UPDATE') + dispatcher.dispatch(message, await fetchRoomMemberIds(this), [fromId]) this.controller = fromId @@ -377,8 +380,8 @@ export default class Room { client.hset('controller', this.id, toId) - const message = new WSMessage(0, { u: toId }, 'CONTROLLER_UPDATE') - message.broadcastRoom(this, [fromId]) + const message = new MesaMessage(0, { u: toId }, 'CONTROLLER_UPDATE') + dispatcher.dispatch(message, await fetchRoomMemberIds(this), [fromId]) this.controller = toId @@ -407,8 +410,8 @@ export default class Room { client.hdel('controller', this.id) - const message = new WSMessage(0, { u: null }, 'CONTROLLER_UPDATE') - message.broadcastRoom(this, [senderId]) + const message = new MesaMessage(0, { u: null }, 'CONTROLLER_UPDATE') + dispatcher.dispatch(message, await fetchRoomMemberIds(this), [senderId]) this.controller = null @@ -461,8 +464,8 @@ export default class Room { public destroy = () => new Promise(async (resolve, reject) => { try { - const message = new WSMessage(0, {}, 'ROOM_DESTROY') - message.broadcastRoom(this) + const message = new MesaMessage(0, {}, 'ROOM_DESTROY') + dispatcher.dispatch(message, await fetchRoomMemberIds(this)) await StoredRoom.deleteOne({ 'info.id': this.id }) await StoredMessage.deleteMany({ 'info.room': this.id }) diff --git a/src/models/user/index.ts b/src/models/user/index.ts index 3046c79..687e3ad 100644 --- a/src/models/user/index.ts +++ b/src/models/user/index.ts @@ -1,3 +1,5 @@ +import { Message } from '@cryb/mesa' + import StoredUser from '../../schemas/user.schema' import IUser, { IDiscordCredentials, Role } from './defs' @@ -11,11 +13,13 @@ import StoredMessage from '../../schemas/message.schema' import config from '../../config/defaults.js' import client from '../../config/redis.config' -import WSMessage from '../../server/websocket/models/message' +import dispatcher from '../../config/dispatcher.config' + import { constructAvatar, exchangeRefreshToken, fetchUserProfile } from '../../services/oauth2/discord.service' import { TooManyMembers, UserNotFound, UserNotInRoom } from '../../utils/errors.utils' import { generateFlake, signToken } from '../../utils/generate.utils' import { extractUserId, UNALLOCATED_PORTALS_KEYS, extractRoomId } from '../../utils/helpers.utils' +import { fetchRoomMemberIds } from '../../utils/fetchers.utils' export type UserResolvable = User | string @@ -168,8 +172,8 @@ export default class User { this.icon = icon if (this.room) { - const message = new WSMessage(0, this, 'USER_UPDATE') - message.broadcastRoom(this.room, [this.id]) + const message = new Message(0, this, 'USER_UPDATE') + dispatcher.dispatch(message, await fetchRoomMemberIds(this.room), [this.id]) } resolve(this) @@ -181,7 +185,7 @@ export default class User { public signToken = () => new Promise(async (resolve, reject) => { try { const { id } = this, - token = await signToken({ id, type: 'user' }) + token = signToken({ id, type: 'user' }) resolve(token) } catch (error) { @@ -257,8 +261,8 @@ export default class User { ) createPortal(room) - const message = new WSMessage(0, { ...this, room: undefined }, 'USER_JOIN') - message.broadcastRoom(room) + const message = new Message(0, { ...this, room: undefined }, 'USER_JOIN') + dispatcher.dispatch(message, await fetchRoomMemberIds(room)) this.room = room @@ -295,8 +299,8 @@ export default class User { if (leavingUserIsOwner) this.room.transferOwnership(this.room.members[0]) - const message = new WSMessage(0, { u: this.id }, 'USER_LEAVE') - message.broadcastRoom(this.room) + const message = new Message(0, { u: this.id }, 'USER_LEAVE') + dispatcher.dispatch(message, await fetchRoomMemberIds(this.room)) } await StoredUser.updateOne({ diff --git a/src/server/index.ts b/src/server/index.ts index cc100dd..63da8e2 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -7,7 +7,8 @@ import cors from 'cors' import express, { json } from 'express' import helmet from 'helmet' import morgan from 'morgan' -import { Server } from 'ws' + +import Mesa from '@cryb/mesa' import { connect } from 'mongoose' @@ -15,6 +16,7 @@ import routes from './routes' import websocket from './websocket' import passport from '../config/passport.config' +import { getOptions } from '../config/redis.config' import { verify_env } from '../utils/verifications.utils' verify_env( @@ -32,7 +34,21 @@ verify_env( const app = express() const server = createServer(app) -const wss = new Server({ server }) +const mesa = new Mesa({ + server, + namespace: 'api', + redis: getOptions(), + + heartbeat: { + enabled: true, + interval: 10000, + maxAttempts: 3 + }, + reconnect: { + enabled: true, + interval: 5000 + } +}) connect(process.env.MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true }) @@ -44,6 +60,6 @@ app.use(morgan('dev')) app.use(passport.initialize()) routes(app) -websocket(wss) +websocket(mesa) export default server diff --git a/src/server/websocket/handlers/internal.ts b/src/server/websocket/handlers/internal.ts deleted file mode 100644 index 559d674..0000000 --- a/src/server/websocket/handlers/internal.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Server } from 'ws' - -import IWSEvent from '../models/event' - -import logMessage from '../log' - -export interface IWSInternalEvent { - message: IWSEvent - recipients: string[] - sync: boolean -} - -export default (message: IWSEvent, recipients: string[], _: boolean, wss: Server) => { - if (recipients.length === 0) - return - - if (process.env.NODE_ENV !== 'production') - logMessage(message, recipients) - - const delivered = [] - - // TODO: Sync for clients not on ws when event is global - if (recipients[0] === '*') - return wss.clients.forEach(client => client.send(JSON.stringify(message))) - else - Array.from(wss.clients) - .filter(client => recipients.indexOf(client['id']) > -1) - .forEach(client => { - delivered.push(client['id']) - client.send(JSON.stringify(message)) - }) -} diff --git a/src/server/websocket/handlers/message.ts b/src/server/websocket/handlers/message.ts deleted file mode 100644 index 2d47112..0000000 --- a/src/server/websocket/handlers/message.ts +++ /dev/null @@ -1,70 +0,0 @@ -import client, { createPubSubClient } from '../../../config/redis.config' - -import User from '../../../models/user' -import IWSEvent, { WSEventType } from '../models/event' -import WSMessage from '../models/message' -import WSSocket from '../models/socket' - -import { extractRoomId, extractUserId } from '../../../utils/helpers.utils' -import { validateControllerEvent } from '../../../utils/validate.utils' -import logMessage from '../log' - -const pub = createPubSubClient(), - CONTROLLER_EVENT_TYPES: WSEventType[] = [ - 'KEY_DOWN', - 'KEY_UP', - 'PASTE_TEXT', - 'MOUSE_MOVE', - 'MOUSE_SCROLL', - 'MOUSE_DOWN', - 'MOUSE_UP' - ] - -export default async (message: IWSEvent, socket: WSSocket) => { - const { op, d, t } = message - - if (process.env.NODE_ENV !== 'production') - logMessage(message) - - if (op === 0) { - if (t === 'TYPING_UPDATE') { - const typingUpdate = new WSMessage(0, { u: socket.user.id, typing: !!d.typing }, 'TYPING_UPDATE') - - typingUpdate.broadcastRoom(socket.user.room, [socket.user.id]) - } else if (CONTROLLER_EVENT_TYPES.indexOf(t) > -1) { - if (!validateControllerEvent(d, t)) - return - - if (!socket.user) - return // Check if the socket is actually authenticated - - if (!socket.user.room) - return // Check if the user is in a room - - if (typeof socket.user.room === 'string') - return // Check if room is unreadable - - if (!socket.user.room.portal.id) - socket.set('user', await new User().load(socket.user.id)) // Workaround for controller bug - - if (await client.hget('controller', extractRoomId(socket.user.room)) !== extractUserId(socket.user)) - return // Check if the user has the controller - - pub.publish('portals', JSON.stringify({ - op, - d: { - t: socket.user.room.portal.id, - ...d - }, - t - })) - } - } else if (op === 1) { // Heartbeat - socket.set('last_heartbeat_at', Date.now()) - socket.send(new WSMessage(11, {})) - } else if (op === 2) { // Identify - const { token } = d - - socket.authenticate(token) - } -} diff --git a/src/server/websocket/handlers/undeliverable.ts b/src/server/websocket/handlers/undeliverable.ts deleted file mode 100644 index c056c1a..0000000 --- a/src/server/websocket/handlers/undeliverable.ts +++ /dev/null @@ -1,32 +0,0 @@ -import IWSEvent, { WSEventType } from '../models/event' - -import client from '../../../config/redis.config' - -const DISALLOWED_UNDELIVERABLE_EVENT_TYPES: WSEventType[] = [ - 'PRESENCE_UPDATE', - 'TYPING_UPDATE' -] - -export default (message: IWSEvent, recipients: string[]) => { - if (DISALLOWED_UNDELIVERABLE_EVENT_TYPES.indexOf(message.t) > -1) - return - - recipients.forEach(async id => { - if (!id) - return - - try { - const _undelivered = await client.hget('undelivered_events', id) - let undelivered: IWSEvent[] = [] - - if (_undelivered) - undelivered = JSON.parse(_undelivered) - - undelivered.splice(0, 0, message) - - await client.hset('undelivered_events', id, JSON.stringify(undelivered)) - } catch (error) { - console.error(error) - } - }) -} diff --git a/src/server/websocket/index.ts b/src/server/websocket/index.ts index 0fd002a..e950385 100644 --- a/src/server/websocket/index.ts +++ b/src/server/websocket/index.ts @@ -1,133 +1,116 @@ -import WebSocket, { Server } from 'ws' +import Mesa, { Message } from '@cryb/mesa' -import IWSEvent from './models/event' -import WSMessage from './models/message' -import WSSocket from './models/socket' +import axios from 'axios' import config from '../../config/defaults' -import client, { createPubSubClient } from '../../config/redis.config' +import redis, { createPubSubClient } from '../../config/redis.config' import log from '../../utils/log.utils' -import { extractRoomId, extractUserId, UNALLOCATED_PORTALS_KEYS } from '../../utils/helpers.utils' -import handleInternalMessage, { IWSInternalEvent } from './handlers/internal' -import handleMessage from './handlers/message' - -/** - * Redis PUB/SUB - */ -const sub = createPubSubClient() - -type ConfigKey = 'c_heartbeat_interval' | 'c_reconnect_interval' | 'c_authentication_timeout' -const fetchConfigItem = async (key: ConfigKey) => parseInt(await client.hget('socket_config', key)) +import Room from '../../models/room' +import User from '../../models/user' -export default (wss: Server) => { - sub.on('message', async (_, data) => { - try { - const { message, recipients, sync }: IWSInternalEvent = JSON.parse(data) - - handleInternalMessage(message, recipients, sync, wss) - } catch (error) { - console.error('WS Error:', error) - } - }).subscribe('ws') +import { fetchRoomMemberIds } from '../../utils/fetchers.utils' +import { verifyToken } from '../../utils/generate.utils' +import { extractRoomId, extractUserId, UNALLOCATED_PORTALS_KEYS } from '../../utils/helpers.utils' +import { validateControllerEvent } from '../../utils/validate.utils' - wss.on('connection', async (ws: WebSocket) => { - const socket = new WSSocket(ws) +const CONTROLLER_EVENT_TYPES = ['KEY_DOWN', 'KEY_UP', 'PASTE_TEXT', 'MOUSE_MOVE', 'MOUSE_SCROLL', 'MOUSE_DOWN', 'MOUSE_UP'], + pub = createPubSubClient() +export default (mesa: Mesa) => { + mesa.on('connection', client => { log('Connection', 'ws', 'CYAN') - const c_heartbeat_interval = await fetchConfigItem('c_heartbeat_interval') || 10000, - c_reconnect_interval = await fetchConfigItem('c_reconnect_interval') || 5000, - c_authentication_timeout = await fetchConfigItem('c_authentication_timeout') || 10000 + client.authenticate(async ({ token }, done) => { + let id: string, + user: User - // Hello Socket - const op = 10, d = { c_heartbeat_interval, c_reconnect_interval, c_authentication_timeout } - socket.send(new WSMessage(op, d)) + try { + if (process.env.AUTH_BASE_URL) { + const { data } = await axios.post(process.env.AUTH_BASE_URL, { token }) + + user = new User(data) + id = user.id + } else { + id = verifyToken(token) as string + user = await new User().load(id) + } - const authentication_timeout = setTimeout(() => { - if (socket.authenticated) - return + done(null, { id, user }) + } catch (error) { + done(error, null) + } + }) - ws.close(1008) - }, c_authentication_timeout) + client.on('message', async message => { + const { opcode, data, type } = message - const maxHeartbeatTries = 3 - let heartbeatTries = 0 + if (type === 'TYPING_UPDATE') { + mesa.send( + new Message(0, { u: client.id, typing: !!data.typing }, 'TYPING_UPDATE'), + await fetchRoomMemberIds(client.user.room) + ) + } else if (CONTROLLER_EVENT_TYPES.indexOf(type) > -1) { + if (!validateControllerEvent(data, type)) return - const heartbeat_interval = setInterval(() => { - if (!socket.authenticated) - return + if (!client.user) + return // Check if the socket is actually authenticated - const offset = c_heartbeat_interval * 1.25 // Set the offset (leeway) of the last heartbeat at + if (!client.user.room) + return // Check if the user is in a room - if ((socket.lastHeartbeatAt - Date.now()) > -offset) - return heartbeatTries = 0 // If there has been a heartbeat update, return and reset the heartbeatTries variable + if (typeof client.user.room === 'string') + return // Check if room is unreadable - // If there have been no heartbeat updates - heartbeatTries += 1 // Increate heartbeat tries by one - if (heartbeatTries > maxHeartbeatTries) - // If there are more heartbeat tries than the maximum allowed heartbeat tries, close the connection - ws.close(1001) - else - // Else, send a new websocket message forcing a hearbeat, attaching the tries and max tries before termination - socket.send(new WSMessage(1, { tries: heartbeatTries, max: maxHeartbeatTries })) - }, c_heartbeat_interval) + if (!client.user.room.portal.id) + client.updateUser({ id: client.id, user: await new User().load(client.user.id) }) // Workaround for controller bug - ws.on('message', async data => { - let json: IWSEvent + if (await redis.hget('controller', extractRoomId(client.user.room)) !== extractUserId(client.user)) + return // Check if the user has the controller - try { - json = JSON.parse(data.toString()) - } catch (error) { - return ws.close(1007) + pub.publish('portals', JSON.stringify({ + op: opcode, + d: { + t: client.user.room.portal.id, + ...data + }, + t: type + })) } - - handleMessage(json, socket) }) - ws.on('close', async () => { - clearInterval(heartbeat_interval) - clearTimeout(authentication_timeout) + client.on('disconnect', async (code, reason) => { + log(`Disconnection ${client.authenticated ? `with id ${client.id}` : ''} code: ${code}, reason: ${reason}`, 'ws', 'CYAN') - if (socket.id) { - client.srem('connected_clients', socket.id) - client.hdel('client_sessions', socket.id) - } + if (client.authenticated && client.user.room) { + // if(await redis.hget('controller', roomId) === client.id) - log(`Disconnection ${socket.authenticated ? `with id ${socket.id}` : ''}`, 'ws', 'CYAN') - - if (socket.user && socket.user.room) { - const roomId = extractRoomId(socket.user.room) - - // if(await client.hget('controller', roomId) === socket.id) // We can use optimisation here in order to speed up the controller release cycle - const message = new WSMessage(0, { u: socket.id, presence: 'offline' }, 'PRESENCE_UPDATE') - message.broadcastRoom(roomId, [socket.id]) + const message = new Message(0, { u: client.id, presence: 'offline' }, 'PRESENCE_UPDATE') + mesa.send(message, await fetchRoomMemberIds(client.user.room)) - if (typeof socket.user.room === 'string') + if (typeof client.user.room === 'string') { try { - await socket.user.fetchRoom() + await client.user.fetchRoom() } catch (error) { return } // Room doesn't exists + } - if (typeof socket.user.room === 'string') + if (typeof client.user.room === 'string') return - const { room } = socket.user + const { room } = client.user as { room: Room } - if (extractUserId(room.controller) === socket.user.id) - room.releaseControl(socket.user) + if (extractUserId(room.controller) === client.user.id) + room.releaseControl(client.user) if (config.destroy_portal_when_empty) { setTimeout(async () => (await room.load(room.id)).fetchOnlineMemberIds().then(({ portal, online }) => { - if (online.length > 0) - return - - if (UNALLOCATED_PORTALS_KEYS.indexOf(portal.status) > -1) - return + if (online.length > 0) return + if (UNALLOCATED_PORTALS_KEYS.indexOf(portal.status) > -1) return room.destroyPortal() }).catch(console.error), config.empty_room_portal_destroy * 1000 diff --git a/src/server/websocket/log.ts b/src/server/websocket/log.ts deleted file mode 100644 index 46f940a..0000000 --- a/src/server/websocket/log.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { cloneDeep } from 'lodash' - -import IWSEvent from './models/event' - -import log, { ILogPrefix } from '../../utils/log.utils' - -export const WSLogPrefix: ILogPrefix = { - content: 'ws', - color: 'CYAN' -} - -export default (message: IWSEvent, recipients?: string[]) => { - const { op, d, t } = cloneDeep(message) - - let logline = `Opcode: ${op}, ` - - if (t) - logline += `type: ${t}, ` - - logline += `data: ` - - if (d['token'] && process.env.NODE_ENV === 'production') - d['token'] = 'redacted' - - logline += `${JSON.stringify(d)}` - - if (recipients) - if (recipients.length < 10) - logline += ` to ${JSON.stringify(recipients)}` - else - logline += ` to ${recipients.length} recipients` - - log(logline, [WSLogPrefix, { content: recipients ? 'emit' : 'recieve', color: 'MAGENTA' }]) -} diff --git a/src/server/websocket/models/event.ts b/src/server/websocket/models/event.ts deleted file mode 100644 index 0db8846..0000000 --- a/src/server/websocket/models/event.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { PortalEventType } from '../../../models/portal/defs' - -type WSEventIncomingType = -'PRESENCE_UPDATE' | -'TYPING_UPDATE' - -type WSEventIncomingMouseEventType = -'KEY_UP' | -'KEY_DOWN' | -'PASTE_TEXT' | -'MOUSE_MOVE' | -'MOUSE_SCROLL' | -'MOUSE_DOWN' | -'MOUSE_UP' - -type WSEventEmittingType = -'APERTURE_CONFIG' | -'ROOM_DESTROY' | -'MESSAGE_CREATE' | -'MESSAGE_DESTROY' | -'TYPING_UPDATE' | -'INVITE_UPDATE' | -'USER_JOIN' | -'USER_UPDATE' | -'USER_LEAVE' | -'OWNER_UPDATE' | -'CONTROLLER_UPDATE' | -'PORTAL_QUEUE_UPDATE' - -export type WSEventType = WSEventIncomingType | WSEventIncomingMouseEventType | WSEventEmittingType | PortalEventType - -export default interface IWSEvent { - op: number - d: any - t?: WSEventType -} diff --git a/src/server/websocket/models/message.ts b/src/server/websocket/models/message.ts deleted file mode 100644 index d36991f..0000000 --- a/src/server/websocket/models/message.ts +++ /dev/null @@ -1,84 +0,0 @@ -import handleUndeliverableMessage from '../handlers/undeliverable' - -import { IWSInternalEvent } from '../handlers/internal' -import IWSEvent, { WSEventType } from './event' - -import Room from '../../../models/room' -import StoredUser from '../../../schemas/user.schema' - -import client, { createPubSubClient } from '../../../config/redis.config' - -const pub = createPubSubClient() - -export default class WSMessage { - public opcode: number - public data: any - public type?: WSEventType - - constructor(opcode: number, data: any = {}, type?: WSEventType) { - this.opcode = opcode - this.data = data - this.type = type - } - - public broadcast = async (recipients: string[] = ['*'], excluding: string[] = [], sync: boolean = true) => { - recipients = recipients.filter(id => excluding.indexOf(id) === -1) - - if (recipients.length === 0) - return - - const message = this.serialize(), - internalMessage: IWSInternalEvent = { message, recipients, sync } - - pub.publish('ws', JSON.stringify(internalMessage)) - - if (recipients.length > 0) - try { - const online = await client.smembers('connected_clients') - - if (!online) - return - - const undelivered = recipients.filter(id => online.indexOf(id) === -1) - handleUndeliverableMessage(message, undelivered) - } catch (error) { - console.error(error) - } - } - - public broadcastRoom = async (room: Room | string, excluding: string[] = []) => { - if (!room) - return - - let id: string - - if (typeof room === 'string') - id = room - else - id = room.id - - try { - const recipients = ( - await StoredUser.distinct('info.id', { 'info.room': id }) - ).filter( - id => excluding.indexOf(id) === -1 - ) - - if (recipients.length === 0) - return - - this.broadcast(recipients) - } catch (error) { - console.error(error) - } - } - - public serialize = () => { - const object: IWSEvent = { op: this.opcode, d: this.data } - - if (this.type) - object.t = this.type - - return object - } -} diff --git a/src/server/websocket/models/socket.ts b/src/server/websocket/models/socket.ts deleted file mode 100644 index c3d0503..0000000 --- a/src/server/websocket/models/socket.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { camelCase } from 'lodash' - -import WebSocket from 'ws' - -import IWSEvent from './event' -import WSMessage from './message' - -import Room from '../../../models/room' -import User from '../../../models/user' - -import client from '../../../config/redis.config' - -import config from '../../../config/defaults' -import { signApertureToken } from '../../../utils/aperture.utils' -import { verifyToken } from '../../../utils/generate.utils' -import { extractUserId, UNALLOCATED_PORTALS_KEYS } from '../../../utils/helpers.utils' -import log from '../../../utils/log.utils' -import { WSLogPrefix } from '../log' - -type SocketConfigKey = 'id' | 'type' | 'user' | 'group' | 'authenticated' | 'last_heartbeat_at' -const socketKeys: SocketConfigKey[] = ['id', 'type', 'user', 'group', 'authenticated', 'last_heartbeat_at'] - -export default class WSSocket { - public id: string - - public user?: User - public room?: Room - public authenticated: boolean = false - public lastHeartbeatAt: number - - private socket: WebSocket - - constructor(socket: WebSocket) { - this.socket = socket - - socketKeys.forEach(key => { - if (socket[key]) - this[camelCase(key)] = socket[key] - }) - } - - public set(key: SocketConfigKey, value: any, save: boolean = true) { - this.socket[key] = value - this[camelCase(key)] = value - - if (save) this.save() - } - - public save() { - const { id, authenticated, lastHeartbeatAt } = this - - if (!id) - return - - client.hset('client_sessions', id, JSON.stringify({ id, authenticated, lastHeartbeatAt })) - } - - public send = (message: WSMessage) => this.socket.send(JSON.stringify(message.serialize())) - - public authenticate = async (_token: string) => { - const { id } = verifyToken(_token) as { id: string } - - try { - const user = await new User().load(id) - - if (!user) - return - - // Set user - this.set('user', user) - - // Presence Update - if (user.room) { - const { room } = user as { room: Room } - - const message = new WSMessage(0, { u: user.id, presence: 'online' }, 'PRESENCE_UPDATE') - message.broadcastRoom(room, [user.id]) - - if (room.portal.status !== 'open') - room.fetchMembers().then(({ members }) => { - if ( - members.length > (config.min_member_portal_creation_count - 1) && - UNALLOCATED_PORTALS_KEYS.indexOf(room.portal.status) > -1 - ) - room.createPortal() - }) - else if (room.portal.id) { - const token = signApertureToken(room.portal.id), - apertureMessage = new WSMessage(0, { ws: process.env.APERTURE_WS_URL, t: token }, 'APERTURE_CONFIG') - - apertureMessage.broadcast([extractUserId(user)]) - } - } - - // Log update - log(`Authenticated user ${user.name} (${user.id})`, [WSLogPrefix, { content: 'auth', color: 'GREEN' }], 'CYAN') - - this.sendUndeliverableMessages() - } catch (error) { - return this.socket.close(1013) - } - - const save = false - - this.set('id', id, save) - this.set('last_heartbeat_at', Date.now(), save) - this.set('authenticated', true, save) - - try { - await client.sadd('connected_clients', id) - } catch (error) { - console.error(error) - } - - if (!save) - this.save() - } - - public close = (code: number) => this.socket.close(code) - - private sendUndeliverableMessages = async () => { - try { - const _undelivered = await client.hget('undelivered_events', this.id) - let undelivered: IWSEvent[] = [] - - if (_undelivered) - undelivered = JSON.parse(_undelivered) - - undelivered.forEach((event, s) => this.socket.send(JSON.stringify({ ...event, s }))) - - client.hset('undelivered_events', this.id, JSON.stringify([])) - } catch (error) { - console.error(error) - } - } -} diff --git a/src/utils/fetchers.utils.ts b/src/utils/fetchers.utils.ts new file mode 100644 index 0000000..80dc382 --- /dev/null +++ b/src/utils/fetchers.utils.ts @@ -0,0 +1,6 @@ +import { RoomResolvable } from '../models/room' +import { extractRoomId } from './helpers.utils' + +import StoredUser from '../schemas/user.schema' + +export const fetchRoomMemberIds = (room: RoomResolvable) => StoredUser.distinct('info.id', { 'info.room': extractRoomId(room) }) \ No newline at end of file diff --git a/src/utils/validate.utils.ts b/src/utils/validate.utils.ts index a9c0107..116273a 100644 --- a/src/utils/validate.utils.ts +++ b/src/utils/validate.utils.ts @@ -1,5 +1,3 @@ -import { WSEventType } from '../server/websocket/models/event' - const validateKeyControllerEvent = data => { /** * TODO: Add proper key code range validation @@ -20,7 +18,7 @@ const validateControllerPosition = data => ( const validateControllerButton = (button: number) => button === 1 || button === 3 -export const validateControllerEvent = (data, type: WSEventType) => { +export const validateControllerEvent = (data, type) => { switch (type) { case 'KEY_DOWN': return validateKeyControllerEvent(data) diff --git a/yarn.lock b/yarn.lock index 5f109a1..fc294da 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18,6 +18,14 @@ esutils "^2.0.2" js-tokens "^4.0.0" +"@cryb/mesa@^1.2.6": + version "1.2.6" + resolved "https://registry.yarnpkg.com/@cryb/mesa/-/mesa-1.2.6.tgz#448061e7b59868037e50118e65c5523cf8143853" + integrity sha512-pUX/1yQ8C3ckaqvcgr7sqXOSrbYXIfVmb79mpJtIeHwaS+d8HBsMPPZkMRvz/rwhaWrlw5eGStKKQ+DCagMhww== + dependencies: + ioredis "^4.14.1" + ws "^7.2.1" + "@types/body-parser@*": version "1.17.0" resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.17.0.tgz#9f5c9d9bd04bb54be32d5eb9fc0d8c974e6cf58c" @@ -242,11 +250,6 @@ array-flatten@1.1.1: resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= -async-limiter@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" - integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -1636,12 +1639,10 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -ws@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.1.1.tgz#f9942dc868b6dffb72c14fd8f2ba05f77a4d5983" - integrity sha512-o41D/WmDeca0BqYhsr3nJzQyg9NF5X8l/UdnFNux9cS3lwB+swm8qGWX5rn+aD6xfBU3rGmtHij7g7x6LxFU3A== - dependencies: - async-limiter "^1.0.0" +ws@^7.2.1: + version "7.2.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.1.tgz#03ed52423cd744084b2cf42ed197c8b65a936b8e" + integrity sha512-sucePNSafamSKoOqoNfBd8V0StlkzJKL2ZAhGQinCfNQ+oacw+Pk7lcdAElecBF2VkLNZRiIb5Oi1Q5lVUVt2A== x-xss-protection@1.3.0: version "1.3.0" From 6d5c96763f3bcd101c18d22882468868cc4e742f Mon Sep 17 00:00:00 2001 From: William Gibson Date: Sun, 5 Jan 2020 12:42:54 +0000 Subject: [PATCH 2/8] Finalise mesa integration --- .env.example | 3 +++ package.json | 2 +- src/config/defaults.ts | 2 ++ src/config/dispatcher.config.ts | 4 +++- src/models/message/index.ts | 1 + src/models/room/index.ts | 5 +++-- src/server/index.ts | 15 +++++++++------ src/server/websocket/index.ts | 15 +++++++++++++-- yarn.lock | 8 ++++---- 9 files changed, 39 insertions(+), 16 deletions(-) diff --git a/.env.example b/.env.example index dd6f6b4..1c9317e 100644 --- a/.env.example +++ b/.env.example @@ -36,6 +36,9 @@ DISCORD_CALLBACK_URL=http://localhost:3000/auth/discord # This should be a list of comma (,) separated Discord OAuth2 Redirect URLs DISCORD_OAUTH_ORIGINS=http://localhost:3000 +# Optional: Mesa namespace. Defaults to api +# MESA_NAMESPACE=api + # Optional: User Configurable # The maximum amount of members a room can have (default 10) diff --git a/package.json b/package.json index 2f2c25e..d06204f 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "lint": "tslint src/**/*.ts{,x}" }, "dependencies": { - "@cryb/mesa": "^1.2.6", + "@cryb/mesa": "^1.2.9", "axios": "^0.19.0", "biguint-format": "^1.0.1", "chance": "^1.0.18", diff --git a/src/config/defaults.ts b/src/config/defaults.ts index 2e15b37..306d6a1 100644 --- a/src/config/defaults.ts +++ b/src/config/defaults.ts @@ -1,4 +1,6 @@ export default { + mesa_namespace: process.env.MESA_NAMESPACE || 'api', + /** * The amount of members a room needs to hit for a Portal (VM) to be created / queued (default 2) */ diff --git a/src/config/dispatcher.config.ts b/src/config/dispatcher.config.ts index 74d8000..8d10479 100644 --- a/src/config/dispatcher.config.ts +++ b/src/config/dispatcher.config.ts @@ -1,4 +1,6 @@ import { Dispatcher } from '@cryb/mesa' + +import config from '../config/defaults' import { getOptions } from './redis.config' -export default new Dispatcher(getOptions()) +export default new Dispatcher(getOptions(), { namespace: config.mesa_namespace }) diff --git a/src/models/message/index.ts b/src/models/message/index.ts index fcf1c4f..e3df39d 100644 --- a/src/models/message/index.ts +++ b/src/models/message/index.ts @@ -70,6 +70,7 @@ export default class Message { this.setup(json) const message = new MesaMessage(0, this, 'MESSAGE_CREATE') + console.log(message, await fetchRoomMemberIds(author.room), [author.id]) dispatcher.dispatch(message, await fetchRoomMemberIds(author.room), [author.id]) resolve(this) diff --git a/src/models/room/index.ts b/src/models/room/index.ts index 20d067a..35c24a4 100644 --- a/src/models/room/index.ts +++ b/src/models/room/index.ts @@ -12,8 +12,9 @@ import { createPortal, destroyPortal } from '../../drivers/portals.driver' import StoredRoom from '../../schemas/room.schema' import IRoom, { IPortalAllocation, RoomType } from './defs' -import dispatcher from '../../config/dispatcher.config' +import config from '../../config/defaults' import client from '../../config/redis.config' +import dispatcher from '../../config/dispatcher.config' import { ControllerIsNotAvailable, PortalNotOpen, @@ -192,7 +193,7 @@ export default class Room { public fetchOnlineMemberIds = () => new Promise(async (resolve, reject) => { try { const memberIds = await StoredUser.distinct('info.id', { 'info.room': this.id }), - connectedClientIds: string[] = await client.smembers('connected_clients') + connectedClientIds: string[] = await client.smembers(config.mesa_namespace ? `connected_clients_${config.mesa_namespace}` : 'connected_clients') this.online = connectedClientIds.filter(id => memberIds.indexOf(id) > -1) diff --git a/src/server/index.ts b/src/server/index.ts index 63da8e2..3a7bb3c 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -3,18 +3,18 @@ dotenv.config() import { createServer } from 'http' -import cors from 'cors' import express, { json } from 'express' -import helmet from 'helmet' -import morgan from 'morgan' - import Mesa from '@cryb/mesa' - import { connect } from 'mongoose' +import cors from 'cors' +import helmet from 'helmet' +import morgan from 'morgan' + import routes from './routes' import websocket from './websocket' +import config from '../config/defaults' import passport from '../config/passport.config' import { getOptions } from '../config/redis.config' import { verify_env } from '../utils/verifications.utils' @@ -36,7 +36,7 @@ const app = express() const server = createServer(app) const mesa = new Mesa({ server, - namespace: 'api', + namespace: config.mesa_namespace, redis: getOptions(), heartbeat: { @@ -47,6 +47,9 @@ const mesa = new Mesa({ reconnect: { enabled: true, interval: 5000 + }, + authentication: { + storeConnectedUsers: true } }) diff --git a/src/server/websocket/index.ts b/src/server/websocket/index.ts index e950385..b360a2d 100644 --- a/src/server/websocket/index.ts +++ b/src/server/websocket/index.ts @@ -25,6 +25,8 @@ export default (mesa: Mesa) => { let id: string, user: User + console.log(token, process.env.AUTH_BASE_URL) + try { if (process.env.AUTH_BASE_URL) { const { data } = await axios.post(process.env.AUTH_BASE_URL, { token }) @@ -32,12 +34,20 @@ export default (mesa: Mesa) => { user = new User(data) id = user.id } else { - id = verifyToken(token) as string + id = (verifyToken(token) as { id: string }).id user = await new User().load(id) } + mesa.send( + new Message(0, { u: id, presence: 'online'}, 'PRESENCE_UPDATE'), + await fetchRoomMemberIds(user.room), + [id] + ) + done(null, { id, user }) } catch (error) { + console.error(error) + done(error, null) } }) @@ -48,7 +58,8 @@ export default (mesa: Mesa) => { if (type === 'TYPING_UPDATE') { mesa.send( new Message(0, { u: client.id, typing: !!data.typing }, 'TYPING_UPDATE'), - await fetchRoomMemberIds(client.user.room) + await fetchRoomMemberIds(client.user.room), + [client.id] ) } else if (CONTROLLER_EVENT_TYPES.indexOf(type) > -1) { if (!validateControllerEvent(data, type)) return diff --git a/yarn.lock b/yarn.lock index fc294da..ecc15c9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18,10 +18,10 @@ esutils "^2.0.2" js-tokens "^4.0.0" -"@cryb/mesa@^1.2.6": - version "1.2.6" - resolved "https://registry.yarnpkg.com/@cryb/mesa/-/mesa-1.2.6.tgz#448061e7b59868037e50118e65c5523cf8143853" - integrity sha512-pUX/1yQ8C3ckaqvcgr7sqXOSrbYXIfVmb79mpJtIeHwaS+d8HBsMPPZkMRvz/rwhaWrlw5eGStKKQ+DCagMhww== +"@cryb/mesa@^1.2.9": + version "1.2.9" + resolved "https://registry.yarnpkg.com/@cryb/mesa/-/mesa-1.2.9.tgz#a3773038a7e56a8be3c6569f18f05ca37ed12d0e" + integrity sha512-yeN/a5R3Q2q8Y8DNKksyrVkh4daqanCiwMedPf0QMgOwZQzgPrWP5tVNqokhxXcoMtuh5yWF5wWwprjWq54cRQ== dependencies: ioredis "^4.14.1" ws "^7.2.1" From 34a284cc6155465f38bc04a39d207cb48beddc1a Mon Sep 17 00:00:00 2001 From: William Gibson Date: Sat, 30 May 2020 03:37:35 +0100 Subject: [PATCH 3/8] Upgrade Mesa --- package.json | 2 +- src/server/routes.ts | 2 +- yarn.lock | 14 ++++++++++---- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index d06204f..cb9d6fb 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "lint": "tslint src/**/*.ts{,x}" }, "dependencies": { - "@cryb/mesa": "^1.2.9", + "@cryb/mesa": "^1.4.3", "axios": "^0.19.0", "biguint-format": "^1.0.1", "chance": "^1.0.18", diff --git a/src/server/routes.ts b/src/server/routes.ts index 96f9e5c..988ccca 100644 --- a/src/server/routes.ts +++ b/src/server/routes.ts @@ -3,7 +3,7 @@ import { Application } from 'express' export default (app: Application) => { app.use('/auth', require('../controllers/auth.controller').default) - app.use('/admin', require('../controllers/admin.controller').default) + // app.use('/admin', require('../controllers/admin.controller').default) app.use('/internal', require('../controllers/internal.controller').default) app.use('/user', require('../controllers/user.controller').default) diff --git a/yarn.lock b/yarn.lock index ecc15c9..f5ac3ec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18,11 +18,12 @@ esutils "^2.0.2" js-tokens "^4.0.0" -"@cryb/mesa@^1.2.9": - version "1.2.9" - resolved "https://registry.yarnpkg.com/@cryb/mesa/-/mesa-1.2.9.tgz#a3773038a7e56a8be3c6569f18f05ca37ed12d0e" - integrity sha512-yeN/a5R3Q2q8Y8DNKksyrVkh4daqanCiwMedPf0QMgOwZQzgPrWP5tVNqokhxXcoMtuh5yWF5wWwprjWq54cRQ== +"@cryb/mesa@^1.4.3": + version "1.4.3" + resolved "https://registry.yarnpkg.com/@cryb/mesa/-/mesa-1.4.3.tgz#565f5c4eaf41f8d80de849c8c220017327215254" + integrity sha512-kv6ncqZ2QgbTYvAbKCdY+0F9NCsBQQYUwuRCbn9hsEXJcbaZh6gGqiCJUS97xS7o8s7lRUVJujMzD+P5mIAo6w== dependencies: + death "^1.1.0" ioredis "^4.14.1" ws "^7.2.1" @@ -466,6 +467,11 @@ dasherize@2.0.0: resolved "https://registry.yarnpkg.com/dasherize/-/dasherize-2.0.0.tgz#6d809c9cd0cf7bb8952d80fc84fa13d47ddb1308" integrity sha1-bYCcnNDPe7iVLYD8hPoT1H3bEwg= +death@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/death/-/death-1.1.0.tgz#01aa9c401edd92750514470b8266390c66c67318" + integrity sha1-AaqcQB7dknUFFEcLgmY5DGbGcxg= + debug@2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" From 47b50be79d3d667efd706e72cc1268397eb39631 Mon Sep 17 00:00:00 2001 From: William Gibson Date: Sat, 30 May 2020 03:39:45 +0100 Subject: [PATCH 4/8] Remove token log --- src/server/websocket/index.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/server/websocket/index.ts b/src/server/websocket/index.ts index b360a2d..47fec05 100644 --- a/src/server/websocket/index.ts +++ b/src/server/websocket/index.ts @@ -22,10 +22,8 @@ export default (mesa: Mesa) => { log('Connection', 'ws', 'CYAN') client.authenticate(async ({ token }, done) => { - let id: string, - user: User - - console.log(token, process.env.AUTH_BASE_URL) + let id: string + let user: User try { if (process.env.AUTH_BASE_URL) { From 5cb304b08202a27f52f82cca330bd0c9ed895da8 Mon Sep 17 00:00:00 2001 From: William Gibson Date: Sun, 31 May 2020 07:21:01 +0100 Subject: [PATCH 5/8] Clean mesa integration and spaces --- src/config/defaults.ts | 28 +- src/config/passport.config.ts | 98 +- src/config/redis.config.ts | 50 +- src/controllers/auth.controller.ts | 52 +- src/controllers/controller.controller.ts | 50 +- src/controllers/internal.controller.ts | 88 +- src/controllers/invite.controller.ts | 62 +- src/controllers/member.controller.ts | 36 +- src/controllers/message.controller.ts | 70 +- src/controllers/room.controller.ts | 220 ++-- src/controllers/user.controller.ts | 28 +- src/drivers/portals.driver.ts | 53 +- src/models/invite/defs.ts | 50 +- src/models/invite/index.ts | 364 +++--- src/models/message/defs.ts | 18 +- src/models/message/index.ts | 236 ++-- src/models/portal/defs.ts | 2 +- src/models/report/defs.ts | 18 +- src/models/report/index.ts | 172 +-- src/models/room/defs.ts | 50 +- src/models/room/index.ts | 1011 ++++++++--------- src/models/settings/defs.ts | 12 +- src/models/settings/index.ts | 32 +- src/models/user/ban/defs.ts | 20 +- src/models/user/ban/index.ts | 206 ++-- src/models/user/defs.ts | 44 +- src/models/user/index.ts | 604 +++++----- src/schemas/ban.schema.ts | 22 +- src/schemas/invite.schema.ts | 36 +- src/schemas/message.schema.ts | 20 +- src/schemas/report.schema.ts | 20 +- src/schemas/room.schema.ts | 38 +- src/schemas/user.schema.ts | 46 +- src/server/index.ts | 44 +- src/server/mesa/index.ts | 151 +++ .../authenticate.internal.middleware.ts | 22 +- src/server/routes.ts | 14 +- src/server/websocket/index.ts | 131 --- src/services/oauth2/discord.service.ts | 122 +- src/utils/errors.utils.ts | 306 ++--- src/utils/fetchers.utils.ts | 2 +- src/utils/helpers.utils.ts | 78 +- src/utils/log.utils.ts | 64 +- src/utils/validate.utils.ts | 52 +- src/utils/verifications.utils.ts | 8 +- tslint.json | 2 +- 46 files changed, 2410 insertions(+), 2442 deletions(-) create mode 100644 src/server/mesa/index.ts delete mode 100644 src/server/websocket/index.ts diff --git a/src/config/defaults.ts b/src/config/defaults.ts index 306d6a1..ba30ddb 100644 --- a/src/config/defaults.ts +++ b/src/config/defaults.ts @@ -1,34 +1,34 @@ export default { - mesa_namespace: process.env.MESA_NAMESPACE || 'api', + mesa_namespace: process.env.MESA_NAMESPACE || 'api', - /** + /** * The amount of members a room needs to hit for a Portal (VM) to be created / queued (default 2) */ - min_member_portal_creation_count: parseInt(process.env.MIN_MEMBER_PORTAL_CREATION_COUNT || '2'), + min_member_portal_creation_count: parseInt(process.env.MIN_MEMBER_PORTAL_CREATION_COUNT || '2'), - /** + /** * The maximum amount of members a room can have (default 10) */ - max_room_member_count: parseInt(process.env.MAX_ROOM_MEMBER_COUNT || '10'), + max_room_member_count: parseInt(process.env.MAX_ROOM_MEMBER_COUNT || '10'), - /** + /** * Whenever destroy portals when room gets empty */ - destroy_portal_when_empty: typeof process.env.DESTROY_PORTAL_WHEN_EMPTY !== 'undefined' ? - (process.env.DESTROY_PORTAL_WHEN_EMPTY === 'true') : true, + destroy_portal_when_empty: typeof process.env.DESTROY_PORTAL_WHEN_EMPTY !== 'undefined' ? + (process.env.DESTROY_PORTAL_WHEN_EMPTY === 'true') : true, - /** + /** * The timeout before an empty room gets their portal destroyed in seconds (default 5) */ - empty_room_portal_destroy: parseInt(process.env.EMPTY_ROOM_PORTAL_DESTROY || '5'), + empty_room_portal_destroy: parseInt(process.env.EMPTY_ROOM_PORTAL_DESTROY || '5'), - /** + /** * The user IDs that are allowed to create rooms, comma (,) separated */ - room_whitelist: (process.env.ROOM_WHITELIST === 'true'), + room_whitelist: (process.env.ROOM_WHITELIST === 'true'), - /** + /** * The user IDs that are allowed to create rooms, comma (,) separated */ - allowed_user_ids: (process.env.ALLOWED_USER_IDS || '').split(',') + allowed_user_ids: (process.env.ALLOWED_USER_IDS || '').split(',') } diff --git a/src/config/passport.config.ts b/src/config/passport.config.ts index c9441f8..182ee34 100644 --- a/src/config/passport.config.ts +++ b/src/config/passport.config.ts @@ -10,75 +10,75 @@ passport.serializeUser((user, done) => done(null, user)) passport.deserializeUser((id, done) => done(null, id)) passport.use(new Strategy({ - jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), - secretOrKey: process.env.JWT_KEY + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + secretOrKey: process.env.JWT_KEY }, async ({ id }, done) => { - const noSessionResponses = ['USER_NO_AUTH', 'USER_NOT_FOUND'] + const noSessionResponses = ['USER_NO_AUTH', 'USER_NOT_FOUND'] - try { - const user = await new User().load(id) + try { + const user = await new User().load(id) - return done(null, user) - } catch (error) { - if (error.response.indexOf(noSessionResponses)) - return done(UserNoAuth) + return done(null, user) + } catch (error) { + if (error.response.indexOf(noSessionResponses)) + return done(UserNoAuth) - done(error) - } + done(error) + } })) const BAN_SAFE_ENDPOINTS = [ - 'GET /user/me' + 'GET /user/me' ] const fetchEndpoint = (req: Request) => `${req.method} ${req.baseUrl}${req.route.path}` const fetchUser = async ( - req: Request, - res: Response, - next: NextFunction + req: Request, + res: Response, + next: NextFunction ) => new Promise(async (resolve, reject) => { - try { - if (process.env.AUTH_BASE_URL) { - const { authorization } = req.headers, - token = authorization.split(' ')[1], - { data: { resource } } = await axios.post(process.env.AUTH_BASE_URL, { token }), - user = new User(resource) - - resolve(user) - } else - passport.authenticate('jwt', { session: false }, async (err, user: User) => { - if (err) - return handleError(err, res) - - if (!user) - return handleError(UserNoAuth, res) - - resolve(user) - })(req, res, next) - } catch (error) { - reject(error) - } + try { + if (process.env.AUTH_BASE_URL) { + const { authorization } = req.headers, + token = authorization.split(' ')[1], + { data: { resource } } = await axios.post(process.env.AUTH_BASE_URL, { token }), + user = new User(resource) + + resolve(user) + } else + passport.authenticate('jwt', { session: false }, async (err, user: User) => { + if (err) + return handleError(err, res) + + if (!user) + return handleError(UserNoAuth, res) + + resolve(user) + })(req, res, next) + } catch (error) { + reject(error) + } }) export const authenticate = async (req: Request, res: Response, next: NextFunction) => { - try { - const user = await fetchUser(req, res, next), - endpoint = fetchEndpoint(req), - ban = await user.fetchBan() + try { + const user = await fetchUser(req, res, next), + endpoint = fetchEndpoint(req), + ban = await user.fetchBan() - if (ban && BAN_SAFE_ENDPOINTS.indexOf(endpoint) > -1) - return handleError(UserBanned, res) + if (ban && BAN_SAFE_ENDPOINTS.indexOf(endpoint) > -1) + return handleError(UserBanned, res) - if (req.baseUrl === '/admin' && user.roles.indexOf('admin') === -1) - return handleError(UserNoAuth, res) + if (req.baseUrl === '/admin' && user.roles.indexOf('admin') === -1) + return handleError(UserNoAuth, res) - req.user = user + req.user = user - next() - } catch (error) { - handleError(error, res) - } + next() + } catch (error) { + handleError(error, res) + } } export default passport diff --git a/src/config/redis.config.ts b/src/config/redis.config.ts index a8f2684..e0cf267 100644 --- a/src/config/redis.config.ts +++ b/src/config/redis.config.ts @@ -2,36 +2,36 @@ import Redis from 'ioredis' import { URL } from 'url' interface ISentinel { - host: string - port: number + host: string + port: number } const parseSentinels = (sentinels: string) => - sentinels.split(',').map(uri => ({ - host: uri.split(':')[1].replace('//', ''), - port: parseInt(uri.split(':')[2]) - } as ISentinel)) // Parse sentinels from process env + sentinels.split(',').map(uri => ({ + host: uri.split(':')[1].replace('//', ''), + port: parseInt(uri.split(':')[2]) + } as ISentinel)) // Parse sentinels from process env export const getOptions = () => { // Get Options Method - if (!process.env.REDIS_URI && !process.env.REDIS_SENTINELS) - throw new Error('No value was found for REDIS_URI or REDIS_SENTINELS - make sure .env is setup correctly!') - - if (process.env.REDIS_SENTINELS) - return { - sentinels: parseSentinels(process.env.REDIS_SENTINELS), - name: 'mymaster' - } as Redis.RedisOptions - - if (process.env.REDIS_URI) { - const uri = new URL(process.env.REDIS_URI) - - return { - host: uri.hostname || 'localhost', - port: parseInt(uri.port) || 6379, - db: parseInt(uri.pathname) || 0, - password: uri.password ? decodeURIComponent(uri.password) : null - } as Redis.RedisOptions - } + if (!process.env.REDIS_URI && !process.env.REDIS_SENTINELS) + throw new Error('No value was found for REDIS_URI or REDIS_SENTINELS - make sure .env is setup correctly!') + + if (process.env.REDIS_SENTINELS) + return { + sentinels: parseSentinels(process.env.REDIS_SENTINELS), + name: 'mymaster' + } as Redis.RedisOptions + + if (process.env.REDIS_URI) { + const uri = new URL(process.env.REDIS_URI) + + return { + host: uri.hostname || 'localhost', + port: parseInt(uri.port) || 6379, + db: parseInt(uri.pathname) || 0, + password: uri.password ? decodeURIComponent(uri.password) : null + } as Redis.RedisOptions + } } export const createPubSubClient = () => new Redis(getOptions()) diff --git a/src/controllers/auth.controller.ts b/src/controllers/auth.controller.ts index fe90bf7..212f7e2 100644 --- a/src/controllers/auth.controller.ts +++ b/src/controllers/auth.controller.ts @@ -7,45 +7,45 @@ import fetchDiscordTokens, { DISCORD_OAUTH_BASE_URL, DISCORD_OAUTH_SCOPES } from import { handleError } from '../utils/errors.utils' const app = express(), - origins = process.env.DISCORD_OAUTH_ORIGINS.split(',') + origins = process.env.DISCORD_OAUTH_ORIGINS.split(',') app.post('/discord', async (req, res) => { - if (origins.indexOf(req.get('origin')) === -1) - return res.sendStatus(401) + if (origins.indexOf(req.get('origin')) === -1) + return res.sendStatus(401) - const { code } = req.body + const { code } = req.body - try { - const { access_token, refresh_token, scope } = await fetchDiscordTokens(code), - user = await new User().findOrCreate(access_token, refresh_token, scope.split(' ')), - token = await user.signToken() + try { + const { access_token, refresh_token, scope } = await fetchDiscordTokens(code), + user = await new User().findOrCreate(access_token, refresh_token, scope.split(' ')), + token = await user.signToken() - res.send(token) - } catch (error) { - handleError(error, res) - } + res.send(token) + } catch (error) { + handleError(error, res) + } }) app.get('/discord/redirect', async (req, res) => { - let state = '' + let state = '' - if (req.query.invite) - state += `invite=${req.query.invite}` + if (req.query.invite) + state += `invite=${req.query.invite}` - const _params = { - response_type: 'code', - client_id: process.env.DISCORD_CLIENT_ID, - redirect_uri: process.env.DISCORD_CALLBACK_URL, - scope: DISCORD_OAUTH_SCOPES.join(' '), + const _params = { + response_type: 'code', + client_id: process.env.DISCORD_CLIENT_ID, + redirect_uri: process.env.DISCORD_CALLBACK_URL, + scope: DISCORD_OAUTH_SCOPES.join(' '), - state: null - } + state: null + } - if (state !== '') - _params.state = state + if (state !== '') + _params.state = state - const params = queryString.stringify(_params) - res.send(`${DISCORD_OAUTH_BASE_URL}?${params}`) + const params = queryString.stringify(_params) + res.send(`${DISCORD_OAUTH_BASE_URL}?${params}`) }) export default app diff --git a/src/controllers/controller.controller.ts b/src/controllers/controller.controller.ts index bf5cacb..13cb8ff 100644 --- a/src/controllers/controller.controller.ts +++ b/src/controllers/controller.controller.ts @@ -9,43 +9,43 @@ import { handleError } from '../utils/errors.utils' const app = express() app.post('/take', authenticate, async (req, res) => { - const { user } = req as { user: User } + const { user } = req as { user: User } - try { - const { room } = await user.fetchRoom() as { room: Room } - await room.takeControl(user) + try { + const { room } = await user.fetchRoom() as { room: Room } + await room.takeControl(user) - res.sendStatus(200) - } catch (error) { - handleError(error, res) - } + res.sendStatus(200) + } catch (error) { + handleError(error, res) + } }) app.post('/give/:id', authenticate, async (req, res) => { - const { user } = req as { user: User }, - { id: toId } = req.params + const { user } = req as { user: User }, + { id: toId } = req.params - try { - const { room } = await user.fetchRoom() as { room: Room } - await room.giveControl(toId, user) + try { + const { room } = await user.fetchRoom() as { room: Room } + await room.giveControl(toId, user) - res.sendStatus(200) - } catch (error) { - handleError(error, res) - } + res.sendStatus(200) + } catch (error) { + handleError(error, res) + } }) app.post('/release', authenticate, async (req, res) => { - const { user } = req as { user: User } + const { user } = req as { user: User } - try { - const { room } = await user.fetchRoom() as { room: Room } - await room.releaseControl(user) + try { + const { room } = await user.fetchRoom() as { room: Room } + await room.releaseControl(user) - res.sendStatus(200) - } catch (error) { - handleError(error, res) - } + res.sendStatus(200) + } catch (error) { + handleError(error, res) + } }) export default app diff --git a/src/controllers/internal.controller.ts b/src/controllers/internal.controller.ts index fd9f315..ca68ab8 100644 --- a/src/controllers/internal.controller.ts +++ b/src/controllers/internal.controller.ts @@ -18,75 +18,75 @@ const app = express() * Assign New Portal ID to Room */ app.post('/portal', authenticate, async (req, res) => { - const { id, roomId } = req.body as { id: string, roomId: string } + const { id, roomId } = req.body as { id: string, roomId: string } - try { - const room = await new Room().load(roomId) - await room.setPortalId(id) + try { + const room = await new Room().load(roomId) + await room.setPortalId(id) - res.sendStatus(200) - } catch (error) { - handleError(error, res) - } + res.sendStatus(200) + } catch (error) { + handleError(error, res) + } }) /** * Existing Portal Status Update */ app.put('/portal', authenticate, async (req, res) => { - const { id, status } = req.body as { id: string, status: PortalAllocationStatus } - // console.log('recieved', id, status, 'from portal microservice, finding room...') + const { id, status } = req.body as { id: string, status: PortalAllocationStatus } + // console.log('recieved', id, status, 'from portal microservice, finding room...') - try { - const doc = await StoredRoom.findOne({ 'info.portal.id': id }) + try { + const doc = await StoredRoom.findOne({ 'info.portal.id': id }) - if (!doc) - return RoomNotFound + if (!doc) + return RoomNotFound - // console.log('room found, updating status...') + // console.log('room found, updating status...') - const room = new Room(doc) - const { portal: allocation } = await room.updatePortalAllocation({ status }) - const { online } = await room.fetchOnlineMemberIds() + const room = new Room(doc) + const { portal: allocation } = await room.updatePortalAllocation({ status }) + const { online } = await room.fetchOnlineMemberIds() - // console.log('status updated and online members fetched:', online) + // console.log('status updated and online members fetched:', online) - if (online.length > 0) { - /** + if (online.length > 0) { + /** * Broadcast allocation to all online clients */ - const updateMessage = new Message(0, allocation, 'PORTAL_UPDATE') - dispatcher.dispatch(updateMessage, online) + const updateMessage = new Message(0, allocation, 'PORTAL_UPDATE') + dispatcher.dispatch(updateMessage, online) - if (status === 'open') { - const token = signApertureToken(id), - apertureMessage = new Message(0, { ws: process.env.APERTURE_WS_URL, t: token }, 'APERTURE_CONFIG') + if (status === 'open') { + const token = signApertureToken(id), + apertureMessage = new Message(0, { ws: process.env.APERTURE_WS_URL, t: token }, 'APERTURE_CONFIG') - dispatcher.dispatch(apertureMessage, online) - } - } + dispatcher.dispatch(apertureMessage, online) + } + } - res.sendStatus(200) - } catch (error) { - handleError(error, res) - } + res.sendStatus(200) + } catch (error) { + handleError(error, res) + } }) app.post('/queue', authenticate, (req, res) => { - const { queue } = req.body as { queue: string[] } + const { queue } = req.body as { queue: string[] } - queue.forEach(async (id, i) => { - try { - const op = 0, d = { pos: i, len: queue.length }, t = 'PORTAL_QUEUE_UPDATE', - message = new Message(op, d, t) + queue.forEach(async (id, i) => { + try { + const op = 0, d = { pos: i, len: queue.length }, t = 'PORTAL_QUEUE_UPDATE', + message = new Message(op, d, t) - dispatcher.dispatch(message, await fetchRoomMemberIds(id)) - } catch (error) { - handleError(error, res) - } - }) + dispatcher.dispatch(message, await fetchRoomMemberIds(id)) + } catch (error) { + handleError(error, res) + } + }) - res.sendStatus(200) + res.sendStatus(200) }) export default app diff --git a/src/controllers/invite.controller.ts b/src/controllers/invite.controller.ts index 3b3ab8d..5e5adcc 100644 --- a/src/controllers/invite.controller.ts +++ b/src/controllers/invite.controller.ts @@ -9,52 +9,52 @@ import { handleError, UserNotInRoom } from '../utils/errors.utils' const app = express() app.post('/', authenticate, async (req, res) => { - const { user } = req as { user: User } + const { user } = req as { user: User } - if (!user.room) - return handleError(UserNotInRoom, res) + if (!user.room) + return handleError(UserNotInRoom, res) - try { - const invite = await new Invite().create(null, 'room', null, { system: false }, req.user) + try { + const invite = await new Invite().create(null, 'room', null, { system: false }, req.user) - res.send(invite) - } catch (error) { - handleError(error, res) - } + res.send(invite) + } catch (error) { + handleError(error, res) + } }) app.post('/:code', authenticate, async (req, res) => { - const { user } = req as { user: User }, - { code } = req.params, - { type } = req.body + const { user } = req as { user: User }, + { code } = req.params, + { type } = req.body - try { - const invite = await new Invite().findFromCode(code), - target = await invite.use(user, type) + try { + const invite = await new Invite().findFromCode(code), + target = await invite.use(user, type) - if (!target) - return res.sendStatus(200) + if (!target) + return res.sendStatus(200) - res.send(target) - } catch (error) { - handleError(error, res) - } + res.send(target) + } catch (error) { + handleError(error, res) + } }) app.get('/:code/peek', async (req, res) => { - const { code } = req.params + const { code } = req.params - try { - const invite = await new Invite().findFromCode(code), - { target } = await invite.fetchTarget() + try { + const invite = await new Invite().findFromCode(code), + { target } = await invite.fetchTarget() - if (!target) - return res.sendStatus(401) + if (!target) + return res.sendStatus(401) - res.send(target) - } catch (error) { - handleError(error, res) - } + res.send(target) + } catch (error) { + handleError(error, res) + } }) export default app diff --git a/src/controllers/member.controller.ts b/src/controllers/member.controller.ts index 2bd7f64..efb071d 100644 --- a/src/controllers/member.controller.ts +++ b/src/controllers/member.controller.ts @@ -9,32 +9,32 @@ import { extractUserId } from '../utils/helpers.utils' const app = express() app.post('/:id/kick', authenticate, async (req, res) => { - const { user } = req as { user: User } + const { user } = req as { user: User } - if (!user.room) - return handleError(UserNotInRoom, res) + if (!user.room) + return handleError(UserNotInRoom, res) - const { id } = req.params + const { id } = req.params - if (typeof user.room === 'string') - return res.status(500) + if (typeof user.room === 'string') + return res.status(500) - if (extractUserId(user.room.owner) !== user.id) - return res.status(401) + if (extractUserId(user.room.owner) !== user.id) + return res.status(401) - try { - const { members } = await user.room.fetchMembers(), - member = members.find(({ id: userId }) => userId === id) + try { + const { members } = await user.room.fetchMembers(), + member = members.find(({ id: userId }) => userId === id) - if (!member) - return res.status(409) + if (!member) + return res.status(409) - await member.leaveRoom() + await member.leaveRoom() - res.sendStatus(200) - } catch (error) { - handleError(error, res) - } + res.sendStatus(200) + } catch (error) { + handleError(error, res) + } }) app.post('/:id/report', authenticate, async (_, res) => res.sendStatus(200)) diff --git a/src/controllers/message.controller.ts b/src/controllers/message.controller.ts index 9f06be9..edf983a 100644 --- a/src/controllers/message.controller.ts +++ b/src/controllers/message.controller.ts @@ -12,60 +12,60 @@ import { extractUserId } from '../utils/helpers.utils' const app = express() app.post('/', authenticate, async (req, res) => { - const { user } = req as { user: User } + const { user } = req as { user: User } - if (!user.room) - return handleError(UserNotInRoom, res) + if (!user.room) + return handleError(UserNotInRoom, res) - const { content } = req.body + const { content } = req.body - if (content.length === 0) - return handleError(MessageTooShort, res) + if (content.length === 0) + return handleError(MessageTooShort, res) - if (content.length >= 255) - return handleError(MessageTooLong, res) + if (content.length >= 255) + return handleError(MessageTooLong, res) - try { - const message = await new Message().create(content, user) + try { + const message = await new Message().create(content, user) - res.send(message) - } catch (error) { - handleError(error, res) - } + res.send(message) + } catch (error) { + handleError(error, res) + } }) app.post('/:id/report', authenticate, async (req, res) => { - const { user } = req as { user: User }, - { id: messageId } = req.params + const { user } = req as { user: User }, + { id: messageId } = req.params - try { - await new Report().create(messageId, user.room, user) + try { + await new Report().create(messageId, user.room, user) - res.sendStatus(200) - } catch (error) { - handleError(error, res) - } + res.sendStatus(200) + } catch (error) { + handleError(error, res) + } }) app.delete('/:id', authenticate, async (req, res) => { - const { user } = req as { user: User }, - { id: messageId } = req.params + const { user } = req as { user: User }, + { id: messageId } = req.params - try { - if (typeof user.room === 'string') - await user.fetchRoom() + try { + if (typeof user.room === 'string') + await user.fetchRoom() - const message = await new Message().load(messageId) + const message = await new Message().load(messageId) - if (extractUserId(message.author) !== user.id && extractUserId((user.room as Room).owner) !== user.id) - return res.sendStatus(401) + if (extractUserId(message.author) !== user.id && extractUserId((user.room as Room).owner) !== user.id) + return res.sendStatus(401) - await message.destroy(req.user) + await message.destroy(req.user) - res.sendStatus(200) - } catch (error) { - handleError(error, res) - } + res.sendStatus(200) + } catch (error) { + handleError(error, res) + } }) export default app diff --git a/src/controllers/room.controller.ts b/src/controllers/room.controller.ts index aa26419..a9ee359 100644 --- a/src/controllers/room.controller.ts +++ b/src/controllers/room.controller.ts @@ -9,186 +9,186 @@ import { RoomType } from '../models/room/defs' import { authenticate } from '../config/passport.config' import { - handleError, RoomNameTooLong, RoomNameTooShort, - UserAlreadyInRoom, UserNotAuthorized, UserNotInRoom + handleError, RoomNameTooLong, RoomNameTooShort, + UserAlreadyInRoom, UserNotAuthorized, UserNotInRoom } from '../utils/errors.utils' import { extractRoomId, extractUserId } from '../utils/helpers.utils' const app = express(), - AVAILABLE_TYPES: RoomType[] = ['vm'] + AVAILABLE_TYPES: RoomType[] = ['vm'] app.get('/', authenticate, async (req, res) => { - const { user } = req as { user: User } + const { user } = req as { user: User } - if (!user.room) - return handleError(UserNotInRoom, res) + if (!user.room) + return handleError(UserNotInRoom, res) - const roomId = extractRoomId(user.room) + const roomId = extractRoomId(user.room) - try { - const room = await new Room().load(roomId) + try { + const room = await new Room().load(roomId) - await room.fetchMembers() - await room.fetchMessages() - await room.fetchOnlineMemberIds() + await room.fetchMembers() + await room.fetchMessages() + await room.fetchOnlineMemberIds() - const ownerId = extractUserId(room.owner) + const ownerId = extractUserId(room.owner) - if (ownerId === user.id) - await room.fetchInvites() + if (ownerId === user.id) + await room.fetchInvites() - res.send(room.prepare()) - } catch (error) { - handleError(error, res) - } + res.send(room.prepare()) + } catch (error) { + handleError(error, res) + } }) app.post('/', authenticate, async (req, res) => { - const { user } = req as { user: User } + const { user } = req as { user: User } - if (user.room) - return handleError(UserAlreadyInRoom, res) + if (user.room) + return handleError(UserAlreadyInRoom, res) - if (config.room_whitelist && !config.allowed_user_ids.includes(user.id)) - return handleError(UserNotAuthorized, res) + if (config.room_whitelist && !config.allowed_user_ids.includes(user.id)) + return handleError(UserNotAuthorized, res) - const { name } = req.body + const { name } = req.body - if (name.length === 0) - return handleError(RoomNameTooShort, res) + if (name.length === 0) + return handleError(RoomNameTooShort, res) - if (name.length >= 30) - return handleError(RoomNameTooLong, res) + if (name.length >= 30) + return handleError(RoomNameTooLong, res) - try { - const room = await new Room().create(name, user) + try { + const room = await new Room().create(name, user) - res.send(room) - } catch (error) { - handleError(error, res) - } + res.send(room) + } catch (error) { + handleError(error, res) + } }) app.delete('/', authenticate, async (req, res) => { - const { user } = req as { user: User } + const { user } = req as { user: User } - if (!user.room) - return handleError(UserNotInRoom, res) + if (!user.room) + return handleError(UserNotInRoom, res) - try { - const { room } = await user.fetchRoom() as { room: Room } + try { + const { room } = await user.fetchRoom() as { room: Room } - if (extractUserId(room.owner) !== extractUserId(user)) - return res.sendStatus(401) + if (extractUserId(room.owner) !== extractUserId(user)) + return res.sendStatus(401) - await room.destroy() + await room.destroy() - res.sendStatus(200) - } catch (error) { - handleError(error, res) - } + res.sendStatus(200) + } catch (error) { + handleError(error, res) + } }) app.post('/portal/restart', authenticate, async (req, res) => { - const { user } = req as { user: User } + const { user } = req as { user: User } - if (!user.room) - return handleError(UserNotInRoom, res) + if (!user.room) + return handleError(UserNotInRoom, res) - if (typeof user.room === 'string') - return res.status(500) + if (typeof user.room === 'string') + return res.status(500) - if (extractUserId(user.room.owner) !== user.id) - return res.status(401) + if (extractUserId(user.room.owner) !== user.id) + return res.status(401) - try { - await user.room.restartPortal() + try { + await user.room.restartPortal() - res.sendStatus(200) - } catch (error) { - handleError(error, res) - } + res.sendStatus(200) + } catch (error) { + handleError(error, res) + } }) app.get('/invites', authenticate, async (req, res) => { - const { user } = req as { user: User } + const { user } = req as { user: User } - if (!user.room) - return handleError(UserNotInRoom, res) + if (!user.room) + return handleError(UserNotInRoom, res) - if (typeof user.room === 'string') - return res.status(500) + if (typeof user.room === 'string') + return res.status(500) - if (extractUserId(user.room.owner) !== user.id) - return res.status(401) + if (extractUserId(user.room.owner) !== user.id) + return res.status(401) - try { - const { invites } = await user.room.fetchInvites() + try { + const { invites } = await user.room.fetchInvites() - res.send(invites) - } catch (error) { - handleError(error, res) - } + res.send(invites) + } catch (error) { + handleError(error, res) + } }) app.post('/invite/refresh', authenticate, async (req, res) => { - const { user } = req as { user: User } + const { user } = req as { user: User } - if (!user.room) - return handleError(UserNotInRoom, res) + if (!user.room) + return handleError(UserNotInRoom, res) - if (typeof user.room === 'string') - return res.status(500) + if (typeof user.room === 'string') + return res.status(500) - if (extractUserId(user.room.owner) !== user.id) - return res.status(401) + if (extractUserId(user.room.owner) !== user.id) + return res.status(401) - try { - const invite = await user.room.refreshInvites(req.user, false) + try { + const invite = await user.room.refreshInvites(req.user, false) - res.send(invite) - } catch (error) { - handleError(error, res) - } + res.send(invite) + } catch (error) { + handleError(error, res) + } }) app.post('/leave', authenticate, async (req, res) => { - const { user } = req as { user: User } + const { user } = req as { user: User } - if (!user.room) - return handleError(UserNotInRoom, res) + if (!user.room) + return handleError(UserNotInRoom, res) - try { - await user.leaveRoom() + try { + await user.leaveRoom() - res.sendStatus(200) - } catch (error) { - handleError(error, res) - } + res.sendStatus(200) + } catch (error) { + handleError(error, res) + } }) app.patch('/type', authenticate, async (req, res) => { - const { user } = req as { user: User }, { type } = req.body as { type: RoomType } + const { user } = req as { user: User }, { type } = req.body as { type: RoomType } - if (!user.room) - return handleError(UserNotInRoom, res) + if (!user.room) + return handleError(UserNotInRoom, res) - if (typeof user.room === 'string') - return res.status(500) + if (typeof user.room === 'string') + return res.status(500) - if (extractUserId(user.room.owner) !== user.id) - return res.status(401) + if (extractUserId(user.room.owner) !== user.id) + return res.status(401) - if (AVAILABLE_TYPES.indexOf(type) === -1) - return res.status(406) + if (AVAILABLE_TYPES.indexOf(type) === -1) + return res.status(406) - try { - await user.room.updateType(type) + try { + await user.room.updateType(type) - res.sendStatus(200) - } catch (error) { - handleError(error, res) - } + res.sendStatus(200) + } catch (error) { + handleError(error, res) + } }) import ControllerController from './controller.controller' diff --git a/src/controllers/user.controller.ts b/src/controllers/user.controller.ts index d3b79a4..88a921a 100644 --- a/src/controllers/user.controller.ts +++ b/src/controllers/user.controller.ts @@ -10,27 +10,27 @@ const app = express() app.get('/me', authenticate, (req, res) => res.send(req.user)) app.post('/profile/refresh', authenticate, async (req, res) => { - const { user } = req as { user: User } + const { user } = req as { user: User } - try { - await user.refreshProfile() + try { + await user.refreshProfile() - res.send(user) - } catch (error) { - handleError(error, res) - } + res.send(user) + } catch (error) { + handleError(error, res) + } }) app.delete('/me', authenticate, async (req, res) => { - const { user } = req as { user: User } + const { user } = req as { user: User } - try { - await user.destroy() + try { + await user.destroy() - res.sendStatus(200) - } catch (error) { - handleError(error, res) - } + res.sendStatus(200) + } catch (error) { + handleError(error, res) + } }) export default app diff --git a/src/drivers/portals.driver.ts b/src/drivers/portals.driver.ts index 7f7f872..de00fa2 100644 --- a/src/drivers/portals.driver.ts +++ b/src/drivers/portals.driver.ts @@ -1,47 +1,50 @@ +import { Message as MesaMessage } from '@cryb/mesa' import axios from 'axios' import jwt from 'jsonwebtoken' +import dispatcher from '../config/dispatcher.config' + import Room from '../models/room' +import { extractUserId } from '../utils/helpers.utils' import log from '../utils/log.utils' -import WSMessage from '../server/websocket/models/message' const url = `${process.env.PORTALS_API_URL}/`, key = process.env.PORTALS_API_KEY const generateRoomToken = (room: Room) => jwt.sign({ roomId: room.id }, key), - generateHeaders = async (room: Room) => ({ - Authorization: `Valve ${generateRoomToken(room)}` - }) + generateHeaders = async (room: Room) => ({ + Authorization: `Valve ${generateRoomToken(room)}` + }) export const createPortal = (room: Room) => new Promise(async (resolve, reject) => { - try { - const headers = await generateHeaders(room) - log(`Sending request to ${url}create with room id: ${room.id}`, [{ content: 'portals', color: 'MAGENTA' }]) + try { + const headers = await generateHeaders(room) + log(`Sending request to ${url}create with room id: ${room.id}`, [{ content: 'portals', color: 'MAGENTA' }]) - const response = await axios.post(`${url}create`, { roomId: room.id }, { headers }) + const response = await axios.post(`${url}create`, { roomId: room.id }, { headers }) - const portalQueueMessage = new WSMessage(0, response.data, 'QUEUE_UPDATE') - portalQueueMessage.broadcastRoom(room) + const portalQueueMessage = new MesaMessage(0, response.data, 'QUEUE_UPDATE') + dispatcher.dispatch(portalQueueMessage, room.members.map(extractUserId)) - resolve() - } catch (error) { - console.log(`AXIOS POST FAILED: ${error}`) - reject(error) - } + resolve() + } catch (error) { + console.log(`AXIOS POST FAILED: ${error}`) + reject(error) + } }) export const destroyPortal = (room: Room) => new Promise(async (resolve, reject) => { - try { - const headers = await generateHeaders(room), - { portal } = room + try { + const headers = await generateHeaders(room), + { portal } = room - if (!portal.id) - return + if (!portal.id) + return - await axios.delete(`${url}${portal.id}`, { headers }) + await axios.delete(`${url}${portal.id}`, { headers }) - resolve() - } catch (error) { - reject(error) - } + resolve() + } catch (error) { + reject(error) + } }) diff --git a/src/models/invite/defs.ts b/src/models/invite/defs.ts index 24755d6..2e6e98c 100644 --- a/src/models/invite/defs.ts +++ b/src/models/invite/defs.ts @@ -1,42 +1,42 @@ import { Document } from 'mongoose' -import User from '../user' import Room from '../room' +import User from '../user' export type TargetResolvable = Room | User | string export type TargetType = 'room' export interface IInviteOptions { - // maxUses?: number - // expiresAfterFirstUse?: boolean - code?: string - random?: boolean - maxUses: number - unlimitedUses: boolean + // maxUses?: number + // expiresAfterFirstUse?: boolean + code?: string + random?: boolean + maxUses: number + unlimitedUses: boolean } export interface IInviteHeaders { - system: boolean + system: boolean } export default interface IInvite { - info: { - id: string - createdAt: number - createdBy: string - - active: boolean - system: boolean - - targetId: string - targetType: TargetType - }, - data: { - code: string - uses: string[] - - options: IInviteOptions - } + info: { + id: string + createdAt: number + createdBy: string + + active: boolean + system: boolean + + targetId: string + targetType: TargetType + }, + data: { + code: string + uses: string[] + + options: IInviteOptions + } } export interface IStoredInvite extends IInvite, Document { } diff --git a/src/models/invite/index.ts b/src/models/invite/index.ts index 23da351..13642a4 100644 --- a/src/models/invite/index.ts +++ b/src/models/invite/index.ts @@ -15,186 +15,186 @@ export type InviteResolvable = Invite | string const chance = new Chance() export default class Invite { - public id: string - public createdAt: number - public createdBy?: UserResolvable - - public active: boolean - - public target?: TargetResolvable - public targetType: TargetType - - public code: string - public uses: string[] - public options: IInviteOptions - - constructor(json?: IInvite) { - if (!json) - return - - this.setup(json) - } - - public load = (id: string) => new Promise(async (resolve, reject) => { - try { - const doc = await StoredInvite.findOne({ 'info.id': id }) - - if (!doc) - throw InviteNotFound - - this.setup(doc) - - resolve(this) - } catch (error) { - reject(error) - } - }) - - public findFromCode = (code: string) => new Promise(async (resolve, reject) => { - try { - const doc = await StoredInvite.findOne({ - $and: [ - { - 'info.active': true - }, - { - 'data.code': code - } - ] - }) - - if (!doc) - throw InviteNotFound - - this.setup(doc) - - resolve(this) - } catch (error) { - reject(error) - } - }) - - public use = (user: User, type?: TargetType) => new Promise(async (resolve, reject) => { - if (type && type !== this.targetType) - return reject(TargetTypeNotFound) - - if (this.targetType === 'room' && user.room) - return reject(UserAlreadyInRoom) - - try { - const targetId = extractTargetId(this.target), - query = { $set: {}, $push: { 'data.uses': user.id } } - - if ((this.uses.length + 1) >= this.options.maxUses && !this.options.unlimitedUses) - query.$set['info.active'] = false - - await StoredInvite.updateOne({ 'info.id': this.id }, query) - - switch (this.targetType) { - case 'room': - const room = await new Room().load(targetId) - await user.joinRoom(room) - - resolve(room) - break - default: - throw TargetTypeNotFound - } - } catch (error) { - reject(error) - } - }) - - public create = ( - target: TargetResolvable, - targetType: TargetType, - options?: IInviteOptions, - headers?: IInviteHeaders, - creator?: UserResolvable - ) => new Promise(async (resolve, reject) => { - try { - const json: IInvite = { - info: { - id: generateFlake(), - createdAt: Date.now(), - createdBy: extractUserId(creator), - - active: true, - system: headers ? headers.system || false : false, - - targetId: extractTargetId(target), - targetType - }, - data: { - code: !options.random && options.code ? options.code : chance.string({ - pool: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', - length: 5 - }), - uses: [], - - options: options ? { - maxUses: options.maxUses, - unlimitedUses: options.unlimitedUses - } : { - maxUses: 0, - unlimitedUses: true - } - } - } - - const stored = new StoredInvite(json) - await stored.save() - - this.setup(json) - - resolve(this) - } catch (error) { - reject(error) - } - }) - - public fetchTarget = () => new Promise(async (resolve, reject) => { - try { - const targetId = extractTargetId(this.target) - - switch (this.targetType) { - case 'room': - const room = await new Room().load(targetId) - await room.fetchMembers() - - this.target = room - } - - resolve(this) - } catch (error) { - reject(error) - } - }) - - public destroy = () => new Promise(async (resolve, reject) => { - try { - await StoredInvite.deleteOne({ - 'info.id': this.id - }) - - resolve() - } catch (error) { - reject(error) - } - }) - - public setup = (json: IInvite) => { - this.id = json.info.id - this.createdAt = json.info.createdAt - this.createdBy = json.info.createdBy - - this.active = json.info.active - - this.target = json.info.targetId - this.targetType = json.info.targetType - - this.code = json.data.code - this.uses = json.data.uses - this.options = json.data.options - } + public id: string + public createdAt: number + public createdBy?: UserResolvable + + public active: boolean + + public target?: TargetResolvable + public targetType: TargetType + + public code: string + public uses: string[] + public options: IInviteOptions + + constructor(json?: IInvite) { + if (!json) + return + + this.setup(json) + } + + public load = (id: string) => new Promise(async (resolve, reject) => { + try { + const doc = await StoredInvite.findOne({ 'info.id': id }) + + if (!doc) + throw InviteNotFound + + this.setup(doc) + + resolve(this) + } catch (error) { + reject(error) + } + }) + + public findFromCode = (code: string) => new Promise(async (resolve, reject) => { + try { + const doc = await StoredInvite.findOne({ + $and: [ + { + 'info.active': true + }, + { + 'data.code': code + } + ] + }) + + if (!doc) + throw InviteNotFound + + this.setup(doc) + + resolve(this) + } catch (error) { + reject(error) + } + }) + + public use = (user: User, type?: TargetType) => new Promise(async (resolve, reject) => { + if (type && type !== this.targetType) + return reject(TargetTypeNotFound) + + if (this.targetType === 'room' && user.room) + return reject(UserAlreadyInRoom) + + try { + const targetId = extractTargetId(this.target), + query = { $set: {}, $push: { 'data.uses': user.id } } + + if ((this.uses.length + 1) >= this.options.maxUses && !this.options.unlimitedUses) + query.$set['info.active'] = false + + await StoredInvite.updateOne({ 'info.id': this.id }, query) + + switch (this.targetType) { + case 'room': + const room = await new Room().load(targetId) + await user.joinRoom(room) + + resolve(room) + break + default: + throw TargetTypeNotFound + } + } catch (error) { + reject(error) + } + }) + + public create = ( + target: TargetResolvable, + targetType: TargetType, + options?: IInviteOptions, + headers?: IInviteHeaders, + creator?: UserResolvable + ) => new Promise(async (resolve, reject) => { + try { + const json: IInvite = { + info: { + id: generateFlake(), + createdAt: Date.now(), + createdBy: extractUserId(creator), + + active: true, + system: headers ? headers.system || false : false, + + targetId: extractTargetId(target), + targetType + }, + data: { + code: !options.random && options.code ? options.code : chance.string({ + pool: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', + length: 5 + }), + uses: [], + + options: options ? { + maxUses: options.maxUses, + unlimitedUses: options.unlimitedUses + } : { + maxUses: 0, + unlimitedUses: true + } + } + } + + const stored = new StoredInvite(json) + await stored.save() + + this.setup(json) + + resolve(this) + } catch (error) { + reject(error) + } + }) + + public fetchTarget = () => new Promise(async (resolve, reject) => { + try { + const targetId = extractTargetId(this.target) + + switch (this.targetType) { + case 'room': + const room = await new Room().load(targetId) + await room.fetchMembers() + + this.target = room + } + + resolve(this) + } catch (error) { + reject(error) + } + }) + + public destroy = () => new Promise(async (resolve, reject) => { + try { + await StoredInvite.deleteOne({ + 'info.id': this.id + }) + + resolve() + } catch (error) { + reject(error) + } + }) + + public setup = (json: IInvite) => { + this.id = json.info.id + this.createdAt = json.info.createdAt + this.createdBy = json.info.createdBy + + this.active = json.info.active + + this.target = json.info.targetId + this.targetType = json.info.targetType + + this.code = json.data.code + this.uses = json.data.uses + this.options = json.data.options + } } diff --git a/src/models/message/defs.ts b/src/models/message/defs.ts index c3adc5f..3a86e42 100644 --- a/src/models/message/defs.ts +++ b/src/models/message/defs.ts @@ -1,16 +1,16 @@ import { Document } from 'mongoose' export default interface IMessage { - info: { - id: string - createdAt: number + info: { + id: string + createdAt: number - author: string - room: string - } - data: { - content: string - } + author: string + room: string + } + data: { + content: string + } } export interface IStoredMessage extends IMessage, Document { } diff --git a/src/models/message/index.ts b/src/models/message/index.ts index e3df39d..6848c43 100644 --- a/src/models/message/index.ts +++ b/src/models/message/index.ts @@ -6,130 +6,130 @@ import User, { UserResolvable } from '../user' import StoredMessage from '../../schemas/message.schema' import IMessage from './defs' +import dispatcher from '../../config/dispatcher.config' import { MessageNotFound, UserNotInRoom } from '../../utils/errors.utils' +import { fetchRoomMemberIds } from '../../utils/fetchers.utils' import { generateFlake } from '../../utils/generate.utils' import { extractRoomId, extractUserId } from '../../utils/helpers.utils' -import dispatcher from '../../config/dispatcher.config' -import { fetchRoomMemberIds } from '../../utils/fetchers.utils' export type MessageResolvable = Message | string export default class Message { - public id: string - public createdAt: number - - public author: UserResolvable - public room: RoomResolvable - - public content: string - - constructor(json?: IMessage) { - if (!json) - return - - this.setup(json) - } + public id: string + public createdAt: number + + public author: UserResolvable + public room: RoomResolvable + + public content: string + + constructor(json?: IMessage) { + if (!json) + return + + this.setup(json) + } - public load = (id: string) => new Promise(async (resolve, reject) => { - try { - const doc = await StoredMessage.findOne({ 'info.id': id }) - - if (!doc) - throw MessageNotFound - - this.setup(doc) - - resolve(this) - } catch (error) { - reject(error) - } - }) - - public create = (content: string, author: User) => new Promise(async (resolve, reject) => { - if (!author.room) - return reject(UserNotInRoom) - - const roomId = extractRoomId(author.room) - - try { - const json: IMessage = { - info: { - id: generateFlake(), - createdAt: Date.now(), - author: author.id, - room: roomId - }, - data: { - content - } - } - - const stored = new StoredMessage(json) - await stored.save() - - this.setup(json) - - const message = new MesaMessage(0, this, 'MESSAGE_CREATE') - console.log(message, await fetchRoomMemberIds(author.room), [author.id]) - dispatcher.dispatch(message, await fetchRoomMemberIds(author.room), [author.id]) - - resolve(this) - } catch (error) { - reject(error) - } - }) - - public fetchAuthor = () => new Promise(async (resolve, reject) => { - const authorId = extractUserId(this.author) - - try { - const author = await new User().load(authorId) - this.author = author - - resolve(this) - } catch (error) { - reject(error) - } - }) - - public fetchRoom = () => new Promise(async (resolve, reject) => { - const roomId = extractRoomId(this.room) - - try { - const room = await new Room().load(roomId) - this.room = room - - resolve(this) - } catch (error) { - reject(error) - } - }) - - public destroy = (requester?: User) => new Promise(async (resolve, reject) => { - try { - await StoredMessage.deleteOne({ - 'info.id': this.id - }) - - const message = new MesaMessage(0, { id: this.id }, 'MESSAGE_DESTROY') - dispatcher.dispatch(message, await fetchRoomMemberIds(this.room), [extractUserId(requester || this.author)]) - - resolve() - } catch (error) { - reject(error) - } - }) - - public setup = (json: IMessage) => { - this.id = json.info.id - this.createdAt = json.info.createdAt - - this.content = json.data.content - - if (!this.author) - this.author = json.info.author - - if (!this.room) - this.room = json.info.room - } + public load = (id: string) => new Promise(async (resolve, reject) => { + try { + const doc = await StoredMessage.findOne({ 'info.id': id }) + + if (!doc) + throw MessageNotFound + + this.setup(doc) + + resolve(this) + } catch (error) { + reject(error) + } + }) + + public create = (content: string, author: User) => new Promise(async (resolve, reject) => { + if (!author.room) + return reject(UserNotInRoom) + + const roomId = extractRoomId(author.room) + + try { + const json: IMessage = { + info: { + id: generateFlake(), + createdAt: Date.now(), + author: author.id, + room: roomId + }, + data: { + content + } + } + + const stored = new StoredMessage(json) + await stored.save() + + this.setup(json) + + const message = new MesaMessage(0, this, 'MESSAGE_CREATE') + console.log(message, await fetchRoomMemberIds(author.room), [author.id]) + dispatcher.dispatch(message, await fetchRoomMemberIds(author.room), [author.id]) + + resolve(this) + } catch (error) { + reject(error) + } + }) + + public fetchAuthor = () => new Promise(async (resolve, reject) => { + const authorId = extractUserId(this.author) + + try { + const author = await new User().load(authorId) + this.author = author + + resolve(this) + } catch (error) { + reject(error) + } + }) + + public fetchRoom = () => new Promise(async (resolve, reject) => { + const roomId = extractRoomId(this.room) + + try { + const room = await new Room().load(roomId) + this.room = room + + resolve(this) + } catch (error) { + reject(error) + } + }) + + public destroy = (requester?: User) => new Promise(async (resolve, reject) => { + try { + await StoredMessage.deleteOne({ + 'info.id': this.id + }) + + const message = new MesaMessage(0, { id: this.id }, 'MESSAGE_DESTROY') + dispatcher.dispatch(message, await fetchRoomMemberIds(this.room), [extractUserId(requester || this.author)]) + + resolve() + } catch (error) { + reject(error) + } + }) + + public setup = (json: IMessage) => { + this.id = json.info.id + this.createdAt = json.info.createdAt + + this.content = json.data.content + + if (!this.author) + this.author = json.info.author + + if (!this.room) + this.room = json.info.room + } } diff --git a/src/models/portal/defs.ts b/src/models/portal/defs.ts index 16ee8c8..47ac80e 100644 --- a/src/models/portal/defs.ts +++ b/src/models/portal/defs.ts @@ -1,5 +1,5 @@ export type PortalEventType = 'PORTAL_CREATE' | 'PORTAL_OPEN' | 'PORTAL_UPDATE' | 'PORTAL_CLOSE' | 'PORTAL_DESTROY' export interface IPortalEvent { - t?: PortalEventType + t?: PortalEventType } diff --git a/src/models/report/defs.ts b/src/models/report/defs.ts index 9b7f4b7..75c0d18 100644 --- a/src/models/report/defs.ts +++ b/src/models/report/defs.ts @@ -1,15 +1,15 @@ import { Document } from 'mongoose' export default interface IReport { - info: { - id: string, - createdAt: number, - createdBy: string - }, - data: { - messageId: string, - roomId: string - } + info: { + id: string, + createdAt: number, + createdBy: string + }, + data: { + messageId: string, + roomId: string + } } export interface IStoredReport extends IReport, Document { } diff --git a/src/models/report/index.ts b/src/models/report/index.ts index 946e21e..d9c6c3c 100644 --- a/src/models/report/index.ts +++ b/src/models/report/index.ts @@ -10,90 +10,90 @@ import { generateFlake } from '../../utils/generate.utils' import { extractMessageId, extractRoomId, extractUserId } from '../../utils/helpers.utils' export default class Report { - public id: string - public createdAt: number - public createdBy: UserResolvable - - public message: MessageResolvable - public room: RoomResolvable - - constructor(json?: IReport) { - if (!json) - return - - this.setup(json) - } - - public load = () => new Promise(async (resolve, reject) => { - try { - const doc = await StoredReport.findOne({ 'info.id': this.id }) - - if (!doc) - throw ReportNotFound - - this.setup(doc) - - resolve(this) - } catch (error) { - reject(error) - } - }) - - public create = ( - message: MessageResolvable, - room: RoomResolvable, - reporter?: UserResolvable - ) => new Promise(async (resolve, reject) => { - try { - const messageId = extractMessageId(message), - roomId = extractRoomId(room) - - const json: IReport = { - info: { - id: generateFlake(), - createdAt: Date.now(), - createdBy: extractUserId(reporter) - }, - data: { - messageId, - roomId - } - } - - const stored = new StoredReport(json) - await stored.save() - - this.setup(json) - - resolve(this) - } catch (error) { - reject(error) - } - }) - - public destroy = () => new Promise(async (resolve, reject) => { - try { - await StoredReport.deleteOne({ - 'info.id': this.id - }) - - resolve() - } catch (error) { - reject(error) - } - }) - - public setup = (json: IReport) => { - this.id = json.info.id - this.createdAt = json.info.createdAt - - if (!this.createdBy) - this.createdBy = json.info.createdBy - - if (!this.message) - this.message = json.data.messageId - - if (!this.room) - this.room = json.data.roomId - } + public id: string + public createdAt: number + public createdBy: UserResolvable + + public message: MessageResolvable + public room: RoomResolvable + + constructor(json?: IReport) { + if (!json) + return + + this.setup(json) + } + + public load = () => new Promise(async (resolve, reject) => { + try { + const doc = await StoredReport.findOne({ 'info.id': this.id }) + + if (!doc) + throw ReportNotFound + + this.setup(doc) + + resolve(this) + } catch (error) { + reject(error) + } + }) + + public create = ( + message: MessageResolvable, + room: RoomResolvable, + reporter?: UserResolvable + ) => new Promise(async (resolve, reject) => { + try { + const messageId = extractMessageId(message), + roomId = extractRoomId(room) + + const json: IReport = { + info: { + id: generateFlake(), + createdAt: Date.now(), + createdBy: extractUserId(reporter) + }, + data: { + messageId, + roomId + } + } + + const stored = new StoredReport(json) + await stored.save() + + this.setup(json) + + resolve(this) + } catch (error) { + reject(error) + } + }) + + public destroy = () => new Promise(async (resolve, reject) => { + try { + await StoredReport.deleteOne({ + 'info.id': this.id + }) + + resolve() + } catch (error) { + reject(error) + } + }) + + public setup = (json: IReport) => { + this.id = json.info.id + this.createdAt = json.info.createdAt + + if (!this.createdBy) + this.createdBy = json.info.createdBy + + if (!this.message) + this.message = json.data.messageId + + if (!this.room) + this.room = json.data.roomId + } } diff --git a/src/models/room/defs.ts b/src/models/room/defs.ts index 706bdaf..ff3e182 100644 --- a/src/models/room/defs.ts +++ b/src/models/room/defs.ts @@ -15,39 +15,39 @@ export type RoomType = 'vm' */ export type PortalAllocationStatus = - 'waiting' | - 'requested' | - 'in-queue' | - 'creating' | - 'starting' | - 'open' | - 'closed' | - 'error' + 'waiting' | + 'requested' | + 'in-queue' | + 'creating' | + 'starting' | + 'open' | + 'closed' | + 'error' export interface IPortalAllocation { - id?: string + id?: string janusId?: number, janusIp?: string, - status: PortalAllocationStatus - lastUpdatedAt?: number + status: PortalAllocationStatus + lastUpdatedAt?: number } export default interface IRoom { - info: { - id: string - createdAt: number - endedAt?: number - - type: RoomType - portal?: IPortalAllocation - - owner: string - controller: string - } - profile: { - name: string - } + info: { + id: string + createdAt: number + endedAt?: number + + type: RoomType + portal?: IPortalAllocation + + owner: string + controller: string + } + profile: { + name: string + } } export interface IStoredRoom extends IRoom, Document { } diff --git a/src/models/room/index.ts b/src/models/room/index.ts index d0c00ae..47fbe33 100644 --- a/src/models/room/index.ts +++ b/src/models/room/index.ts @@ -12,539 +12,502 @@ import { createPortal, destroyPortal } from '../../drivers/portals.driver' import StoredRoom from '../../schemas/room.schema' import IRoom, { IPortalAllocation, RoomType } from './defs' -import config from '../../config/defaults' -import client from '../../config/redis.config' import dispatcher from '../../config/dispatcher.config' + +import client from '../../config/redis.config' +// import WSMessage from '../../server/websocket/models/message' import { - ControllerIsNotAvailable, - PortalNotOpen, - RoomNotFound, - UserAlreadyInRoom, - UserDoesNotHaveRemote, - UserIsNotPermitted + ControllerIsNotAvailable, + PortalNotOpen, + RoomNotFound, + UserAlreadyInRoom, + UserDoesNotHaveRemote, + UserIsNotPermitted } from '../../utils/errors.utils' import { generateFlake } from '../../utils/generate.utils' import { extractUserId, GroupedMessage, groupMessages } from '../../utils/helpers.utils' -import { fetchRoomMemberIds } from '../../utils/fetchers.utils' export type RoomResolvable = Room | string export default class Room { - public id: string - public createdAt: number - public endedAt?: number - - public type: RoomType - public active: boolean - public invites: Invite[] - public owner: UserResolvable - - public portal?: IPortalAllocation - public controller: UserResolvable - - public name: string - - public members: User[] - public messages: GroupedMessage[] = [] - - public online: string[] - - constructor(json?: IRoom) { - if(!json) return - - this.setup(json) - } - - public load = (id: string) => new Promise(async (resolve, reject) => { - try { - const doc = await StoredRoom.findOne({ 'info.id': id }) - if(!doc) - return reject(RoomNotFound) - - this.setup(doc) - - resolve(this) - } catch(error) { - reject(error) - } - }) - - public create = (name: string, creator: User) => new Promise(async (resolve, reject) => { - if(creator.room) - return reject(UserAlreadyInRoom) - - try { - const json: IRoom = { - info: { - id: generateFlake(), - createdAt: Date.now(), - - type: 'vm', - portal: { - status: 'waiting', - lastUpdatedAt: Date.now() - }, - - owner: creator.id, - controller: creator.id - }, - profile: { - name - } - } - - resolve(this) - } catch (error) { - reject(error) - } - }) - - /** - * The system prop indicates if the invite was created by a Cryb update - */ - public createInvite = (creator: User, system: boolean) => new Promise(async (resolve, reject) => { - try { - const invite = await new Invite().create( - this, - 'room', - { maxUses: 0, unlimitedUses: true }, - { system: true }, - creator - ) - - if (!this.invites) - this.invites = [] - - this.invites.push(invite) - - if (system) { - const message = new MesaMessage(0, invite, 'INVITE_UPDATE') - dispatcher.dispatch(message, [extractUserId(this.owner)]) - } - - resolve(invite) - } catch (error) { - reject(error) - } - }) - // const stored = new StoredRoom(json) - // await stored.save() - - // this.setup(json) - - // await this.createInvite(creator, false) // System prop false as data will be delivered over REST - // await creator.joinRoom(this, true) - - // client.hset('controller', this.id, creator.id) - - // resolve(this) - // } catch(error) { - // reject(error) - // } - // }) - - /** + public id: string + public createdAt: number + public endedAt?: number + + public type: RoomType + public active: boolean + public invites: Invite[] + public owner: UserResolvable + + public portal?: IPortalAllocation + public controller: UserResolvable + + public name: string + + public members: User[] + public messages: GroupedMessage[] = [] + + public online: string[] + + constructor(json?: IRoom) { + if (!json) return + + this.setup(json) + } + + public load = (id: string) => new Promise(async (resolve, reject) => { + try { + const doc = await StoredRoom.findOne({ 'info.id': id }) + if (!doc) + return reject(RoomNotFound) + + this.setup(doc) + + resolve(this) + } catch (error) { + reject(error) + } + }) + + public create = (name: string, creator: User) => new Promise(async (resolve, reject) => { + if (creator.room) + return reject(UserAlreadyInRoom) + + try { + const json: IRoom = { + info: { + id: generateFlake(), + createdAt: Date.now(), + + type: 'vm', + portal: { + status: 'waiting', + lastUpdatedAt: Date.now() + }, + + owner: creator.id, + controller: creator.id + }, + profile: { + name + } + } + + const stored = new StoredRoom(json) + await stored.save() + + this.setup(json) + + await this.createInvite(creator, false) // System prop false as data will be delivered over REST + await creator.joinRoom(this, true) + + client.hset('controller', this.id, creator.id) + + resolve(this) + } catch (error) { + reject(error) + } + }) + + /** * The system prop indicates if the invite was created by a Cryb update */ - public createInvite = (creator: User, system: boolean) => new Promise(async (resolve, reject) => { - try { - const invite = await new Invite().create( - this, - 'room', - { maxUses: 0, unlimitedUses: true }, - { system: true }, - creator - ) - - if(!this.invites) - this.invites = [] - - this.invites.push(invite) - - if(system) { - const message = new WSMessage(0, invite, 'INVITE_UPDATE') - message.broadcast([ extractUserId(this.owner) ]) - } - - resolve(invite) - } catch(error) { - reject(error) - } - }) - - public transferOwnership = (to: User) => new Promise(async (resolve, reject) => { - try { - const newOwnerId = extractUserId(to) - - await StoredRoom.updateOne({ - 'info.id': this.id - }, { - $set: { - 'info.owner': newOwnerId - } - }) - - const message = new WSMessage(0, { u: newOwnerId }, 'OWNER_UPDATE') - message.broadcastRoom(this) - - this.owner = to - - resolve(this) - } catch(error) { - reject(error) - } - }) - - public fetchMembers = (index: number = 0) => new Promise(async (resolve, reject) => { - try { - const docs = await StoredUser.find({ 'info.room': this.id }).skip(index).limit(10) - - if(docs.length === 0) - return resolve(this) - - const members = docs.map(doc => new User(doc)) - this.members = members - - const ownerId = extractUserId(this.owner), - controllerId = extractUserId(this.controller) - - members.forEach(member => { - if(ownerId === member.id) - this.owner = member - - if(controllerId === member.id) - this.controller = member - }) - - resolve(this) - } catch(error) { - reject(error) - } - }) - - public fetchOnlineMemberIds = () => new Promise(async (resolve, reject) => { - try { - const memberIds = await StoredUser.distinct('info.id', { 'info.room': this.id }), - connectedClientIds: string[] = await client.smembers('connected_clients') - - this.online = connectedClientIds.filter(id => memberIds.indexOf(id) > -1) - - resolve(this) - } catch(error) { - reject(error) - } - }) - - public fetchMessages = (index: number = 0) => new Promise(async (resolve, reject) => { - try { - const docs = await StoredMessage.find({ 'info.room': this.id }).sort({ 'info.createdAt': -1 }).skip(index).limit(50) - - if(docs.length === 0) - return resolve(this) - - const messages = docs.map(doc => new Message(doc)) - this.messages = groupMessages(messages.reverse()) - - resolve(this) - } catch(error) { - reject(error) - } - }) - - public fetchInvites = (index: number = 0) => new Promise(async (resolve, reject) => { - try { - const docs = await StoredInvite.find({ - $and: [ - { - 'info.targetId': this.id - }, - { - 'info.targetType': 'room' - }, - { - 'info.active': true - } - ] - }).skip(index).limit(10) - - if(docs.length === 0) - return resolve(this) - - const invites = docs.map(doc => new Invite(doc)) - this.invites = invites - - resolve(this) - } catch(error) { - reject(error) - } - }) - - public refreshInvites = (user: User, system: boolean) => new Promise(async (resolve, reject) => { - try { - await this.destroyInvites() - - const invite = await this.createInvite(user, system) - - resolve(invite) - } catch(error) { - reject(error) - } - }) - - public destroyInvites = () => new Promise(async (resolve, reject) => { - try { - await StoredInvite.deleteMany({ - $and: [ - { - 'info.targetId': this.id - }, - { - 'info.targetType': 'room' - } - ] - }) - - this.invites = [] - - resolve(this) - } catch(error) { - reject(error) - } - }) - - public setPortalId = (id: string) => new Promise(async (resolve, reject) => { - try { - const allocation: IPortalAllocation = { - id, - janusId: 1, - janusIp: '0.0.0.0', - status: 'creating', - lastUpdatedAt: Date.now() - } - - await StoredRoom.updateOne({ - 'info.id': this.id - }, { - $set: { - 'info.portal': allocation - } - }) - - const message = new WSMessage(0, allocation, 'PORTAL_UPDATE') - message.broadcastRoom(this) - - this.portal = allocation - - resolve(this) - } catch(error) { - reject(error) - } - }) - - public updatePortalAllocation = (allocation: IPortalAllocation) => new Promise(async (resolve, reject) => { - allocation.lastUpdatedAt = Date.now() - - try { - const currentAllocation = this.portal - Object.keys(allocation).forEach(key => currentAllocation[key] = allocation[key]) - - if(currentAllocation.status === 'closed') - delete currentAllocation.id - - await StoredRoom.updateOne({ - 'info.id': this.id - }, { - $set: { - 'info.portal': currentAllocation - } - }) - - this.portal = currentAllocation - - resolve(this) - } catch(error) { - reject(error) - } - }) - - const message = new MesaMessage(0, allocation, 'PORTAL_UPDATE') - dispatcher.dispatch(message, await fetchRoomMemberIds(this)) - - if(this.controller !== null) - return reject(ControllerIsNotAvailable) - - try { - await StoredRoom.updateOne({ - 'info.id' :this.id - }, { - $set: { - 'info.controller': fromId - } - }) - - client.hset('controller', this.id, fromId) - - const message = new WSMessage(0, { u: fromId }, 'CONTROLLER_UPDATE') - message.broadcastRoom(this, [ fromId ]) - - this.controller = fromId - - resolve(this) - } catch(error) { - reject(error) - } - }) - - public giveControl = (to: UserResolvable, from: UserResolvable) => new Promise(async (resolve, reject) => { - const ownerId = extractUserId(this.owner), - controllerId = extractUserId(this.controller), - toId = extractUserId(to), - fromId = extractUserId(from) - - if(fromId !== controllerId && fromId !== ownerId) - return reject(UserDoesNotHaveRemote) - - try { - await StoredRoom.updateOne({ - 'info.id': this.id - }, { - $set: { - 'info.controller': toId - } - }) - - client.hset('controller', this.id, toId) - - const message = new MesaMessage(0, { u: fromId }, 'CONTROLLER_UPDATE') - dispatcher.dispatch(message, await fetchRoomMemberIds(this), [fromId]) - - this.controller = toId - - resolve(this) - } catch(error) { - reject(error) - } - }) - - public releaseControl = (sender: UserResolvable) => new Promise(async (resolve, reject) => { - const ownerId = extractUserId(this.owner), - senderId = extractUserId(sender), - controllerId = extractUserId(this.controller) - - if(senderId !== ownerId && senderId !== controllerId) - return reject(UserIsNotPermitted) - - const message = new MesaMessage(0, { u: toId }, 'CONTROLLER_UPDATE') - dispatcher.dispatch(message, await fetchRoomMemberIds(this), [fromId]) - - try { - await StoredRoom.updateOne({ - 'info.id': this.id - }, { - $set: { - 'info.controller': null - } - }) - - client.hdel('controller', this.id) - - const message = new WSMessage(0, { u: null }, 'CONTROLLER_UPDATE') - message.broadcastRoom(this, [ senderId ]) - - this.controller = null - - resolve(this) - } catch(error) { - reject(error) - } - }) - - public createPortal = () => createPortal(this) - - public restartPortal = () => new Promise(async (resolve, reject) => { - if(this.portal.status !== 'open') - return reject(PortalNotOpen) - - try { - await this.destroyPortal() - await this.createPortal() - - resolve() - } catch(error) { - reject(error) - } - }) - - public destroyPortal = async () => { - await destroyPortal(this) - await this.updatePortalAllocation({ status: 'closed' }) - - delete this.portal - } - - public updateType = (type: RoomType) => new Promise(async (resolve, reject) => { - try { - await StoredRoom.updateOne({ - 'info.id': this.id - }, { - $set: { - 'info.type': type - } - }) - - this.type = type - - resolve(this) - } catch(error) { - reject(error) - } - }) - - public destroy = () => new Promise(async (resolve, reject) => { - try { - const message = new WSMessage(0, {}, 'ROOM_DESTROY') - message.broadcastRoom(this) - - await StoredRoom.deleteOne({ 'info.id': this.id }) - await StoredMessage.deleteMany({ 'info.room': this.id }) - - await StoredUser.updateMany({ - 'info.room': this.id - }, { - $unset: { - 'info.room': '' - } - }) - - await this.destroyInvites() - - if(this.portal) - destroyPortal(this) - - await client.hdel('controller', this.id) - - resolve() - } catch(error) { - reject(error) - } - }) - - public setup = (json: IRoom) => { - this.id = json.info.id - this.createdAt = json.info.createdAt - this.endedAt = json.info.endedAt - - this.type = json.info.type - this.portal = json.info.portal - - this.owner = json.info.owner - this.controller = json.info.controller - - this.name = json.profile.name - } - - public prepare = () => ({ - ...this, - members: this.members.map(member => typeof member === 'string' ? member : member.prepare()) - } as Room) + public createInvite = (creator: User, system: boolean) => new Promise(async (resolve, reject) => { + try { + const invite = await new Invite().create( + this, + 'room', + { maxUses: 0, unlimitedUses: true }, + { system: true }, + creator + ) + + if (!this.invites) + this.invites = [] + + this.invites.push(invite) + + if (system) { + const message = new MesaMessage(0, invite, 'INVITE_UPDATE') + dispatcher.dispatch(message, [extractUserId(this.owner)]) + } + + resolve(invite) + } catch (error) { + reject(error) + } + }) + + public transferOwnership = (to: User) => new Promise(async (resolve, reject) => { + try { + const newOwnerId = extractUserId(to) + + await StoredRoom.updateOne({ + 'info.id': this.id + }, { + $set: { + 'info.owner': newOwnerId + } + }) + + const message = new MesaMessage(0, { u: newOwnerId }, 'OWNER_UPDATE') + dispatcher.dispatch(message, this.members.map(extractUserId)) + + this.owner = to + + resolve(this) + } catch (error) { + reject(error) + } + }) + + public fetchMembers = (index: number = 0) => new Promise(async (resolve, reject) => { + try { + const docs = await StoredUser.find({ 'info.room': this.id }).skip(index).limit(10) + + if (docs.length === 0) + return resolve(this) + + const members = docs.map(doc => new User(doc)) + this.members = members + + const ownerId = extractUserId(this.owner), + controllerId = extractUserId(this.controller) + + members.forEach(member => { + if (ownerId === member.id) + this.owner = member + + if (controllerId === member.id) + this.controller = member + }) + + resolve(this) + } catch (error) { + reject(error) + } + }) + + public fetchOnlineMemberIds = () => new Promise(async (resolve, reject) => { + try { + const memberIds = await StoredUser.distinct('info.id', { 'info.room': this.id }), + connectedClientIds: string[] = await client.smembers('connected_clients') + + this.online = connectedClientIds.filter(id => memberIds.indexOf(id) > -1) + + resolve(this) + } catch (error) { + reject(error) + } + }) + + public fetchMessages = (index: number = 0) => new Promise(async (resolve, reject) => { + try { + const docs = await StoredMessage.find({ 'info.room': this.id }).sort({ 'info.createdAt': -1 }).skip(index).limit(50) + + if (docs.length === 0) + return resolve(this) + + const messages = docs.map(doc => new Message(doc)) + this.messages = groupMessages(messages.reverse()) + + resolve(this) + } catch (error) { + reject(error) + } + }) + + public fetchInvites = (index: number = 0) => new Promise(async (resolve, reject) => { + try { + const docs = await StoredInvite.find({ + $and: [ + { + 'info.targetId': this.id + }, + { + 'info.targetType': 'room' + }, + { + 'info.active': true + } + ] + }).skip(index).limit(10) + + if (docs.length === 0) + return resolve(this) + + const invites = docs.map(doc => new Invite(doc)) + this.invites = invites + + resolve(this) + } catch (error) { + reject(error) + } + }) + + public refreshInvites = (user: User, system: boolean) => new Promise(async (resolve, reject) => { + try { + await this.destroyInvites() + + const invite = await this.createInvite(user, system) + + resolve(invite) + } catch (error) { + reject(error) + } + }) + + public destroyInvites = () => new Promise(async (resolve, reject) => { + try { + await StoredInvite.deleteMany({ + $and: [ + { + 'info.targetId': this.id + }, + { + 'info.targetType': 'room' + } + ] + }) + + this.invites = [] + + resolve(this) + } catch (error) { + reject(error) + } + }) + + public setPortalId = (id: string) => new Promise(async (resolve, reject) => { + try { + const allocation: IPortalAllocation = { + id, + janusId: 1, + janusIp: '0.0.0.0', + status: 'creating', + lastUpdatedAt: Date.now() + } + + await StoredRoom.updateOne({ + 'info.id': this.id + }, { + $set: { + 'info.portal': allocation + } + }) + + const message = new MesaMessage(0, allocation, 'PORTAL_UPDATE') + dispatcher.dispatch(message, this.members.map(extractUserId)) + + this.portal = allocation + + resolve(this) + } catch (error) { + reject(error) + } + }) + + public updatePortalAllocation = (allocation: IPortalAllocation) => new Promise(async (resolve, reject) => { + allocation.lastUpdatedAt = Date.now() + + try { + const currentAllocation = this.portal + Object.keys(allocation).forEach(key => currentAllocation[key] = allocation[key]) + + if (currentAllocation.status === 'closed') + delete currentAllocation.id + + await StoredRoom.updateOne({ + 'info.id': this.id + }, { + $set: { + 'info.portal': currentAllocation + } + }) + + this.portal = currentAllocation + + resolve(this) + } catch (error) { + reject(error) + } + }) + + public takeControl = (from: UserResolvable) => new Promise(async (resolve, reject) => { + const fromId = extractUserId(from) + + if (this.controller !== null) + return reject(ControllerIsNotAvailable) + + try { + await StoredRoom.updateOne({ + 'info.id' :this.id + }, { + $set: { + 'info.controller': fromId + } + }) + + client.hset('controller', this.id, fromId) + + const message = new MesaMessage(0, { u: fromId }, 'CONTROLLER_UPDATE') + dispatcher.dispatch(message, this.members.map(extractUserId), [fromId]) + + this.controller = fromId + + resolve(this) + } catch (error) { + reject(error) + } + }) + + public giveControl = (to: UserResolvable, from: UserResolvable) => new Promise(async (resolve, reject) => { + const ownerId = extractUserId(this.owner), + controllerId = extractUserId(this.controller), + toId = extractUserId(to), + fromId = extractUserId(from) + + if (fromId !== controllerId && fromId !== ownerId) + return reject(UserDoesNotHaveRemote) + + try { + await StoredRoom.updateOne({ + 'info.id': this.id + }, { + $set: { + 'info.controller': toId + } + }) + + client.hset('controller', this.id, toId) + + const message = new MesaMessage(0, { u: toId }, 'CONTROLLER_UPDATE') + dispatcher.dispatch(message, this.members.map(extractUserId), [fromId]) + + this.controller = toId + + resolve(this) + } catch (error) { + reject(error) + } + }) + + public releaseControl = (sender: UserResolvable) => new Promise(async (resolve, reject) => { + const ownerId = extractUserId(this.owner), + senderId = extractUserId(sender), + controllerId = extractUserId(this.controller) + + if (senderId !== ownerId && senderId !== controllerId) + return reject(UserIsNotPermitted) + + try { + await StoredRoom.updateOne({ + 'info.id': this.id + }, { + $set: { + 'info.controller': null + } + }) + + client.hdel('controller', this.id) + + const message = new MesaMessage(0, { u: null }, 'CONTROLLER_UPDATE') + dispatcher.dispatch(message, this.members.map(extractUserId), [senderId]) + + this.controller = null + + resolve(this) + } catch (error) { + reject(error) + } + }) + + public createPortal = () => createPortal(this) + + public restartPortal = () => new Promise(async (resolve, reject) => { + if (this.portal.status !== 'open') + return reject(PortalNotOpen) + + try { + await this.destroyPortal() + await this.createPortal() + + resolve() + } catch (error) { + reject(error) + } + }) + + public destroyPortal = async () => { + await destroyPortal(this) + await this.updatePortalAllocation({ status: 'closed' }) + + delete this.portal + } + + public updateType = (type: RoomType) => new Promise(async (resolve, reject) => { + try { + await StoredRoom.updateOne({ + 'info.id': this.id + }, { + $set: { + 'info.type': type + } + }) + + this.type = type + + resolve(this) + } catch (error) { + reject(error) + } + }) + + public destroy = () => new Promise(async (resolve, reject) => { + try { + const message = new MesaMessage(0, {}, 'ROOM_DESTROY') + dispatcher.dispatch(message, this.members.map(extractUserId)) + + await StoredRoom.deleteOne({ 'info.id': this.id }) + await StoredMessage.deleteMany({ 'info.room': this.id }) + + await StoredUser.updateMany({ + 'info.room': this.id + }, { + $unset: { + 'info.room': '' + } + }) + + await this.destroyInvites() + + if (this.portal) + destroyPortal(this) + + await client.hdel('controller', this.id) + + resolve() + } catch (error) { + reject(error) + } + }) + + public setup = (json: IRoom) => { + this.id = json.info.id + this.createdAt = json.info.createdAt + this.endedAt = json.info.endedAt + + this.type = json.info.type + this.portal = json.info.portal + + this.owner = json.info.owner + this.controller = json.info.controller + + this.name = json.profile.name + } + + public prepare = () => ({ + ...this, + members: this.members.map(member => typeof member === 'string' ? member : member.prepare()) + } as Room) } diff --git a/src/models/settings/defs.ts b/src/models/settings/defs.ts index 0e6f622..6c962e1 100644 --- a/src/models/settings/defs.ts +++ b/src/models/settings/defs.ts @@ -1,10 +1,10 @@ export default interface ISettings { - signupsDisabled: boolean - signupsDisabledReason: string + signupsDisabled: boolean + signupsDisabledReason: string - maintenance: boolean - maintenanceReason: string + maintenance: boolean + maintenanceReason: string - roomLimit: number - roomLimitReason: string + roomLimit: number + roomLimitReason: string } diff --git a/src/models/settings/index.ts b/src/models/settings/index.ts index 49542e4..e5074e4 100644 --- a/src/models/settings/index.ts +++ b/src/models/settings/index.ts @@ -3,24 +3,24 @@ import ISettings from './defs' import client from '../../config/redis.config' export default class Settings { - public patch = (update: ISettings) => new Promise(async (resolve, reject) => { - try { - const current = await client.hgetall('settings') as ISettings + public patch = (update: ISettings) => new Promise(async (resolve, reject) => { + try { + const current = await client.hgetall('settings') as ISettings - Object.keys(update).forEach(key => { - if (!update[key]) - return + Object.keys(update).forEach(key => { + if (!update[key]) + return - current[key] = update[key] - }) + current[key] = update[key] + }) - const keys = Object.keys(current).filter(key => !!key) - for (let i = 0; i < keys.length; i++) - await client.hset('settings', keys[i], current[keys[i]]) + const keys = Object.keys(current).filter(key => !!key) + for (let i = 0; i < keys.length; i++) + await client.hset('settings', keys[i], current[keys[i]]) - resolve(current) - } catch (error) { - reject(error) - } - }) + resolve(current) + } catch (error) { + reject(error) + } + }) } diff --git a/src/models/user/ban/defs.ts b/src/models/user/ban/defs.ts index a49f262..27529be 100644 --- a/src/models/user/ban/defs.ts +++ b/src/models/user/ban/defs.ts @@ -1,17 +1,17 @@ import { Document } from 'mongoose' export default interface IBan { - info: { - id: string - createdAt: number - createdBy: string + info: { + id: string + createdAt: number + createdBy: string - active: boolean - } - data: { - userId: string - reason: string - } + active: boolean + } + data: { + userId: string + reason: string + } } export interface IStoredBan extends IBan, Document { } diff --git a/src/models/user/ban/index.ts b/src/models/user/ban/index.ts index b250328..d2d5f59 100644 --- a/src/models/user/ban/index.ts +++ b/src/models/user/ban/index.ts @@ -8,107 +8,107 @@ import { generateFlake } from '../../../utils/generate.utils' import { extractUserId } from '../../../utils/helpers.utils' export default class Ban { - public id: string - public createdAt: number - public createdBy: UserResolvable - public active: boolean - - public user: UserResolvable - public reason: string - - constructor(json?: IBan) { - if (!json) - return - - this.setup(json) - } - - public load = (id: string) => new Promise(async (resolve, reject) => { - try { - const doc = await StoredBan.findOne({ 'info.id': id }) - if (!doc) throw BanNotFound - - this.setup(doc) - - resolve(this) - } catch (error) { - reject(error) - } - }) - - public create = ( - user: UserResolvable, - reason?: string, - from?: UserResolvable - ) => new Promise(async (resolve, reject) => { - try { - const existing = await StoredBan.find({ - $and: [ - { - 'info.active': true - }, - { - 'data.userId': extractUserId(user) - } - ] - }) - - if (existing.length > 0) - throw BanAlreadyExists - - const json: IBan = { - info: { - id: generateFlake(), - createdAt: Date.now(), - createdBy: extractUserId(from), - active: true - }, - data: { - userId: extractUserId(user), - reason - } - } - - const stored = new StoredBan(json) - await stored.save() - - this.setup(json) - - resolve(this) - } catch (error) { - reject(error) - } - }) - - public setActive = (active: boolean) => new Promise(async (resolve, reject) => { - try { - await StoredBan.updateOne({ - 'info.id': this.id - }, { - $set: { - 'info.active': active - } - }) - - this.active = active - - resolve(this) - } catch (error) { - reject(error) - } - }) - - public setup = (json: IBan) => { - this.id = json.info.id - this.createdAt = json.info.createdAt - this.active = json.info.active - - this.reason = json.data.reason - - if (!this.createdBy) - this.createdBy = json.info.createdBy - - if (!this.user) - this.user = json.data.userId - } + public id: string + public createdAt: number + public createdBy: UserResolvable + public active: boolean + + public user: UserResolvable + public reason: string + + constructor(json?: IBan) { + if (!json) + return + + this.setup(json) + } + + public load = (id: string) => new Promise(async (resolve, reject) => { + try { + const doc = await StoredBan.findOne({ 'info.id': id }) + if (!doc) throw BanNotFound + + this.setup(doc) + + resolve(this) + } catch (error) { + reject(error) + } + }) + + public create = ( + user: UserResolvable, + reason?: string, + from?: UserResolvable + ) => new Promise(async (resolve, reject) => { + try { + const existing = await StoredBan.find({ + $and: [ + { + 'info.active': true + }, + { + 'data.userId': extractUserId(user) + } + ] + }) + + if (existing.length > 0) + throw BanAlreadyExists + + const json: IBan = { + info: { + id: generateFlake(), + createdAt: Date.now(), + createdBy: extractUserId(from), + active: true + }, + data: { + userId: extractUserId(user), + reason + } + } + + const stored = new StoredBan(json) + await stored.save() + + this.setup(json) + + resolve(this) + } catch (error) { + reject(error) + } + }) + + public setActive = (active: boolean) => new Promise(async (resolve, reject) => { + try { + await StoredBan.updateOne({ + 'info.id': this.id + }, { + $set: { + 'info.active': active + } + }) + + this.active = active + + resolve(this) + } catch (error) { + reject(error) + } + }) + + public setup = (json: IBan) => { + this.id = json.info.id + this.createdAt = json.info.createdAt + this.active = json.info.active + + this.reason = json.data.reason + + if (!this.createdBy) + this.createdBy = json.info.createdBy + + if (!this.user) + this.user = json.data.userId + } } diff --git a/src/models/user/defs.ts b/src/models/user/defs.ts index 5ebb7bf..ad5e4f7 100644 --- a/src/models/user/defs.ts +++ b/src/models/user/defs.ts @@ -5,39 +5,39 @@ export type Role = 'admin' | 'invited' export type CredentialType = 'regular' | 'discord' export interface IRegularCredentials { - email: string - password: string + email: string + password: string } export interface IDiscordCredentials { - userId: string - accessToken: string - refreshToken: string - scopes: string[] + userId: string + accessToken: string + refreshToken: string + scopes: string[] } export type Credentials = IRegularCredentials | IDiscordCredentials export interface IProfile { - name: string - icon: string + name: string + icon: string } export default interface IUser { - info: { - id: string - joinedAt: number - username: string - roles: Role[] - - room?: string - invite?: string - } - security: { - type: CredentialType - credentials: Credentials - } - profile: IProfile + info: { + id: string + joinedAt: number + username: string + roles: Role[] + + room?: string + invite?: string + } + security: { + type: CredentialType + credentials: Credentials + } + profile: IProfile } export interface IStoredUser extends IUser, Document { } diff --git a/src/models/user/index.ts b/src/models/user/index.ts index 687e3ad..3bcf546 100644 --- a/src/models/user/index.ts +++ b/src/models/user/index.ts @@ -12,347 +12,347 @@ import StoredBan from '../../schemas/ban.schema' import StoredMessage from '../../schemas/message.schema' import config from '../../config/defaults.js' -import client from '../../config/redis.config' import dispatcher from '../../config/dispatcher.config' +import client from '../../config/redis.config' import { constructAvatar, exchangeRefreshToken, fetchUserProfile } from '../../services/oauth2/discord.service' import { TooManyMembers, UserNotFound, UserNotInRoom } from '../../utils/errors.utils' -import { generateFlake, signToken } from '../../utils/generate.utils' -import { extractUserId, UNALLOCATED_PORTALS_KEYS, extractRoomId } from '../../utils/helpers.utils' import { fetchRoomMemberIds } from '../../utils/fetchers.utils' +import { generateFlake, signToken } from '../../utils/generate.utils' +import { extractRoomId, extractUserId, UNALLOCATED_PORTALS_KEYS } from '../../utils/helpers.utils' export type UserResolvable = User | string export default class User { - public id: string - public joinedAt: number - public username: string - - public roles: Role[] - - public name: string - public icon: string - - public room?: Room | string - - constructor(json?: IUser) { - if (!json) - return - - this.setup(json) - } - - public load = (id: string) => new Promise(async (resolve, reject) => { - try { - const doc = await StoredUser.findOne({ 'info.id': id }) - - if (!doc) - throw UserNotFound - - this.setup(doc) - - if (this.room) - await this.fetchRoom() - - resolve(this) - } catch (error) { - reject(error) - } - }) - - public findOrCreate = ( - accessToken: string, - refreshToken?: string, - scopes?: string[] - ) => new Promise(async (resolve, reject) => { - try { - const { - id, - email, - username: name, - avatar: avatarHash - } = await fetchUserProfile(accessToken), - existing = await StoredUser.findOne({ - $and: [ - { - 'security.type': 'discord' - }, - { - 'security.credentials.userId': id - } - ] - }), - avatar = constructAvatar({ - userId: id, - email, hash: - avatarHash - }) - - if (existing) { - this.setup(existing) - - await StoredUser.updateOne({ - 'info.id': this.id - }, { - $set: { - 'profile.name': name, - 'profile.icon': avatar, - - 'security.credentials.email': email, - 'security.credentials.scopes': scopes, - 'security.credentials.accessToken': accessToken, - 'security.credentials.refreshToken': refreshToken - } - }) - - resolve(this) - } else { - const json: IUser = { - info: { - id: generateFlake(), - joinedAt: Date.now(), - username: name, - roles: [] - }, - security: { - type: 'discord', - credentials: { - userId: id, - email, - - scopes, - accessToken, - refreshToken - } - }, - profile: { - name, - icon: avatar - } - } - - const stored = new StoredUser(json) - await stored.save() - - this.setup(json) - - resolve(this) - } - } catch (error) { - reject(error) - } - }) - - public refreshProfile = () => new Promise(async (resolve, reject) => { - try { - const { security: { credentials } } = await StoredUser.findOne({ 'info.id': this.id }), - { refreshToken } = (credentials as IDiscordCredentials), - { access_token, refresh_token } = await exchangeRefreshToken(refreshToken), - { id, username: name, email, avatar: avatarHash } = await fetchUserProfile(access_token), - icon = constructAvatar({ - userId: id, - email, - - hash: avatarHash - }) - - await StoredUser.updateOne({ - 'info.id': this.id - }, { - $set: { - 'security.credentials.accessToken': access_token, - 'security.credentials.refreshToken': refresh_token, - - 'profile.name': name, - 'profile.icon': icon - } - }) - - this.name = name - this.icon = icon - - if (this.room) { - const message = new Message(0, this, 'USER_UPDATE') - dispatcher.dispatch(message, await fetchRoomMemberIds(this.room), [this.id]) - } - - resolve(this) - } catch (error) { - reject(error) - } - }) - - public signToken = () => new Promise(async (resolve, reject) => { - try { - const { id } = this, - token = signToken({ id, type: 'user' }) - - resolve(token) - } catch (error) { - reject(error) - } - }) - - public fetchRoom = () => new Promise(async (resolve, reject) => { - if (!this.room) - return reject(UserNotInRoom) - - const roomId = extractRoomId(this.room) - - try { - const room = await new Room().load(roomId) - this.room = room - - resolve(this) - } catch (error) { - reject(error) - } - }) - - public fetchBan = () => new Promise(async (resolve, reject) => { - try { - const doc = await StoredBan.findOne({ - $and: [ - { - 'info.active': true - }, - { - 'data.userId': this.id - } - ] - }) - - if (!doc) - return resolve(null) - - const ban = new Ban(doc) - resolve(ban) - } catch (error) { - reject(error) - } - }) - - public joinRoom = (room: Room, isInitialMember: boolean = false) => new Promise(async (resolve, reject) => { - try { - if (!room.members) - await room.fetchMembers() - - if (room.members && room.members.length >= config.max_room_member_count) - throw TooManyMembers - - await StoredUser.updateOne({ - 'info.id': this.id - }, { - $set: { - 'info.room': room.id - } - }) - - /** + public id: string + public joinedAt: number + public username: string + + public roles: Role[] + + public name: string + public icon: string + + public room?: Room | string + + constructor(json?: IUser) { + if (!json) + return + + this.setup(json) + } + + public load = (id: string) => new Promise(async (resolve, reject) => { + try { + const doc = await StoredUser.findOne({ 'info.id': id }) + + if (!doc) + throw UserNotFound + + this.setup(doc) + + if (this.room) + await this.fetchRoom() + + resolve(this) + } catch (error) { + reject(error) + } + }) + + public findOrCreate = ( + accessToken: string, + refreshToken?: string, + scopes?: string[] + ) => new Promise(async (resolve, reject) => { + try { + const { + id, + email, + username: name, + avatar: avatarHash + } = await fetchUserProfile(accessToken), + existing = await StoredUser.findOne({ + $and: [ + { + 'security.type': 'discord' + }, + { + 'security.credentials.userId': id + } + ] + }), + avatar = constructAvatar({ + userId: id, + email, hash: + avatarHash + }) + + if (existing) { + this.setup(existing) + + await StoredUser.updateOne({ + 'info.id': this.id + }, { + $set: { + 'profile.name': name, + 'profile.icon': avatar, + + 'security.credentials.email': email, + 'security.credentials.scopes': scopes, + 'security.credentials.accessToken': accessToken, + 'security.credentials.refreshToken': refreshToken + } + }) + + resolve(this) + } else { + const json: IUser = { + info: { + id: generateFlake(), + joinedAt: Date.now(), + username: name, + roles: [] + }, + security: { + type: 'discord', + credentials: { + userId: id, + email, + + scopes, + accessToken, + refreshToken + } + }, + profile: { + name, + icon: avatar + } + } + + const stored = new StoredUser(json) + await stored.save() + + this.setup(json) + + resolve(this) + } + } catch (error) { + reject(error) + } + }) + + public refreshProfile = () => new Promise(async (resolve, reject) => { + try { + const { security: { credentials } } = await StoredUser.findOne({ 'info.id': this.id }), + { refreshToken } = (credentials as IDiscordCredentials), + { access_token, refresh_token } = await exchangeRefreshToken(refreshToken), + { id, username: name, email, avatar: avatarHash } = await fetchUserProfile(access_token), + icon = constructAvatar({ + userId: id, + email, + + hash: avatarHash + }) + + await StoredUser.updateOne({ + 'info.id': this.id + }, { + $set: { + 'security.credentials.accessToken': access_token, + 'security.credentials.refreshToken': refresh_token, + + 'profile.name': name, + 'profile.icon': icon + } + }) + + this.name = name + this.icon = icon + + if (this.room) { + const message = new Message(0, this, 'USER_UPDATE') + dispatcher.dispatch(message, await fetchRoomMemberIds(this.room), [this.id]) + } + + resolve(this) + } catch (error) { + reject(error) + } + }) + + public signToken = () => new Promise(async (resolve, reject) => { + try { + const { id } = this, + token = signToken({ id, type: 'user' }) + + resolve(token) + } catch (error) { + reject(error) + } + }) + + public fetchRoom = () => new Promise(async (resolve, reject) => { + if (!this.room) + return reject(UserNotInRoom) + + const roomId = extractRoomId(this.room) + + try { + const room = await new Room().load(roomId) + this.room = room + + resolve(this) + } catch (error) { + reject(error) + } + }) + + public fetchBan = () => new Promise(async (resolve, reject) => { + try { + const doc = await StoredBan.findOne({ + $and: [ + { + 'info.active': true + }, + { + 'data.userId': this.id + } + ] + }) + + if (!doc) + return resolve(null) + + const ban = new Ban(doc) + resolve(ban) + } catch (error) { + reject(error) + } + }) + + public joinRoom = (room: Room, isInitialMember: boolean = false) => new Promise(async (resolve, reject) => { + try { + if (!room.members) + await room.fetchMembers() + + if (room.members && room.members.length >= config.max_room_member_count) + throw TooManyMembers + + await StoredUser.updateOne({ + 'info.id': this.id + }, { + $set: { + 'info.room': room.id + } + }) + + /** * The local instance of the room has not been updated for this user, * so we will check if there is only 1 member in the room before the update */ - if ( - !isInitialMember && - room.members && - room.members.length === (config.min_member_portal_creation_count - 1) && - UNALLOCATED_PORTALS_KEYS.indexOf(room.portal.status) > -1 - ) - createPortal(room) + if ( + !isInitialMember && + room.members && + room.members.length === (config.min_member_portal_creation_count - 1) && + UNALLOCATED_PORTALS_KEYS.indexOf(room.portal.status) > -1 + ) + createPortal(room) - const message = new Message(0, { ...this, room: undefined }, 'USER_JOIN') - dispatcher.dispatch(message, await fetchRoomMemberIds(room)) + const message = new Message(0, { ...this, room: undefined }, 'USER_JOIN') + dispatcher.dispatch(message, await fetchRoomMemberIds(room)) - this.room = room + this.room = room - resolve(this) - } catch (error) { - reject(error) - } - }) + resolve(this) + } catch (error) { + reject(error) + } + }) - public leaveRoom = () => new Promise(async (resolve, reject) => { - try { - if (typeof this.room === 'string') - await this.fetchRoom() + public leaveRoom = () => new Promise(async (resolve, reject) => { + try { + if (typeof this.room === 'string') + await this.fetchRoom() - if (typeof this.room === 'string') - return + if (typeof this.room === 'string') + return - await this.room.fetchMembers() + await this.room.fetchMembers() - /** + /** * In this instance, the WebSocket message is sent before the DB * update. This is because the client needs to recieve the message * that the user has left the room, and any state changes on the * client side to handle the room being left needs to be ran */ - const memberIndex = this.room.members.map(({ id }) => id).indexOf(this.id) - this.room.members.splice(memberIndex, 1) + const memberIndex = this.room.members.map(({ id }) => id).indexOf(this.id) + this.room.members.splice(memberIndex, 1) - if (this.room.members.length === 0) - await this.room.destroy() - else { - const leavingUserIsOwner = this.id === extractUserId(this.room.owner) + if (this.room.members.length === 0) + await this.room.destroy() + else { + const leavingUserIsOwner = this.id === extractUserId(this.room.owner) - if (leavingUserIsOwner) - this.room.transferOwnership(this.room.members[0]) + if (leavingUserIsOwner) + this.room.transferOwnership(this.room.members[0]) - const message = new Message(0, { u: this.id }, 'USER_LEAVE') - dispatcher.dispatch(message, await fetchRoomMemberIds(this.room)) - } + const message = new Message(0, { u: this.id }, 'USER_LEAVE') + dispatcher.dispatch(message, await fetchRoomMemberIds(this.room)) + } - await StoredUser.updateOne({ - 'info.id': this.id - }, { - $unset: { - 'info.room': '' - } - }) + await StoredUser.updateOne({ + 'info.id': this.id + }, { + $unset: { + 'info.room': '' + } + }) - client.hset('undelivered_events', this.id, JSON.stringify([])) + client.hset('undelivered_events', this.id, JSON.stringify([])) - delete this.room + delete this.room - resolve(this) - } catch (error) { - reject(error) - } - }) + resolve(this) + } catch (error) { + reject(error) + } + }) - public destroy = () => new Promise(async (resolve, reject) => { - try { - if (this.room) - await this.leaveRoom() + public destroy = () => new Promise(async (resolve, reject) => { + try { + if (this.room) + await this.leaveRoom() - await StoredUser.deleteOne({ - 'info.id': this.id - }) + await StoredUser.deleteOne({ + 'info.id': this.id + }) - await StoredMessage.deleteMany({ - 'info.author': this.id - }) + await StoredMessage.deleteMany({ + 'info.author': this.id + }) - resolve() - } catch (error) { - reject(error) - } - }) + resolve() + } catch (error) { + reject(error) + } + }) - public setup = (json: IUser) => { - this.id = json.info.id - this.joinedAt = json.info.joinedAt - this.username = json.info.username + public setup = (json: IUser) => { + this.id = json.info.id + this.joinedAt = json.info.joinedAt + this.username = json.info.username - this.roles = json.info.roles + this.roles = json.info.roles - this.name = json.profile.name - this.icon = json.profile.icon + this.name = json.profile.name + this.icon = json.profile.icon - if (!this.room) - this.room = json.info.room - } + if (!this.room) + this.room = json.info.room + } - public prepare = () => ({ ...this } as User) + public prepare = () => ({ ...this } as User) } diff --git a/src/schemas/ban.schema.ts b/src/schemas/ban.schema.ts index 9e290c4..4a77dca 100644 --- a/src/schemas/ban.schema.ts +++ b/src/schemas/ban.schema.ts @@ -3,19 +3,19 @@ import { model, Schema } from 'mongoose' import { IStoredBan } from '../models/user/ban/defs' const BanSchema = new Schema({ - info: { - id: String, - createdAt: Number, - createdBy: String, + info: { + id: String, + createdAt: Number, + createdBy: String, - active: Boolean - }, - data: { - userId: String, - reason: String - } + active: Boolean + }, + data: { + userId: String, + reason: String + } }, { - typeKey: '$type' + typeKey: '$type' }) const StoredBan = model('Ban', BanSchema) diff --git a/src/schemas/invite.schema.ts b/src/schemas/invite.schema.ts index 553d488..29bc30b 100644 --- a/src/schemas/invite.schema.ts +++ b/src/schemas/invite.schema.ts @@ -3,28 +3,28 @@ import { model, Schema } from 'mongoose' import { IStoredInvite } from '../models/invite/defs' const InviteSchema = new Schema({ - info: { - id: String, - createdAt: Number, - createdBy: String, + info: { + id: String, + createdAt: Number, + createdBy: String, - active: Boolean, - system: Boolean, + active: Boolean, + system: Boolean, - targetId: String, - targetType: String - }, - data: { - code: String, - uses: [String], + targetId: String, + targetType: String + }, + data: { + code: String, + uses: [String], - options: { - maxUses: Number, - unlimitedUses: Boolean - } - } + options: { + maxUses: Number, + unlimitedUses: Boolean + } + } }, { - typeKey: '$type' + typeKey: '$type' }) const StoredInvite = model('Invite', InviteSchema) diff --git a/src/schemas/message.schema.ts b/src/schemas/message.schema.ts index f2c5239..0d4ee2e 100644 --- a/src/schemas/message.schema.ts +++ b/src/schemas/message.schema.ts @@ -3,17 +3,17 @@ import { model, Schema } from 'mongoose' import { IStoredMessage } from '../models/message/defs' const MessageSchema = new Schema({ - info: { - id: String, - createdAt: Number, - author: String, - room: String - }, - data: { - content: String - } + info: { + id: String, + createdAt: Number, + author: String, + room: String + }, + data: { + content: String + } }, { - typeKey: '$type' + typeKey: '$type' }) const StoredMessage = model('Message', MessageSchema) diff --git a/src/schemas/report.schema.ts b/src/schemas/report.schema.ts index 3deb678..b83efd0 100644 --- a/src/schemas/report.schema.ts +++ b/src/schemas/report.schema.ts @@ -3,17 +3,17 @@ import { model, Schema } from 'mongoose' import { IStoredReport } from '../models/report/defs' const ReportSchema = new Schema({ - info: { - id: String, - createdAt: Number, - createdBy: String - }, - data: { - messageId: String, - roomId: String - } + info: { + id: String, + createdAt: Number, + createdBy: String + }, + data: { + messageId: String, + roomId: String + } }, { - typeKey: '$type' + typeKey: '$type' }) const StoredReport = model('Report', ReportSchema) diff --git a/src/schemas/room.schema.ts b/src/schemas/room.schema.ts index 0d8f6b4..8e27521 100644 --- a/src/schemas/room.schema.ts +++ b/src/schemas/room.schema.ts @@ -3,30 +3,30 @@ import { model, Schema } from 'mongoose' import { IStoredRoom } from '../models/room/defs' const RoomSchema = new Schema({ - info: { - id: String, - createdAt: Number, - endedAt: Number, + info: { + id: String, + createdAt: Number, + endedAt: Number, - type: String, - portal: { - id: String, - janusId: Number, + type: String, + portal: { + id: String, + janusId: Number, janusIp: String, - status: String, - lastUpdatedAt: String - }, + status: String, + lastUpdatedAt: String + }, - owner: String, - invite: String, - controller: String - }, - profile: { - name: String - } + owner: String, + invite: String, + controller: String + }, + profile: { + name: String + } }, { - typeKey: '$type' + typeKey: '$type' }) const StoredRoom = model('Room', RoomSchema) diff --git a/src/schemas/user.schema.ts b/src/schemas/user.schema.ts index 43e91c1..46f687d 100644 --- a/src/schemas/user.schema.ts +++ b/src/schemas/user.schema.ts @@ -3,32 +3,32 @@ import { model, Schema } from 'mongoose' import { IStoredUser } from '../models/user/defs' const UserSchema = new Schema({ - info: { - id: String, - joinedAt: Number, - username: String, - roles: [String], + info: { + id: String, + joinedAt: Number, + username: String, + roles: [String], - room: String - }, - security: { - type: String, - credentials: { - userId: String, - scopes: [String], - accessToken: String, - refreshToken: String, + room: String + }, + security: { + type: String, + credentials: { + userId: String, + scopes: [String], + accessToken: String, + refreshToken: String, - email: String, - password: String - } - }, - profile: { - name: String, - icon: String - } + email: String, + password: String + } + }, + profile: { + name: String, + icon: String + } }, { - typeKey: '$type' + typeKey: '$type' }) const StoredUser = model('User', UserSchema) diff --git a/src/server/index.ts b/src/server/index.ts index 3a7bb3c..c28cf2a 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -3,16 +3,16 @@ dotenv.config() import { createServer } from 'http' -import express, { json } from 'express' import Mesa from '@cryb/mesa' +import express, { json } from 'express' import { connect } from 'mongoose' import cors from 'cors' import helmet from 'helmet' import morgan from 'morgan' +import mesa from './mesa' import routes from './routes' -import websocket from './websocket' import config from '../config/defaults' import passport from '../config/passport.config' @@ -20,38 +20,20 @@ import { getOptions } from '../config/redis.config' import { verify_env } from '../utils/verifications.utils' verify_env( - 'JWT_KEY', - 'PORTALS_API_URL', - 'PORTALS_API_KEY', - 'APERTURE_WS_URL', - 'APERTURE_WS_KEY', - 'MONGO_URI', - 'DISCORD_CLIENT_ID', - 'DISCORD_CLIENT_SECRET', - 'DISCORD_CALLBACK_URL', - 'DISCORD_OAUTH_ORIGINS' + 'JWT_KEY', + 'PORTALS_API_URL', + 'PORTALS_API_KEY', + 'APERTURE_WS_URL', + 'APERTURE_WS_KEY', + 'MONGO_URI', + 'DISCORD_CLIENT_ID', + 'DISCORD_CLIENT_SECRET', + 'DISCORD_CALLBACK_URL', + 'DISCORD_OAUTH_ORIGINS' ) const app = express() const server = createServer(app) -const mesa = new Mesa({ - server, - namespace: config.mesa_namespace, - redis: getOptions(), - - heartbeat: { - enabled: true, - interval: 10000, - maxAttempts: 3 - }, - reconnect: { - enabled: true, - interval: 5000 - }, - authentication: { - storeConnectedUsers: true - } -}) connect(process.env.MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true }) @@ -63,6 +45,6 @@ app.use(morgan('dev')) app.use(passport.initialize()) routes(app) -websocket(mesa) +mesa(app) export default server diff --git a/src/server/mesa/index.ts b/src/server/mesa/index.ts new file mode 100644 index 0000000..1baeac8 --- /dev/null +++ b/src/server/mesa/index.ts @@ -0,0 +1,151 @@ +import http from 'http' +import Mesa, { Message } from '@cryb/mesa' + +import axios from 'axios' + +import config from '../../config/defaults' +import redis, { createPubSubClient, getOptions } from '../../config/redis.config' +import log from '../../utils/log.utils' + +import Room from '../../models/room' +import User from '../../models/user' + +import { fetchRoomMemberIds } from '../../utils/fetchers.utils' +import { verifyToken } from '../../utils/generate.utils' +import { extractRoomId, extractUserId, UNALLOCATED_PORTALS_KEYS } from '../../utils/helpers.utils' +import { validateControllerEvent } from '../../utils/validate.utils' + +const CONTROLLER_EVENT_TYPES = ['KEY_DOWN', 'KEY_UP', 'PASTE_TEXT', 'MOUSE_MOVE', 'MOUSE_SCROLL', 'MOUSE_DOWN', 'MOUSE_UP'], + pub = createPubSubClient() + +export default (server: http.Server) => { + const mesa = new Mesa({ + server, + namespace: config.mesa_namespace, + redis: getOptions(), + + // heartbeat: { + // enabled: true, + // interval: 10000, + // maxAttempts: 3 + // }, + reconnect: { + enabled: true, + interval: 5000 + }, + authentication: { + storeConnectedUsers: true + } + }) + + mesa.on('connection', client => { + log('Connection', 'ws', 'CYAN') + + client.authenticate(async ({ token }, done) => { + let id: string + let user: User + + try { + if (process.env.AUTH_BASE_URL) { + const { data } = await axios.post(process.env.AUTH_BASE_URL, { token }) + + user = new User(data) + id = user.id + } else { + id = (verifyToken(token) as { id: string }).id + user = await new User().load(id) + } + + mesa.send( + new Message(0, { u: id, presence: 'online'}, 'PRESENCE_UPDATE'), + await fetchRoomMemberIds(user.room), + [id] + ) + + done(null, { id, user }) + } catch (error) { + console.error(error) + + done(error, null) + } + }) + + client.on('message', async message => { + const { opcode, data, type } = message + + if (type === 'TYPING_UPDATE') { + mesa.send( + new Message(0, { u: client.id, typing: !!data.typing }, 'TYPING_UPDATE'), + await fetchRoomMemberIds(client.user.room), + [client.id] + ) + } else if (CONTROLLER_EVENT_TYPES.indexOf(type) > -1) { + if (!validateControllerEvent(data, type)) return + + if (!client.user) + return // Check if the socket is actually authenticated + + if (!client.user.room) + return // Check if the user is in a room + + if (typeof client.user.room === 'string') + return // Check if room is unreadable + + if (!client.user.room.portal.id) + client.updateUser({ id: client.id, user: await new User().load(client.user.id) }) // Workaround for controller bug + + if (await redis.hget('controller', extractRoomId(client.user.room)) !== extractUserId(client.user)) + return // Check if the user has the controller + + pub.publish('portals', JSON.stringify({ + op: opcode, + d: { + t: client.user.room.portal.id, + ...data + }, + t: type + })) + } + }) + + client.on('disconnect', async (code, reason) => { + log(`Disconnection ${client.authenticated ? `with id ${client.id}` : ''} code: ${code}, reason: ${reason}`, 'ws', 'CYAN') + + if (client.authenticated && client.user.room) { + // if(await redis.hget('controller', roomId) === client.id) + + // We can use optimisation here in order to speed up the controller release cycle + + const message = new Message(0, { u: client.id, presence: 'offline' }, 'PRESENCE_UPDATE') + mesa.send(message, await fetchRoomMemberIds(client.user.room)) + + if (typeof client.user.room === 'string') { + try { + await client.user.fetchRoom() + } catch (error) { + return + } // Room doesn't exists + } + + if (typeof client.user.room === 'string') + return + + const { room } = client.user as { room: Room } + + if (extractUserId(room.controller) === client.user.id) + room.releaseControl(client.user) + + if (config.destroy_portal_when_empty) { + setTimeout(async () => + (await room.load(room.id)).fetchOnlineMemberIds().then(({ portal, online }) => { + if (online.length > 0) return + if (UNALLOCATED_PORTALS_KEYS.indexOf(portal.status) > -1) return + + room.destroyPortal() + }).catch(console.error), config.empty_room_portal_destroy * 1000 + ) + } + } + }) + }) +} diff --git a/src/server/middleware/authenticate.internal.middleware.ts b/src/server/middleware/authenticate.internal.middleware.ts index 41278f2..cf87c2d 100644 --- a/src/server/middleware/authenticate.internal.middleware.ts +++ b/src/server/middleware/authenticate.internal.middleware.ts @@ -1,22 +1,22 @@ -import { Request, Response, NextFunction } from 'express' +import { NextFunction, Request, Response } from 'express' import { verify } from 'jsonwebtoken' export default async (req: Request, res: Response, next: NextFunction) => { - const { authorization } = req.headers + const { authorization } = req.headers - if (!authorization) - return res.sendStatus(401) + if (!authorization) + return res.sendStatus(401) - const token = authorization.split(' ')[1] + const token = authorization.split(' ')[1] - if (!token) - return res.sendStatus(401) + if (!token) + return res.sendStatus(401) - const payload = verify(token, process.env.PORTALS_API_KEY) + const payload = verify(token, process.env.PORTALS_API_KEY) - if (!payload) - return res.sendStatus(401) + if (!payload) + return res.sendStatus(401) - next() + next() } diff --git a/src/server/routes.ts b/src/server/routes.ts index 52b9067..8dd3f92 100644 --- a/src/server/routes.ts +++ b/src/server/routes.ts @@ -1,14 +1,14 @@ import { Application } from 'express' export default (app: Application) => { - app.use('/auth', require('../controllers/auth.controller').default) + app.use('/auth', require('../controllers/auth.controller').default) - // app.use('/admin', require('../controllers/admin.controller').default) - app.use('/internal', require('../controllers/internal.controller').default) + // app.use('/admin', require('../controllers/admin.controller').default) + app.use('/internal', require('../controllers/internal.controller').default) - app.use('/user', require('../controllers/user.controller').default) - app.use('/room', require('../controllers/room.controller').default) - app.use('/invite', require('../controllers/invite.controller').default) + app.use('/user', require('../controllers/user.controller').default) + app.use('/room', require('../controllers/room.controller').default) + app.use('/invite', require('../controllers/invite.controller').default) - app.use('/internal', require('../controllers/internal.controller').default) + app.use('/internal', require('../controllers/internal.controller').default) } diff --git a/src/server/websocket/index.ts b/src/server/websocket/index.ts deleted file mode 100644 index 47fec05..0000000 --- a/src/server/websocket/index.ts +++ /dev/null @@ -1,131 +0,0 @@ -import Mesa, { Message } from '@cryb/mesa' - -import axios from 'axios' - -import config from '../../config/defaults' -import redis, { createPubSubClient } from '../../config/redis.config' -import log from '../../utils/log.utils' - -import Room from '../../models/room' -import User from '../../models/user' - -import { fetchRoomMemberIds } from '../../utils/fetchers.utils' -import { verifyToken } from '../../utils/generate.utils' -import { extractRoomId, extractUserId, UNALLOCATED_PORTALS_KEYS } from '../../utils/helpers.utils' -import { validateControllerEvent } from '../../utils/validate.utils' - -const CONTROLLER_EVENT_TYPES = ['KEY_DOWN', 'KEY_UP', 'PASTE_TEXT', 'MOUSE_MOVE', 'MOUSE_SCROLL', 'MOUSE_DOWN', 'MOUSE_UP'], - pub = createPubSubClient() - -export default (mesa: Mesa) => { - mesa.on('connection', client => { - log('Connection', 'ws', 'CYAN') - - client.authenticate(async ({ token }, done) => { - let id: string - let user: User - - try { - if (process.env.AUTH_BASE_URL) { - const { data } = await axios.post(process.env.AUTH_BASE_URL, { token }) - - user = new User(data) - id = user.id - } else { - id = (verifyToken(token) as { id: string }).id - user = await new User().load(id) - } - - mesa.send( - new Message(0, { u: id, presence: 'online'}, 'PRESENCE_UPDATE'), - await fetchRoomMemberIds(user.room), - [id] - ) - - done(null, { id, user }) - } catch (error) { - console.error(error) - - done(error, null) - } - }) - - client.on('message', async message => { - const { opcode, data, type } = message - - if (type === 'TYPING_UPDATE') { - mesa.send( - new Message(0, { u: client.id, typing: !!data.typing }, 'TYPING_UPDATE'), - await fetchRoomMemberIds(client.user.room), - [client.id] - ) - } else if (CONTROLLER_EVENT_TYPES.indexOf(type) > -1) { - if (!validateControllerEvent(data, type)) return - - if (!client.user) - return // Check if the socket is actually authenticated - - if (!client.user.room) - return // Check if the user is in a room - - if (typeof client.user.room === 'string') - return // Check if room is unreadable - - if (!client.user.room.portal.id) - client.updateUser({ id: client.id, user: await new User().load(client.user.id) }) // Workaround for controller bug - - if (await redis.hget('controller', extractRoomId(client.user.room)) !== extractUserId(client.user)) - return // Check if the user has the controller - - pub.publish('portals', JSON.stringify({ - op: opcode, - d: { - t: client.user.room.portal.id, - ...data - }, - t: type - })) - } - }) - - client.on('disconnect', async (code, reason) => { - log(`Disconnection ${client.authenticated ? `with id ${client.id}` : ''} code: ${code}, reason: ${reason}`, 'ws', 'CYAN') - - if (client.authenticated && client.user.room) { - // if(await redis.hget('controller', roomId) === client.id) - - // We can use optimisation here in order to speed up the controller release cycle - - const message = new Message(0, { u: client.id, presence: 'offline' }, 'PRESENCE_UPDATE') - mesa.send(message, await fetchRoomMemberIds(client.user.room)) - - if (typeof client.user.room === 'string') { - try { - await client.user.fetchRoom() - } catch (error) { - return - } // Room doesn't exists - } - - if (typeof client.user.room === 'string') - return - - const { room } = client.user as { room: Room } - - if (extractUserId(room.controller) === client.user.id) - room.releaseControl(client.user) - - if (config.destroy_portal_when_empty) { - setTimeout(async () => - (await room.load(room.id)).fetchOnlineMemberIds().then(({ portal, online }) => { - if (online.length > 0) return - if (UNALLOCATED_PORTALS_KEYS.indexOf(portal.status) > -1) return - - room.destroyPortal() - }).catch(console.error), config.empty_room_portal_destroy * 1000 - ) - } - } - }) - }) -} diff --git a/src/services/oauth2/discord.service.ts b/src/services/oauth2/discord.service.ts index f2c238c..04d0a94 100644 --- a/src/services/oauth2/discord.service.ts +++ b/src/services/oauth2/discord.service.ts @@ -5,91 +5,91 @@ import qs from 'qs' import { Profile } from 'passport-discord' interface IDiscordAuthentication { - access_token: string - refresh_token: string - scope: string + access_token: string + refresh_token: string + scope: string } export const DISCORD_OAUTH_BASE_URL = 'https://discordapp.com/api/oauth2/authorize' export const DISCORD_OAUTH_SCOPES = ['identify', 'email'] interface IAvatarConstruction { - userId: string - email: string + userId: string + email: string - hash?: string + hash?: string } export const constructAvatar = (data: IAvatarConstruction) => { - if (!data.hash) - return `https://www.gravatar.com/avatar/${md5(data.email || '')}?d=retro&s=128` + if (!data.hash) + return `https://www.gravatar.com/avatar/${md5(data.email || '')}?d=retro&s=128` - const { userId, hash } = data - let url = `https://cdn.discordapp.com/avatars/${userId}/` + const { userId, hash } = data + let url = `https://cdn.discordapp.com/avatars/${userId}/` - if (data.hash.substr(0, 2) === 'a_') - url += `${hash}.gif` - else - url += `${hash}.png` + if (data.hash.substr(0, 2) === 'a_') + url += `${hash}.gif` + else + url += `${hash}.png` - return url + return url } export const fetchUserProfile = (access_token: string) => new Promise(async (resolve, reject) => { - try { - const { data } = await axios({ - method: 'GET', - url: 'https://discordapp.com/api/v6/users/@me', - headers: { - authorization: `Bearer ${access_token}` - } - }) - - resolve(data) - } catch (error) { - reject(error) - } + try { + const { data } = await axios({ + method: 'GET', + url: 'https://discordapp.com/api/v6/users/@me', + headers: { + authorization: `Bearer ${access_token}` + } + }) + + resolve(data) + } catch (error) { + reject(error) + } }) const sendOauthRequest = (opts: object) => axios({ - method: 'POST', - url: 'https://discordapp.com/api/oauth2/token', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8' - }, - data: qs.stringify({ - ...opts, - redirect_uri: process.env.DISCORD_CALLBACK_URL, - client_id: process.env.DISCORD_CLIENT_ID, - client_secret: process.env.DISCORD_CLIENT_SECRET, - scope: DISCORD_OAUTH_SCOPES.join(' ') - }) + method: 'POST', + url: 'https://discordapp.com/api/oauth2/token', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8' + }, + data: qs.stringify({ + ...opts, + redirect_uri: process.env.DISCORD_CALLBACK_URL, + client_id: process.env.DISCORD_CLIENT_ID, + client_secret: process.env.DISCORD_CLIENT_SECRET, + scope: DISCORD_OAUTH_SCOPES.join(' ') + }) }) export const exchangeRefreshToken = ( - refresh_token: string + refresh_token: string ) => new Promise(async (resolve, reject) => { - try { - const { data } = await sendOauthRequest({ - refresh_token, - grant_type: 'refresh_token' - }) - - resolve(data) - } catch (error) { - reject(error.response ? error.response.data : error) - } + try { + const { data } = await sendOauthRequest({ + refresh_token, + grant_type: 'refresh_token' + }) + + resolve(data) + } catch (error) { + reject(error.response ? error.response.data : error) + } }) export default async (code: string) => new Promise(async (resolve, reject) => { - try { - const { data } = await sendOauthRequest({ - code, - grant_type: 'authorization_code' - }) - - resolve(data) - } catch (error) { - reject(error.response ? error.response.data : error) - } + try { + const { data } = await sendOauthRequest({ + code, + grant_type: 'authorization_code' + }) + + resolve(data) + } catch (error) { + reject(error.response ? error.response.data : error) + } }) diff --git a/src/utils/errors.utils.ts b/src/utils/errors.utils.ts index 6f6cc4d..63ab792 100644 --- a/src/utils/errors.utils.ts +++ b/src/utils/errors.utils.ts @@ -1,229 +1,229 @@ import { Response } from 'express' interface IAPIResponse { - response: string - error: { - title: string - description: string - } - status: number + response: string + error: { + title: string + description: string + } + status: number } export const UserNoAuth: IAPIResponse = { - response: 'USER_NO_AUTH', - error: { - title: 'User No Auth', - description: 'You\'re not authenticated. Please log out and try again.' - }, - status: 401 + response: 'USER_NO_AUTH', + error: { + title: 'User No Auth', + description: 'You\'re not authenticated. Please log out and try again.' + }, + status: 401 } export const UserBanned: IAPIResponse = { - response: 'USER_BANNED', - error: { - title: 'User Banned', - description: 'You\'re banned from this instance.' - }, - status: 401 + response: 'USER_BANNED', + error: { + title: 'User Banned', + description: 'You\'re banned from this instance.' + }, + status: 401 } export const UserNotAuthorized: IAPIResponse = { - response: 'USER_NOT_AUTHORIZED', - error: { - title: 'User Not Authorized', - description: 'You\'re not allowed to create rooms.' - }, - status: 401 + response: 'USER_NOT_AUTHORIZED', + error: { + title: 'User Not Authorized', + description: 'You\'re not allowed to create rooms.' + }, + status: 401 } export const UserNotInRoom: IAPIResponse = { - response: 'USER_NOT_IN_ROOM', - error: { - title: 'User Not in Room', - description: 'You\'re currently not in a room. Join or create one and try again.' - }, - status: 410 + response: 'USER_NOT_IN_ROOM', + error: { + title: 'User Not in Room', + description: 'You\'re currently not in a room. Join or create one and try again.' + }, + status: 410 } export const UserAlreadyInRoom: IAPIResponse = { - response: 'USER_ALREADY_IN_ROOM', - error: { - title: 'User Already in Room', - description: 'You\'re already in a room! Leave this one, and then try again.' - }, - status: 409 + response: 'USER_ALREADY_IN_ROOM', + error: { + title: 'User Already in Room', + description: 'You\'re already in a room! Leave this one, and then try again.' + }, + status: 409 } export const InviteNotFound: IAPIResponse = { - response: 'INVITE_NOT_FOUND', - error: { - title: 'Invite Not Found', - description: 'This invite wasn\'t found. Make sure it hasn\'t expired, and that you typed it in correctly.' - }, - status: 404 + response: 'INVITE_NOT_FOUND', + error: { + title: 'Invite Not Found', + description: 'This invite wasn\'t found. Make sure it hasn\'t expired, and that you typed it in correctly.' + }, + status: 404 } export const RoomNotFound: IAPIResponse = { - response: 'ROOM_NOT_FOUND', - error: { - title: 'Room Not Found', - description: 'This room was not found. Refresh the page and try again.' - }, - status: 404 + response: 'ROOM_NOT_FOUND', + error: { + title: 'Room Not Found', + description: 'This room was not found. Refresh the page and try again.' + }, + status: 404 } export const RoomNameTooLong: IAPIResponse = { - response: 'ROOM_NAME_TOO_LONG', - error: { - title: 'Room Name Too Long', - description: 'This room name is too short. Please specify a name up to 30 characters.' - }, - status: 413 + response: 'ROOM_NAME_TOO_LONG', + error: { + title: 'Room Name Too Long', + description: 'This room name is too short. Please specify a name up to 30 characters.' + }, + status: 413 } export const RoomNameTooShort: IAPIResponse = { - response: 'ROOM_NAME_TOO_SHORT', - error: { - title: 'Room Name Too Long', - description: 'This room name is too short. Please specify a name up to 30 characters.' - }, - status: 413 + response: 'ROOM_NAME_TOO_SHORT', + error: { + title: 'Room Name Too Long', + description: 'This room name is too short. Please specify a name up to 30 characters.' + }, + status: 413 } export const UserNotFound: IAPIResponse = { - response: 'USER_NOT_FOUND', - error: { - title: 'User Not Found', - description: 'This user was not found.' - }, - status: 404 + response: 'USER_NOT_FOUND', + error: { + title: 'User Not Found', + description: 'This user was not found.' + }, + status: 404 } export const ControllerIsNotAvailable: IAPIResponse = { - response: 'CONTROLLER_IS_NOT_AVAILABLE', - error: { - title: 'Controller Is Not Available', - description: 'The controller isn\'t currently available, ask the member who has the control for it.' - }, - status: 406 + response: 'CONTROLLER_IS_NOT_AVAILABLE', + error: { + title: 'Controller Is Not Available', + description: 'The controller isn\'t currently available, ask the member who has the control for it.' + }, + status: 406 } export const UserDoesNotHaveRemote: IAPIResponse = { - response: 'USER_DOES_NOT_HAVE_REMOTE', - error: { - title: 'User Does Not Have Remote', - description: 'This user doesn\'t currently have the remote. Refresh the page and try again.' - }, - status: 417 + response: 'USER_DOES_NOT_HAVE_REMOTE', + error: { + title: 'User Does Not Have Remote', + description: 'This user doesn\'t currently have the remote. Refresh the page and try again.' + }, + status: 417 } export const UserIsNotPermitted: IAPIResponse = { - response: 'USER_IS_NOT_PERMITTED', - error: { - title: 'User Is Not Permitted', - description: 'You\'re not permitted to perform this action. Refresh the page and try again.' - }, - status: 401 + response: 'USER_IS_NOT_PERMITTED', + error: { + title: 'User Is Not Permitted', + description: 'You\'re not permitted to perform this action. Refresh the page and try again.' + }, + status: 401 } export const MessageTooLong: IAPIResponse = { - response: 'MESSAGE_TOO_LONG', - error: { - title: 'Message Too Long', - description: 'The message you\'re trying to send is too long. Short it to under 255 characters before retrying.' - }, - status: 413 + response: 'MESSAGE_TOO_LONG', + error: { + title: 'Message Too Long', + description: 'The message you\'re trying to send is too long. Short it to under 255 characters before retrying.' + }, + status: 413 } export const MessageTooShort: IAPIResponse = { - response: 'MESSAGE_TOO_SHORT', - error: { - title: 'Message Too Short', - description: 'The message you\'re trying to send is too short. Write up to 255 characters for your message.' - }, - status: 413 + response: 'MESSAGE_TOO_SHORT', + error: { + title: 'Message Too Short', + description: 'The message you\'re trying to send is too short. Write up to 255 characters for your message.' + }, + status: 413 } export const MessageNotFound: IAPIResponse = { - response: 'MESSAGE_NOT_FOUND', - error: { - title: 'Message Not Found', - description: 'A message with this ID was not found.' - }, - status: 404 + response: 'MESSAGE_NOT_FOUND', + error: { + title: 'Message Not Found', + description: 'A message with this ID was not found.' + }, + status: 404 } export const ReportNotFound: IAPIResponse = { - response: 'REPORT_NOT_FOUND', - error: { - title: 'Report Not Found', - description: 'A report with this ID was not found.' - }, - status: 404 + response: 'REPORT_NOT_FOUND', + error: { + title: 'Report Not Found', + description: 'A report with this ID was not found.' + }, + status: 404 } export const BanNotFound: IAPIResponse = { - response: 'BAN_NOT_FOUND', - error: { - title: 'Ban Not Found', - description: 'A ban with this ID was not found.' - }, - status: 404 + response: 'BAN_NOT_FOUND', + error: { + title: 'Ban Not Found', + description: 'A ban with this ID was not found.' + }, + status: 404 } export const BanAlreadyExists: IAPIResponse = { - response: 'BAN_ALREADY_EXISTS', - error: { - title: 'Ban Already Exists', - description: 'A ban for this user already exists, remove it and then try again.' - }, - status: 409 + response: 'BAN_ALREADY_EXISTS', + error: { + title: 'Ban Already Exists', + description: 'A ban for this user already exists, remove it and then try again.' + }, + status: 409 } export const TooManyMembers: IAPIResponse = { - response: 'TOO_MANY_MEMBERS', - error: { - title: 'Too Many Members', - description: 'There are too many members in this room.' - }, - status: 409 + response: 'TOO_MANY_MEMBERS', + error: { + title: 'Too Many Members', + description: 'There are too many members in this room.' + }, + status: 409 } export const TargetTypeNotFound: IAPIResponse = { - response: 'TARGET_TYPE_NOT_FOUND', - error: { - title: 'Target Type Not Found', - description: 'We can\'t resolve this target type, please try again later.' - }, - status: 0 + response: 'TARGET_TYPE_NOT_FOUND', + error: { + title: 'Target Type Not Found', + description: 'We can\'t resolve this target type, please try again later.' + }, + status: 0 } export const NoPortalFound: IAPIResponse = { - response: 'NO_PORTAL_FOUND', - error: { - title: 'No Portal Found', - description: 'A portal with this ID was not found.' - }, - status: 404 + response: 'NO_PORTAL_FOUND', + error: { + title: 'No Portal Found', + description: 'A portal with this ID was not found.' + }, + status: 404 } export const PortalNotOpen: IAPIResponse = { - response: 'PORTAL_NOT_OPEN', - error: { - title: 'Portal Not Open', - description: 'This portal is not currently open, please try again later.' - }, - status: 409 + response: 'PORTAL_NOT_OPEN', + error: { + title: 'Portal Not Open', + description: 'This portal is not currently open, please try again later.' + }, + status: 409 } export const handleError = (error: any, res: Response) => { - if (process.env.NODE_ENV === 'development') - console.error(error) - - if (error) - if (error.response && error.error && error.status) - return res.status(error.status).send(error) - else if (error.status) - return res.sendStatus(error.status) - res.sendStatus(500) + if (process.env.NODE_ENV === 'development') + console.error(error) + + if (error) + if (error.response && error.error && error.status) + return res.status(error.status).send(error) + else if (error.status) + return res.sendStatus(error.status) + res.sendStatus(500) } diff --git a/src/utils/fetchers.utils.ts b/src/utils/fetchers.utils.ts index 80dc382..cc15486 100644 --- a/src/utils/fetchers.utils.ts +++ b/src/utils/fetchers.utils.ts @@ -3,4 +3,4 @@ import { extractRoomId } from './helpers.utils' import StoredUser from '../schemas/user.schema' -export const fetchRoomMemberIds = (room: RoomResolvable) => StoredUser.distinct('info.id', { 'info.room': extractRoomId(room) }) \ No newline at end of file +export const fetchRoomMemberIds = (room: RoomResolvable) => StoredUser.distinct('info.id', { 'info.room': extractRoomId(room) }) diff --git a/src/utils/helpers.utils.ts b/src/utils/helpers.utils.ts index 2efb57f..ec4f8fd 100644 --- a/src/utils/helpers.utils.ts +++ b/src/utils/helpers.utils.ts @@ -10,72 +10,72 @@ export const UNALLOCATED_PORTALS_KEYS: PortalAllocationStatus[] = ['waiting', 'r // Extract User id export const extractUserId = (user: UserResolvable) => ( - user ? (typeof user === 'string' ? user : user.id) : null - // Extract Target id + user ? (typeof user === 'string' ? user : user.id) : null + // Extract Target id ) export const extractTargetId = (target: TargetResolvable) => ( - target ? (typeof target === 'string' ? target : target.id) : null - // Extract Room id + target ? (typeof target === 'string' ? target : target.id) : null + // Extract Room id ) export const extractRoomId = (room: RoomResolvable) => ( - room ? (typeof room === 'string' ? room : room.id) : null - // Extract Message id + room ? (typeof room === 'string' ? room : room.id) : null + // Extract Message id ) export const extractMessageId = (message: MessageResolvable) => ( - message ? (typeof message === 'string' ? message : message.id) : null - // Extract Invite id + message ? (typeof message === 'string' ? message : message.id) : null + // Extract Invite id ) export const extractInviteId = (invite: InviteResolvable) => ( - invite ? (typeof invite === 'string' ? invite : invite.id) : null + invite ? (typeof invite === 'string' ? invite : invite.id) : null ) export class GroupedMessage { - public id: string - public createdAt: number + public id: string + public createdAt: number - public author: UserResolvable + public author: UserResolvable - public messages: Message[] - public messageIds: string[] + public messages: Message[] + public messageIds: string[] - constructor(message: Message, author: UserResolvable) { - this.id = message.id - this.createdAt = message.createdAt + constructor(message: Message, author: UserResolvable) { + this.id = message.id + this.createdAt = message.createdAt - this.author = author + this.author = author - this.messages = [message] - this.messageIds = [message.id] - } + this.messages = [message] + this.messageIds = [message.id] + } - public push(message: Message) { - this.messages.push(message) - this.messageIds.push(message.id) - } + public push(message: Message) { + this.messages.push(message) + this.messageIds.push(message.id) + } } export const groupMessages = (messages: Message[]) => { - if (messages.length === 0) - return + if (messages.length === 0) + return - const grouped: GroupedMessage[] = [] + const grouped: GroupedMessage[] = [] - let lastGroupedUserId: string = extractUserId(messages[0].author) + let lastGroupedUserId: string = extractUserId(messages[0].author) - messages.forEach(message => { - if (grouped.length === 0) - grouped.push(new GroupedMessage(message, message.author)) - else if (lastGroupedUserId === extractUserId(message.author)) - grouped[grouped.length - 1].push(message) - else - grouped.push(new GroupedMessage(message, message.author)) + messages.forEach(message => { + if (grouped.length === 0) + grouped.push(new GroupedMessage(message, message.author)) + else if (lastGroupedUserId === extractUserId(message.author)) + grouped[grouped.length - 1].push(message) + else + grouped.push(new GroupedMessage(message, message.author)) - lastGroupedUserId = extractUserId(message.author) - }) + lastGroupedUserId = extractUserId(message.author) + }) - return grouped + return grouped } diff --git a/src/utils/log.utils.ts b/src/utils/log.utils.ts index 1c4fca5..1d24d7f 100644 --- a/src/utils/log.utils.ts +++ b/src/utils/log.utils.ts @@ -1,15 +1,15 @@ export const colors: { - [key in LogColor]: string + [key in LogColor]: string } = { - BLACK: '\x1b[30m', - RED: '\x1b[31m', - GREEN: '\x1b[32m', - YELLOW: '\x1b[33m', - BLUE: '\x1b[34m', - MAGENTA: '\x1b[35m', - CYAN: '\x1b[36m', - WHITE: '\x1b[37m', - RESET: '\x1b[0m' + BLACK: '\x1b[30m', + RED: '\x1b[31m', + GREEN: '\x1b[32m', + YELLOW: '\x1b[33m', + BLUE: '\x1b[34m', + MAGENTA: '\x1b[35m', + CYAN: '\x1b[36m', + WHITE: '\x1b[37m', + RESET: '\x1b[0m' } type LogColor = 'BLACK' | 'RED' | 'GREEN' | 'YELLOW' | 'BLUE' | 'MAGENTA' | 'CYAN' | 'WHITE' | 'RESET' @@ -17,32 +17,32 @@ type LogColor = 'BLACK' | 'RED' | 'GREEN' | 'YELLOW' | 'BLUE' | 'MAGENTA' | 'CYA export const logColors: LogColor[] = ['RED', 'GREEN', 'YELLOW', 'BLUE', 'MAGENTA', 'CYAN'] export interface ILogPrefix { - content: string - color?: LogColor + content: string + color?: LogColor } export default (msg: string, prefixes: ILogPrefix[] | string, color: LogColor = 'GREEN') => { - // Create empty prefix string - let prefix = '' + // Create empty prefix string + let prefix = '' - if (!prefixes) - prefixes = [] + if (!prefixes) + prefixes = [] - // If a prefix / multiple prefixes are defined - if (prefixes) - // If the first item of the possible array contains properties that could indicate a LogPrefix object - if ((prefixes[0] as ILogPrefix).content) - // For every log prefix item - prefix = (prefixes as ILogPrefix[]).map(prefixItem => { - // Construct the prefix as a string, and add it to the 'prefix' string - return `[${prefixItem.color ? colors[prefixItem.color] : colors.GREEN}${prefixItem.content.toUpperCase()}${colors.RESET}]` - }).join(' ') - // If the prefixes item is a string - else - // Construct the prefix as a string, and add it to the 'prefix' string - prefix = `[${color ? colors[color] : colors.GREEN}${(prefixes as string).toUpperCase()}${colors.RESET}]` + // If a prefix / multiple prefixes are defined + if (prefixes) + // If the first item of the possible array contains properties that could indicate a LogPrefix object + if ((prefixes[0] as ILogPrefix).content) + // For every log prefix item + prefix = (prefixes as ILogPrefix[]).map(prefixItem => { + // Construct the prefix as a string, and add it to the 'prefix' string + return `[${prefixItem.color ? colors[prefixItem.color] : colors.GREEN}${prefixItem.content.toUpperCase()}${colors.RESET}]` + }).join(' ') + // If the prefixes item is a string + else + // Construct the prefix as a string, and add it to the 'prefix' string + prefix = `[${color ? colors[color] : colors.GREEN}${(prefixes as string).toUpperCase()}${colors.RESET}]` - // Log the env, prefix and message - // tslint:disable-next-line: no-console - console.log(`[CRYB-API-${process.env.NODE_ENV.toUpperCase()}] ${prefix} ${msg}`) + // Log the env, prefix and message + // tslint:disable-next-line: no-console + console.log(`[CRYB-API-${process.env.NODE_ENV.toUpperCase()}] ${prefix} ${msg}`) } diff --git a/src/utils/validate.utils.ts b/src/utils/validate.utils.ts index 116273a..6e6568e 100644 --- a/src/utils/validate.utils.ts +++ b/src/utils/validate.utils.ts @@ -1,41 +1,41 @@ const validateKeyControllerEvent = data => { - /** + /** * TODO: Add proper key code range validation */ - const isKeyCodeValid = true, - isCtrlKeyValid = typeof data.ctrlKey === 'boolean', - isShiftKeyValid = typeof data.shiftKey === 'boolean' + const isKeyCodeValid = true, + isCtrlKeyValid = typeof data.ctrlKey === 'boolean', + isShiftKeyValid = typeof data.shiftKey === 'boolean' - return isKeyCodeValid && isCtrlKeyValid && isShiftKeyValid + return isKeyCodeValid && isCtrlKeyValid && isShiftKeyValid } const validateControllerPositionCoord = (pos: number) => typeof pos === 'number' && pos > 0 const validateControllerPosition = data => ( - validateControllerPositionCoord(data.x) && - validateControllerPositionCoord(data.y) + validateControllerPositionCoord(data.x) && + validateControllerPositionCoord(data.y) ) const validateControllerButton = (button: number) => button === 1 || button === 3 export const validateControllerEvent = (data, type) => { - switch (type) { - case 'KEY_DOWN': - return validateKeyControllerEvent(data) - case 'KEY_UP': - return validateKeyControllerEvent(data) - case 'PASTE_TEXT': - // TODO: Validation - return true - case 'MOUSE_MOVE': - return validateControllerPosition(data) - case 'MOUSE_SCROLL': - return typeof data.scrollUp === 'boolean' - case 'MOUSE_DOWN': - return validateControllerPosition(data) && validateControllerButton(data.button) - case 'MOUSE_UP': - return validateControllerPosition(data) && validateControllerButton(data.button) - default: - return false - } + switch (type) { + case 'KEY_DOWN': + return validateKeyControllerEvent(data) + case 'KEY_UP': + return validateKeyControllerEvent(data) + case 'PASTE_TEXT': + // TODO: Validation + return true + case 'MOUSE_MOVE': + return validateControllerPosition(data) + case 'MOUSE_SCROLL': + return typeof data.scrollUp === 'boolean' + case 'MOUSE_DOWN': + return validateControllerPosition(data) && validateControllerButton(data.button) + case 'MOUSE_UP': + return validateControllerPosition(data) && validateControllerButton(data.button) + default: + return false + } } diff --git a/src/utils/verifications.utils.ts b/src/utils/verifications.utils.ts index 065bece..0eafa18 100644 --- a/src/utils/verifications.utils.ts +++ b/src/utils/verifications.utils.ts @@ -1,6 +1,6 @@ export const verify_env = (...vars) => { - vars.forEach(evar => { - if (!process.env[evar.toUpperCase()]) - throw new Error(`No value was found for ${evar} - make sure .env is setup correctly!`) - }) + vars.forEach(evar => { + if (!process.env[evar.toUpperCase()]) + throw new Error(`No value was found for ${evar} - make sure .env is setup correctly!`) + }) } diff --git a/tslint.json b/tslint.json index 9e85f35..103d06a 100644 --- a/tslint.json +++ b/tslint.json @@ -8,7 +8,7 @@ "radix": false, "no-console": false, "prefer-for-of": false, - "indent": [true, "tabs", 2], + "indent": [true, "spaces", 2], "curly": [true, "as-needed"], "semicolon": [true, "never"], "quotemark": [true, "single"], From 313a78065cf9d54c115343db873cbcfb85483d26 Mon Sep 17 00:00:00 2001 From: William Gibson Date: Sun, 31 May 2020 07:45:19 +0100 Subject: [PATCH 6/8] Final stuff --- src/models/message/index.ts | 1 - src/server/index.ts | 7 ++----- src/server/mesa/index.ts | 2 ++ 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/models/message/index.ts b/src/models/message/index.ts index 6848c43..09c326f 100644 --- a/src/models/message/index.ts +++ b/src/models/message/index.ts @@ -70,7 +70,6 @@ export default class Message { this.setup(json) const message = new MesaMessage(0, this, 'MESSAGE_CREATE') - console.log(message, await fetchRoomMemberIds(author.room), [author.id]) dispatcher.dispatch(message, await fetchRoomMemberIds(author.room), [author.id]) resolve(this) diff --git a/src/server/index.ts b/src/server/index.ts index c28cf2a..ebbfad5 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -3,9 +3,9 @@ dotenv.config() import { createServer } from 'http' -import Mesa from '@cryb/mesa' import express, { json } from 'express' import { connect } from 'mongoose' +import passport from 'passport' import cors from 'cors' import helmet from 'helmet' @@ -14,9 +14,6 @@ import morgan from 'morgan' import mesa from './mesa' import routes from './routes' -import config from '../config/defaults' -import passport from '../config/passport.config' -import { getOptions } from '../config/redis.config' import { verify_env } from '../utils/verifications.utils' verify_env( @@ -45,6 +42,6 @@ app.use(morgan('dev')) app.use(passport.initialize()) routes(app) -mesa(app) +mesa(server) export default server diff --git a/src/server/mesa/index.ts b/src/server/mesa/index.ts index 1baeac8..962ddda 100644 --- a/src/server/mesa/index.ts +++ b/src/server/mesa/index.ts @@ -62,6 +62,8 @@ export default (server: http.Server) => { [id] ) + log(`Authenticated ${id}`, 'ws', 'CYAN') + done(null, { id, user }) } catch (error) { console.error(error) From 8b32480c846020e10d191b3d350e0c35a4d2d63f Mon Sep 17 00:00:00 2001 From: William Gibson Date: Mon, 27 Jul 2020 00:13:52 +0100 Subject: [PATCH 7/8] Cool eslint --- .eslintrc.js | 258 +---- package.json | 11 +- src/config/passport.config.ts | 21 +- src/config/redis.config.ts | 9 +- src/controllers/auth.controller.ts | 12 +- src/controllers/controller.controller.ts | 4 +- src/controllers/internal.controller.ts | 14 +- src/controllers/invite.controller.ts | 14 +- src/controllers/member.controller.ts | 4 +- src/controllers/message.controller.ts | 8 +- src/controllers/room.controller.ts | 9 +- src/drivers/portals.driver.ts | 15 +- src/models/room/index.ts | 684 +++++++------- src/schemas/room.schema.ts | 2 +- src/server/index.ts | 2 +- src/utils/errors.utils.ts | 13 +- src/utils/fetchers.utils.ts | 2 +- src/utils/log.utils.ts | 22 +- src/utils/validate.utils.ts | 40 +- src/utils/verifications.utils.ts | 2 +- yarn.lock | 1087 +++++++++++++++++----- 21 files changed, 1318 insertions(+), 915 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 487f9ca..6478ef5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,241 +1,65 @@ -/* -👋 Hi! This file was autogenerated by tslint-to-eslint-config. -https://github.com/typescript-eslint/tslint-to-eslint-config - -It represents the closest reasonable ESLint configuration to this -project's original TSLint configuration. - -We recommend eventually switching this configuration to extend from -the recommended rulesets in typescript-eslint. -https://github.com/typescript-eslint/tslint-to-eslint-config/blob/master/docs/FAQs.md - -Happy linting! 💖 -*/ module.exports = { - root: true, - env: { - es6: true + 'env': { + 'es6': true, + 'node': true }, - extends: [ - 'plugin:@typescript-eslint/recommended', - 'plugin:@typescript-eslint/recommended-requiring-type-checking' + 'extends': [ + 'eslint:recommended', + 'plugin:@typescript-eslint/eslint-recommended', + 'plugin:@typescript-eslint/recommended' ], - parser: '@typescript-eslint/parser', - parserOptions: { - project: 'tsconfig.json', - sourceType: 'module' + 'globals': { + 'Atomics': 'readonly', + 'SharedArrayBuffer': 'readonly' }, - plugins: [ - '@typescript-eslint', - '@typescript-eslint/tslint', - 'import', - 'prefer-arrow' + 'parser': '@typescript-eslint/parser', + 'parserOptions': { + 'ecmaVersion': 2018, + 'sourceType': 'module' + }, + 'plugins': [ + '@typescript-eslint' ], - rules: { - '@typescript-eslint/adjacent-overload-signatures': 'error', - '@typescript-eslint/array-type': [ - 'error', - { - default: 'array-simple' - } - ], - '@typescript-eslint/ban-types': [ + 'rules': { + 'indent': [ 'error', - { - types: { - Object: { - message: 'Avoid using the `Object` type. Did you mean `object`?' - }, - Function: { - message: 'Avoid using the `Function` type. Prefer a specific function type, like `() => void`.' - }, - Boolean: { - message: 'Avoid using the `Boolean` type. Did you mean `boolean`?' - }, - Number: { - message: 'Avoid using the `Number` type. Did you mean `number`?' - }, - String: { - message: 'Avoid using the `String` type. Did you mean `string`?' - }, - Symbol: { - message: 'Avoid using the `Symbol` type. Did you mean `symbol`?' - } - } - } + 2 ], - '@typescript-eslint/class-name-casing': 'error', - '@typescript-eslint/consistent-type-assertions': 'error', - '@typescript-eslint/consistent-type-definitions': 'error', - '@typescript-eslint/dot-notation': 'error', - '@typescript-eslint/explicit-member-accessibility': [ - 'error', - { - accessibility: 'explicit' - } + '@typescript-eslint/no-explicit-any': [ + 'off' ], - '@typescript-eslint/explicit-function-return-type': 'off', - '@typescript-eslint/indent': [ - 'error', - 2, - { - FunctionDeclaration: { - parameters: 'first' - }, - FunctionExpression: { - parameters: 'first' - } - } - ], - '@typescript-eslint/member-delimiter-style': [ - 'error', - { - multiline: { - delimiter: 'none', - requireLast: true - }, - singleline: { - delimiter: 'semi', - requireLast: false - } - } + '@typescript-eslint/explicit-module-boundary-types': [ + 'off' ], - '@typescript-eslint/member-ordering': 'error', - '@typescript-eslint/no-empty-function': 'error', - '@typescript-eslint/no-empty-interface': 'error', - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-misused-new': 'error', - '@typescript-eslint/no-namespace': 'error', - '@typescript-eslint/no-parameter-properties': 'off', - '@typescript-eslint/no-unused-expressions': 'error', - '@typescript-eslint/no-use-before-define': 'off', - '@typescript-eslint/no-var-requires': 'error', - '@typescript-eslint/prefer-for-of': 'off', - '@typescript-eslint/prefer-function-type': 'error', - '@typescript-eslint/prefer-namespace-keyword': 'error', - '@typescript-eslint/quotes': [ - 'error', - 'single' + 'prefer-const': [ + 'error' ], - '@typescript-eslint/semi': [ - 'error', - 'never' + 'nonblock-statement-body-position': [ + 'error', 'below' ], - '@typescript-eslint/triple-slash-reference': [ - 'error', - { - path: 'always', - types: 'prefer-import', - lib: 'always' - } + 'no-sequences': [ + 'error' ], - '@typescript-eslint/type-annotation-spacing': 'error', - '@typescript-eslint/unified-signatures': 'error', - 'arrow-body-style': 'error', - 'arrow-parens': [ - 'error', - 'as-needed' + 'eol-last': [ + 'error', 'always' ], - 'brace-style': [ - 'error', - '1tbs' - ], - 'camelcase': 'off', - '@typescript-eslint/camelcase': 'off', - 'comma-dangle': 'error', - 'complexity': 'off', - 'constructor-super': 'error', - 'curly': [ - 'error', - 'multi-or-nest' - ], - 'eol-last': 'error', - 'eqeqeq': [ - 'error', - 'smart' - ], - 'guard-for-in': 'error', - 'id-blacklist': 'off', - 'id-match': 'off', - 'import/order': 'error', - 'max-classes-per-file': [ - 'error', - 1 - ], - 'new-parens': 'error', - 'no-bitwise': 'error', - 'no-caller': 'error', - 'no-cond-assign': 'error', - 'no-console': 'off', - 'no-debugger': 'error', - 'no-empty': 'error', - 'no-eval': 'error', - 'no-fallthrough': 'off', - 'no-invalid-this': 'off', - 'no-multiple-empty-lines': 'error', - 'no-new-wrappers': 'error', - 'no-shadow': [ - 'error', - { - hoist: 'all' - } - ], - 'no-throw-literal': 'error', - 'no-trailing-spaces': 'error', - 'no-undef-init': 'error', - 'no-underscore-dangle': 'off', - 'no-unsafe-finally': 'error', - 'no-unused-labels': 'error', - 'no-var': 'error', - 'object-shorthand': 'error', 'one-var': [ - 'off', - 'never' + 'error', 'never' ], - 'prefer-arrow/prefer-arrow-functions': 'error', - 'prefer-const': 'error', - 'quote-props': [ - 'error', - 'consistent-as-needed' + 'one-var-declaration-per-line': [ + 'error', 'always' ], - 'radix': 'off', - 'space-before-function-paren': [ + 'linebreak-style': [ 'error', - { - anonymous: 'never', - asyncArrow: 'always', - named: 'never' - } + 'unix' ], - 'spaced-comment': [ + 'quotes': [ 'error', - 'always', - { - markers: [ - '/' - ] - } + 'single' ], - 'use-isnan': 'error', - 'valid-typeof': 'off', - '@typescript-eslint/tslint/config': [ + 'semi': [ 'error', - { - rules: { - 'import-spacing': true, - 'typedef': [ - true, - 'parameter' - ], - 'whitespace': [ - true, - 'check-branch', - 'check-module', - 'check-preblock' - ] - } - } + 'never' ] } } - diff --git a/package.json b/package.json index f586b21..58b15d2 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "start": "node dist", "dev": "tsc-watch --compiler typescript/bin/tsc --onSuccess \"yarn start\"", "build": "tsc", - "lint": "eslint src/**/*.ts{,x}" + "lint": "eslint src/**/*.ts" }, "dependencies": { "@cryb/mesa": "^1.4.3", @@ -50,12 +50,9 @@ "@types/passport-discord": "^0.1.3", "@types/passport-jwt": "^3.0.1", "@types/ws": "^6.0.1", - "@typescript-eslint/eslint-plugin": "^2.32.0", - "@typescript-eslint/eslint-plugin-tslint": "^2.32.0", - "@typescript-eslint/parser": "^2.32.0", - "eslint": "^7.0.0", - "eslint-plugin-import": "^2.20.2", - "eslint-plugin-prefer-arrow": "^1.2.1", + "@typescript-eslint/eslint-plugin": "^3.1.0", + "@typescript-eslint/parser": "^3.1.0", + "eslint": "^7.1.0", "tsc-watch": "^2.2.1", "tslint": "^5.20.1", "typescript": "^3.5.3" diff --git a/src/config/passport.config.ts b/src/config/passport.config.ts index 182ee34..68cfbb3 100644 --- a/src/config/passport.config.ts +++ b/src/config/passport.config.ts @@ -40,13 +40,13 @@ const fetchUser = async ( ) => new Promise(async (resolve, reject) => { try { if (process.env.AUTH_BASE_URL) { - const { authorization } = req.headers, - token = authorization.split(' ')[1], - { data: { resource } } = await axios.post(process.env.AUTH_BASE_URL, { token }), - user = new User(resource) + const { authorization } = req.headers + const token = authorization.split(' ')[1] + const { data: { resource } } = await axios.post(process.env.AUTH_BASE_URL, { token }) + const user = new User(resource) resolve(user) - } else + } else { passport.authenticate('jwt', { session: false }, async (err, user: User) => { if (err) return handleError(err, res) @@ -56,6 +56,7 @@ const fetchUser = async ( resolve(user) })(req, res, next) + } } catch (error) { reject(error) } @@ -63,14 +64,14 @@ const fetchUser = async ( export const authenticate = async (req: Request, res: Response, next: NextFunction) => { try { - const user = await fetchUser(req, res, next), - endpoint = fetchEndpoint(req), - ban = await user.fetchBan() + const user = await fetchUser(req, res, next) + const endpoint = fetchEndpoint(req) + const ban = await user.fetchBan() - if (ban && BAN_SAFE_ENDPOINTS.indexOf(endpoint) > -1) + if (ban && BAN_SAFE_ENDPOINTS.includes(endpoint)) return handleError(UserBanned, res) - if (req.baseUrl === '/admin' && user.roles.indexOf('admin') === -1) + if (req.baseUrl === '/admin' && !user.roles.includes('admin')) return handleError(UserNoAuth, res) req.user = user diff --git a/src/config/redis.config.ts b/src/config/redis.config.ts index e0cf267..18d9f74 100644 --- a/src/config/redis.config.ts +++ b/src/config/redis.config.ts @@ -1,9 +1,9 @@ -import Redis from 'ioredis' import { URL } from 'url' +import Redis from 'ioredis' interface ISentinel { - host: string - port: number + host: string; + port: number; } const parseSentinels = (sentinels: string) => @@ -16,11 +16,12 @@ export const getOptions = () => { // Get Options Method if (!process.env.REDIS_URI && !process.env.REDIS_SENTINELS) throw new Error('No value was found for REDIS_URI or REDIS_SENTINELS - make sure .env is setup correctly!') - if (process.env.REDIS_SENTINELS) + if (process.env.REDIS_SENTINELS) { return { sentinels: parseSentinels(process.env.REDIS_SENTINELS), name: 'mymaster' } as Redis.RedisOptions + } if (process.env.REDIS_URI) { const uri = new URL(process.env.REDIS_URI) diff --git a/src/controllers/auth.controller.ts b/src/controllers/auth.controller.ts index 212f7e2..253ce32 100644 --- a/src/controllers/auth.controller.ts +++ b/src/controllers/auth.controller.ts @@ -6,19 +6,19 @@ import User from '../models/user' import fetchDiscordTokens, { DISCORD_OAUTH_BASE_URL, DISCORD_OAUTH_SCOPES } from '../services/oauth2/discord.service' import { handleError } from '../utils/errors.utils' -const app = express(), - origins = process.env.DISCORD_OAUTH_ORIGINS.split(',') +const app = express() +const origins = process.env.DISCORD_OAUTH_ORIGINS.split(',') app.post('/discord', async (req, res) => { - if (origins.indexOf(req.get('origin')) === -1) + if (!origins.includes(req.get('origin'))) return res.sendStatus(401) const { code } = req.body try { - const { access_token, refresh_token, scope } = await fetchDiscordTokens(code), - user = await new User().findOrCreate(access_token, refresh_token, scope.split(' ')), - token = await user.signToken() + const { access_token, refresh_token, scope } = await fetchDiscordTokens(code) + const user = await new User().findOrCreate(access_token, refresh_token, scope.split(' ')) + const token = await user.signToken() res.send(token) } catch (error) { diff --git a/src/controllers/controller.controller.ts b/src/controllers/controller.controller.ts index 13cb8ff..02abb95 100644 --- a/src/controllers/controller.controller.ts +++ b/src/controllers/controller.controller.ts @@ -22,8 +22,8 @@ app.post('/take', authenticate, async (req, res) => { }) app.post('/give/:id', authenticate, async (req, res) => { - const { user } = req as { user: User }, - { id: toId } = req.params + const { user } = req as { user: User } + const { id: toId } = req.params try { const { room } = await user.fetchRoom() as { room: Room } diff --git a/src/controllers/internal.controller.ts b/src/controllers/internal.controller.ts index ca68ab8..e754dab 100644 --- a/src/controllers/internal.controller.ts +++ b/src/controllers/internal.controller.ts @@ -18,7 +18,7 @@ const app = express() * Assign New Portal ID to Room */ app.post('/portal', authenticate, async (req, res) => { - const { id, roomId } = req.body as { id: string, roomId: string } + const { id, roomId } = req.body as { id: string; roomId: string } try { const room = await new Room().load(roomId) @@ -34,7 +34,7 @@ app.post('/portal', authenticate, async (req, res) => { * Existing Portal Status Update */ app.put('/portal', authenticate, async (req, res) => { - const { id, status } = req.body as { id: string, status: PortalAllocationStatus } + const { id, status } = req.body as { id: string; status: PortalAllocationStatus } // console.log('recieved', id, status, 'from portal microservice, finding room...') try { @@ -47,7 +47,7 @@ app.put('/portal', authenticate, async (req, res) => { const room = new Room(doc) const { portal: allocation } = await room.updatePortalAllocation({ status }) - const { online } = await room.fetchOnlineMemberIds() + const { online } = await room.fetchOnlineMemberIds() // console.log('status updated and online members fetched:', online) @@ -59,8 +59,8 @@ app.put('/portal', authenticate, async (req, res) => { dispatcher.dispatch(updateMessage, online) if (status === 'open') { - const token = signApertureToken(id), - apertureMessage = new Message(0, { ws: process.env.APERTURE_WS_URL, t: token }, 'APERTURE_CONFIG') + const token = signApertureToken(id) + const apertureMessage = new Message(0, { ws: process.env.APERTURE_WS_URL, t: token }, 'APERTURE_CONFIG') dispatcher.dispatch(apertureMessage, online) } @@ -77,8 +77,8 @@ app.post('/queue', authenticate, (req, res) => { queue.forEach(async (id, i) => { try { - const op = 0, d = { pos: i, len: queue.length }, t = 'PORTAL_QUEUE_UPDATE', - message = new Message(op, d, t) + const op = 0; const d = { pos: i, len: queue.length }; const t = 'PORTAL_QUEUE_UPDATE' + const message = new Message(op, d, t) dispatcher.dispatch(message, await fetchRoomMemberIds(id)) } catch (error) { diff --git a/src/controllers/invite.controller.ts b/src/controllers/invite.controller.ts index 5e5adcc..e59b05e 100644 --- a/src/controllers/invite.controller.ts +++ b/src/controllers/invite.controller.ts @@ -24,13 +24,13 @@ app.post('/', authenticate, async (req, res) => { }) app.post('/:code', authenticate, async (req, res) => { - const { user } = req as { user: User }, - { code } = req.params, - { type } = req.body + const { user } = req as { user: User } + const { code } = req.params + const { type } = req.body try { - const invite = await new Invite().findFromCode(code), - target = await invite.use(user, type) + const invite = await new Invite().findFromCode(code) + const target = await invite.use(user, type) if (!target) return res.sendStatus(200) @@ -45,8 +45,8 @@ app.get('/:code/peek', async (req, res) => { const { code } = req.params try { - const invite = await new Invite().findFromCode(code), - { target } = await invite.fetchTarget() + const invite = await new Invite().findFromCode(code) + const { target } = await invite.fetchTarget() if (!target) return res.sendStatus(401) diff --git a/src/controllers/member.controller.ts b/src/controllers/member.controller.ts index efb071d..ec17581 100644 --- a/src/controllers/member.controller.ts +++ b/src/controllers/member.controller.ts @@ -23,8 +23,8 @@ app.post('/:id/kick', authenticate, async (req, res) => { return res.status(401) try { - const { members } = await user.room.fetchMembers(), - member = members.find(({ id: userId }) => userId === id) + const { members } = await user.room.fetchMembers() + const member = members.find(({ id: userId }) => userId === id) if (!member) return res.status(409) diff --git a/src/controllers/message.controller.ts b/src/controllers/message.controller.ts index edf983a..117a747 100644 --- a/src/controllers/message.controller.ts +++ b/src/controllers/message.controller.ts @@ -35,8 +35,8 @@ app.post('/', authenticate, async (req, res) => { }) app.post('/:id/report', authenticate, async (req, res) => { - const { user } = req as { user: User }, - { id: messageId } = req.params + const { user } = req as { user: User } + const { id: messageId } = req.params try { await new Report().create(messageId, user.room, user) @@ -48,8 +48,8 @@ app.post('/:id/report', authenticate, async (req, res) => { }) app.delete('/:id', authenticate, async (req, res) => { - const { user } = req as { user: User }, - { id: messageId } = req.params + const { user } = req as { user: User } + const { id: messageId } = req.params try { if (typeof user.room === 'string') diff --git a/src/controllers/room.controller.ts b/src/controllers/room.controller.ts index a9ee359..c3bd780 100644 --- a/src/controllers/room.controller.ts +++ b/src/controllers/room.controller.ts @@ -14,8 +14,8 @@ import { } from '../utils/errors.utils' import { extractRoomId, extractUserId } from '../utils/helpers.utils' -const app = express(), - AVAILABLE_TYPES: RoomType[] = ['vm'] +const app = express() +const AVAILABLE_TYPES: RoomType[] = ['vm'] app.get('/', authenticate, async (req, res) => { const { user } = req as { user: User } @@ -168,7 +168,8 @@ app.post('/leave', authenticate, async (req, res) => { }) app.patch('/type', authenticate, async (req, res) => { - const { user } = req as { user: User }, { type } = req.body as { type: RoomType } + const { user } = req as { user: User }; const + { type } = req.body as { type: RoomType } if (!user.room) return handleError(UserNotInRoom, res) @@ -179,7 +180,7 @@ app.patch('/type', authenticate, async (req, res) => { if (extractUserId(user.room.owner) !== user.id) return res.status(401) - if (AVAILABLE_TYPES.indexOf(type) === -1) + if (!AVAILABLE_TYPES.includes(type)) return res.status(406) try { diff --git a/src/drivers/portals.driver.ts b/src/drivers/portals.driver.ts index de00fa2..cdd5a96 100644 --- a/src/drivers/portals.driver.ts +++ b/src/drivers/portals.driver.ts @@ -9,12 +9,13 @@ import Room from '../models/room' import { extractUserId } from '../utils/helpers.utils' import log from '../utils/log.utils' -const url = `${process.env.PORTALS_API_URL}/`, key = process.env.PORTALS_API_KEY +const url = `${process.env.PORTALS_API_URL}/`; const + key = process.env.PORTALS_API_KEY -const generateRoomToken = (room: Room) => jwt.sign({ roomId: room.id }, key), - generateHeaders = async (room: Room) => ({ - Authorization: `Valve ${generateRoomToken(room)}` - }) +const generateRoomToken = (room: Room) => jwt.sign({ roomId: room.id }, key) +const generateHeaders = async (room: Room) => ({ + Authorization: `Valve ${generateRoomToken(room)}` +}) export const createPortal = (room: Room) => new Promise(async (resolve, reject) => { try { @@ -35,8 +36,8 @@ export const createPortal = (room: Room) => new Promise(async (resolve, reject) export const destroyPortal = (room: Room) => new Promise(async (resolve, reject) => { try { - const headers = await generateHeaders(room), - { portal } = room + const headers = await generateHeaders(room) + const { portal } = room if (!portal.id) return diff --git a/src/models/room/index.ts b/src/models/room/index.ts index 47fbe33..a71bb1e 100644 --- a/src/models/room/index.ts +++ b/src/models/room/index.ts @@ -17,12 +17,12 @@ import dispatcher from '../../config/dispatcher.config' import client from '../../config/redis.config' // import WSMessage from '../../server/websocket/models/message' import { - ControllerIsNotAvailable, - PortalNotOpen, - RoomNotFound, - UserAlreadyInRoom, - UserDoesNotHaveRemote, - UserIsNotPermitted + ControllerIsNotAvailable, + PortalNotOpen, + RoomNotFound, + UserAlreadyInRoom, + UserDoesNotHaveRemote, + UserIsNotPermitted } from '../../utils/errors.utils' import { generateFlake } from '../../utils/generate.utils' import { extractUserId, GroupedMessage, groupMessages } from '../../utils/helpers.utils' @@ -50,464 +50,464 @@ export default class Room { public online: string[] constructor(json?: IRoom) { - if (!json) return + if (!json) return - this.setup(json) + this.setup(json) } public load = (id: string) => new Promise(async (resolve, reject) => { - try { - const doc = await StoredRoom.findOne({ 'info.id': id }) - if (!doc) - return reject(RoomNotFound) + try { + const doc = await StoredRoom.findOne({ 'info.id': id }) + if (!doc) + return reject(RoomNotFound) - this.setup(doc) + this.setup(doc) - resolve(this) - } catch (error) { - reject(error) - } + resolve(this) + } catch (error) { + reject(error) + } }) public create = (name: string, creator: User) => new Promise(async (resolve, reject) => { - if (creator.room) - return reject(UserAlreadyInRoom) - - try { - const json: IRoom = { - info: { - id: generateFlake(), - createdAt: Date.now(), - - type: 'vm', - portal: { - status: 'waiting', - lastUpdatedAt: Date.now() - }, - - owner: creator.id, - controller: creator.id - }, - profile: { - name - } - } + if (creator.room) + return reject(UserAlreadyInRoom) + + try { + const json: IRoom = { + info: { + id: generateFlake(), + createdAt: Date.now(), + + type: 'vm', + portal: { + status: 'waiting', + lastUpdatedAt: Date.now() + }, + + owner: creator.id, + controller: creator.id + }, + profile: { + name + } + } - const stored = new StoredRoom(json) - await stored.save() + const stored = new StoredRoom(json) + await stored.save() - this.setup(json) + this.setup(json) - await this.createInvite(creator, false) // System prop false as data will be delivered over REST - await creator.joinRoom(this, true) + await this.createInvite(creator, false) // System prop false as data will be delivered over REST + await creator.joinRoom(this, true) - client.hset('controller', this.id, creator.id) + client.hset('controller', this.id, creator.id) - resolve(this) - } catch (error) { - reject(error) - } + resolve(this) + } catch (error) { + reject(error) + } }) /** * The system prop indicates if the invite was created by a Cryb update */ public createInvite = (creator: User, system: boolean) => new Promise(async (resolve, reject) => { - try { - const invite = await new Invite().create( - this, - 'room', - { maxUses: 0, unlimitedUses: true }, - { system: true }, - creator - ) - - if (!this.invites) - this.invites = [] - - this.invites.push(invite) - - if (system) { - const message = new MesaMessage(0, invite, 'INVITE_UPDATE') - dispatcher.dispatch(message, [extractUserId(this.owner)]) - } - - resolve(invite) - } catch (error) { - reject(error) + try { + const invite = await new Invite().create( + this, + 'room', + { maxUses: 0, unlimitedUses: true }, + { system: true }, + creator + ) + + if (!this.invites) + this.invites = [] + + this.invites.push(invite) + + if (system) { + const message = new MesaMessage(0, invite, 'INVITE_UPDATE') + dispatcher.dispatch(message, [extractUserId(this.owner)]) } + + resolve(invite) + } catch (error) { + reject(error) + } }) public transferOwnership = (to: User) => new Promise(async (resolve, reject) => { - try { - const newOwnerId = extractUserId(to) - - await StoredRoom.updateOne({ - 'info.id': this.id - }, { - $set: { - 'info.owner': newOwnerId - } - }) - - const message = new MesaMessage(0, { u: newOwnerId }, 'OWNER_UPDATE') - dispatcher.dispatch(message, this.members.map(extractUserId)) - - this.owner = to - - resolve(this) - } catch (error) { - reject(error) - } + try { + const newOwnerId = extractUserId(to) + + await StoredRoom.updateOne({ + 'info.id': this.id + }, { + $set: { + 'info.owner': newOwnerId + } + }) + + const message = new MesaMessage(0, { u: newOwnerId }, 'OWNER_UPDATE') + dispatcher.dispatch(message, this.members.map(extractUserId)) + + this.owner = to + + resolve(this) + } catch (error) { + reject(error) + } }) public fetchMembers = (index: number = 0) => new Promise(async (resolve, reject) => { - try { - const docs = await StoredUser.find({ 'info.room': this.id }).skip(index).limit(10) + try { + const docs = await StoredUser.find({ 'info.room': this.id }).skip(index).limit(10) - if (docs.length === 0) - return resolve(this) + if (docs.length === 0) + return resolve(this) - const members = docs.map(doc => new User(doc)) - this.members = members + const members = docs.map(doc => new User(doc)) + this.members = members - const ownerId = extractUserId(this.owner), - controllerId = extractUserId(this.controller) + const ownerId = extractUserId(this.owner), + controllerId = extractUserId(this.controller) - members.forEach(member => { - if (ownerId === member.id) - this.owner = member + members.forEach(member => { + if (ownerId === member.id) + this.owner = member - if (controllerId === member.id) - this.controller = member - }) + if (controllerId === member.id) + this.controller = member + }) - resolve(this) - } catch (error) { - reject(error) - } + resolve(this) + } catch (error) { + reject(error) + } }) public fetchOnlineMemberIds = () => new Promise(async (resolve, reject) => { - try { - const memberIds = await StoredUser.distinct('info.id', { 'info.room': this.id }), - connectedClientIds: string[] = await client.smembers('connected_clients') + try { + const memberIds = await StoredUser.distinct('info.id', { 'info.room': this.id }), + connectedClientIds: string[] = await client.smembers('connected_clients') - this.online = connectedClientIds.filter(id => memberIds.indexOf(id) > -1) + this.online = connectedClientIds.filter(id => memberIds.indexOf(id) > -1) - resolve(this) - } catch (error) { - reject(error) - } + resolve(this) + } catch (error) { + reject(error) + } }) public fetchMessages = (index: number = 0) => new Promise(async (resolve, reject) => { - try { - const docs = await StoredMessage.find({ 'info.room': this.id }).sort({ 'info.createdAt': -1 }).skip(index).limit(50) + try { + const docs = await StoredMessage.find({ 'info.room': this.id }).sort({ 'info.createdAt': -1 }).skip(index).limit(50) - if (docs.length === 0) - return resolve(this) + if (docs.length === 0) + return resolve(this) - const messages = docs.map(doc => new Message(doc)) - this.messages = groupMessages(messages.reverse()) + const messages = docs.map(doc => new Message(doc)) + this.messages = groupMessages(messages.reverse()) - resolve(this) - } catch (error) { - reject(error) - } + resolve(this) + } catch (error) { + reject(error) + } }) public fetchInvites = (index: number = 0) => new Promise(async (resolve, reject) => { - try { - const docs = await StoredInvite.find({ - $and: [ - { - 'info.targetId': this.id - }, - { - 'info.targetType': 'room' - }, - { - 'info.active': true - } - ] - }).skip(index).limit(10) - - if (docs.length === 0) - return resolve(this) - - const invites = docs.map(doc => new Invite(doc)) - this.invites = invites - - resolve(this) - } catch (error) { - reject(error) - } + try { + const docs = await StoredInvite.find({ + $and: [ + { + 'info.targetId': this.id + }, + { + 'info.targetType': 'room' + }, + { + 'info.active': true + } + ] + }).skip(index).limit(10) + + if (docs.length === 0) + return resolve(this) + + const invites = docs.map(doc => new Invite(doc)) + this.invites = invites + + resolve(this) + } catch (error) { + reject(error) + } }) public refreshInvites = (user: User, system: boolean) => new Promise(async (resolve, reject) => { - try { - await this.destroyInvites() + try { + await this.destroyInvites() - const invite = await this.createInvite(user, system) + const invite = await this.createInvite(user, system) - resolve(invite) - } catch (error) { - reject(error) - } + resolve(invite) + } catch (error) { + reject(error) + } }) public destroyInvites = () => new Promise(async (resolve, reject) => { - try { - await StoredInvite.deleteMany({ - $and: [ - { - 'info.targetId': this.id - }, - { - 'info.targetType': 'room' - } - ] - }) - - this.invites = [] - - resolve(this) - } catch (error) { - reject(error) - } + try { + await StoredInvite.deleteMany({ + $and: [ + { + 'info.targetId': this.id + }, + { + 'info.targetType': 'room' + } + ] + }) + + this.invites = [] + + resolve(this) + } catch (error) { + reject(error) + } }) public setPortalId = (id: string) => new Promise(async (resolve, reject) => { - try { - const allocation: IPortalAllocation = { - id, - janusId: 1, - janusIp: '0.0.0.0', - status: 'creating', - lastUpdatedAt: Date.now() - } + try { + const allocation: IPortalAllocation = { + id, + janusId: 1, + janusIp: '0.0.0.0', + status: 'creating', + lastUpdatedAt: Date.now() + } - await StoredRoom.updateOne({ - 'info.id': this.id - }, { - $set: { - 'info.portal': allocation - } - }) + await StoredRoom.updateOne({ + 'info.id': this.id + }, { + $set: { + 'info.portal': allocation + } + }) - const message = new MesaMessage(0, allocation, 'PORTAL_UPDATE') - dispatcher.dispatch(message, this.members.map(extractUserId)) + const message = new MesaMessage(0, allocation, 'PORTAL_UPDATE') + dispatcher.dispatch(message, this.members.map(extractUserId)) - this.portal = allocation + this.portal = allocation - resolve(this) - } catch (error) { - reject(error) - } + resolve(this) + } catch (error) { + reject(error) + } }) public updatePortalAllocation = (allocation: IPortalAllocation) => new Promise(async (resolve, reject) => { - allocation.lastUpdatedAt = Date.now() + allocation.lastUpdatedAt = Date.now() - try { - const currentAllocation = this.portal - Object.keys(allocation).forEach(key => currentAllocation[key] = allocation[key]) + try { + const currentAllocation = this.portal + Object.keys(allocation).forEach(key => currentAllocation[key] = allocation[key]) - if (currentAllocation.status === 'closed') - delete currentAllocation.id + if (currentAllocation.status === 'closed') + delete currentAllocation.id - await StoredRoom.updateOne({ - 'info.id': this.id - }, { - $set: { - 'info.portal': currentAllocation - } - }) + await StoredRoom.updateOne({ + 'info.id': this.id + }, { + $set: { + 'info.portal': currentAllocation + } + }) - this.portal = currentAllocation + this.portal = currentAllocation - resolve(this) - } catch (error) { - reject(error) - } + resolve(this) + } catch (error) { + reject(error) + } }) public takeControl = (from: UserResolvable) => new Promise(async (resolve, reject) => { - const fromId = extractUserId(from) + const fromId = extractUserId(from) - if (this.controller !== null) - return reject(ControllerIsNotAvailable) + if (this.controller !== null) + return reject(ControllerIsNotAvailable) - try { - await StoredRoom.updateOne({ - 'info.id' :this.id - }, { - $set: { - 'info.controller': fromId - } - }) + try { + await StoredRoom.updateOne({ + 'info.id' :this.id + }, { + $set: { + 'info.controller': fromId + } + }) - client.hset('controller', this.id, fromId) + client.hset('controller', this.id, fromId) - const message = new MesaMessage(0, { u: fromId }, 'CONTROLLER_UPDATE') - dispatcher.dispatch(message, this.members.map(extractUserId), [fromId]) + const message = new MesaMessage(0, { u: fromId }, 'CONTROLLER_UPDATE') + dispatcher.dispatch(message, this.members.map(extractUserId), [fromId]) - this.controller = fromId + this.controller = fromId - resolve(this) - } catch (error) { - reject(error) - } + resolve(this) + } catch (error) { + reject(error) + } }) public giveControl = (to: UserResolvable, from: UserResolvable) => new Promise(async (resolve, reject) => { - const ownerId = extractUserId(this.owner), - controllerId = extractUserId(this.controller), - toId = extractUserId(to), - fromId = extractUserId(from) - - if (fromId !== controllerId && fromId !== ownerId) - return reject(UserDoesNotHaveRemote) - - try { - await StoredRoom.updateOne({ - 'info.id': this.id - }, { - $set: { - 'info.controller': toId - } - }) - - client.hset('controller', this.id, toId) - - const message = new MesaMessage(0, { u: toId }, 'CONTROLLER_UPDATE') - dispatcher.dispatch(message, this.members.map(extractUserId), [fromId]) - - this.controller = toId - - resolve(this) - } catch (error) { - reject(error) - } + const ownerId = extractUserId(this.owner), + controllerId = extractUserId(this.controller), + toId = extractUserId(to), + fromId = extractUserId(from) + + if (fromId !== controllerId && fromId !== ownerId) + return reject(UserDoesNotHaveRemote) + + try { + await StoredRoom.updateOne({ + 'info.id': this.id + }, { + $set: { + 'info.controller': toId + } + }) + + client.hset('controller', this.id, toId) + + const message = new MesaMessage(0, { u: toId }, 'CONTROLLER_UPDATE') + dispatcher.dispatch(message, this.members.map(extractUserId), [fromId]) + + this.controller = toId + + resolve(this) + } catch (error) { + reject(error) + } }) public releaseControl = (sender: UserResolvable) => new Promise(async (resolve, reject) => { - const ownerId = extractUserId(this.owner), - senderId = extractUserId(sender), - controllerId = extractUserId(this.controller) + const ownerId = extractUserId(this.owner), + senderId = extractUserId(sender), + controllerId = extractUserId(this.controller) - if (senderId !== ownerId && senderId !== controllerId) - return reject(UserIsNotPermitted) + if (senderId !== ownerId && senderId !== controllerId) + return reject(UserIsNotPermitted) - try { - await StoredRoom.updateOne({ - 'info.id': this.id - }, { - $set: { - 'info.controller': null - } - }) + try { + await StoredRoom.updateOne({ + 'info.id': this.id + }, { + $set: { + 'info.controller': null + } + }) - client.hdel('controller', this.id) + client.hdel('controller', this.id) - const message = new MesaMessage(0, { u: null }, 'CONTROLLER_UPDATE') - dispatcher.dispatch(message, this.members.map(extractUserId), [senderId]) + const message = new MesaMessage(0, { u: null }, 'CONTROLLER_UPDATE') + dispatcher.dispatch(message, this.members.map(extractUserId), [senderId]) - this.controller = null + this.controller = null - resolve(this) - } catch (error) { - reject(error) - } + resolve(this) + } catch (error) { + reject(error) + } }) public createPortal = () => createPortal(this) public restartPortal = () => new Promise(async (resolve, reject) => { - if (this.portal.status !== 'open') - return reject(PortalNotOpen) + if (this.portal.status !== 'open') + return reject(PortalNotOpen) - try { - await this.destroyPortal() - await this.createPortal() + try { + await this.destroyPortal() + await this.createPortal() - resolve() - } catch (error) { - reject(error) - } + resolve() + } catch (error) { + reject(error) + } }) public destroyPortal = async () => { - await destroyPortal(this) - await this.updatePortalAllocation({ status: 'closed' }) + await destroyPortal(this) + await this.updatePortalAllocation({ status: 'closed' }) - delete this.portal + delete this.portal } public updateType = (type: RoomType) => new Promise(async (resolve, reject) => { - try { - await StoredRoom.updateOne({ - 'info.id': this.id - }, { - $set: { - 'info.type': type - } - }) - - this.type = type - - resolve(this) - } catch (error) { - reject(error) - } + try { + await StoredRoom.updateOne({ + 'info.id': this.id + }, { + $set: { + 'info.type': type + } + }) + + this.type = type + + resolve(this) + } catch (error) { + reject(error) + } }) public destroy = () => new Promise(async (resolve, reject) => { - try { - const message = new MesaMessage(0, {}, 'ROOM_DESTROY') - dispatcher.dispatch(message, this.members.map(extractUserId)) + try { + const message = new MesaMessage(0, {}, 'ROOM_DESTROY') + dispatcher.dispatch(message, this.members.map(extractUserId)) - await StoredRoom.deleteOne({ 'info.id': this.id }) - await StoredMessage.deleteMany({ 'info.room': this.id }) + await StoredRoom.deleteOne({ 'info.id': this.id }) + await StoredMessage.deleteMany({ 'info.room': this.id }) - await StoredUser.updateMany({ - 'info.room': this.id - }, { - $unset: { - 'info.room': '' - } - }) + await StoredUser.updateMany({ + 'info.room': this.id + }, { + $unset: { + 'info.room': '' + } + }) - await this.destroyInvites() + await this.destroyInvites() - if (this.portal) - destroyPortal(this) + if (this.portal) + destroyPortal(this) - await client.hdel('controller', this.id) + await client.hdel('controller', this.id) - resolve() - } catch (error) { - reject(error) - } + resolve() + } catch (error) { + reject(error) + } }) public setup = (json: IRoom) => { - this.id = json.info.id - this.createdAt = json.info.createdAt - this.endedAt = json.info.endedAt + this.id = json.info.id + this.createdAt = json.info.createdAt + this.endedAt = json.info.endedAt - this.type = json.info.type - this.portal = json.info.portal + this.type = json.info.type + this.portal = json.info.portal - this.owner = json.info.owner - this.controller = json.info.controller + this.owner = json.info.owner + this.controller = json.info.controller - this.name = json.profile.name + this.name = json.profile.name } public prepare = () => ({ - ...this, - members: this.members.map(member => typeof member === 'string' ? member : member.prepare()) + ...this, + members: this.members.map(member => typeof member === 'string' ? member : member.prepare()) } as Room) } diff --git a/src/schemas/room.schema.ts b/src/schemas/room.schema.ts index 8e27521..b988f59 100644 --- a/src/schemas/room.schema.ts +++ b/src/schemas/room.schema.ts @@ -12,7 +12,7 @@ const RoomSchema = new Schema({ portal: { id: String, janusId: Number, - janusIp: String, + janusIp: String, status: String, lastUpdatedAt: String diff --git a/src/server/index.ts b/src/server/index.ts index ebbfad5..4e522a4 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -11,10 +11,10 @@ import cors from 'cors' import helmet from 'helmet' import morgan from 'morgan' +import { verify_env } from '../utils/verifications.utils' import mesa from './mesa' import routes from './routes' -import { verify_env } from '../utils/verifications.utils' verify_env( 'JWT_KEY', diff --git a/src/utils/errors.utils.ts b/src/utils/errors.utils.ts index 63ab792..1c35057 100644 --- a/src/utils/errors.utils.ts +++ b/src/utils/errors.utils.ts @@ -1,12 +1,12 @@ import { Response } from 'express' interface IAPIResponse { - response: string + response: string; error: { - title: string - description: string - } - status: number + title: string; + description: string; + }; + status: number; } export const UserNoAuth: IAPIResponse = { @@ -220,10 +220,11 @@ export const handleError = (error: any, res: Response) => { if (process.env.NODE_ENV === 'development') console.error(error) - if (error) + if (error) { if (error.response && error.error && error.status) return res.status(error.status).send(error) else if (error.status) return res.sendStatus(error.status) + } res.sendStatus(500) } diff --git a/src/utils/fetchers.utils.ts b/src/utils/fetchers.utils.ts index cc15486..dde5569 100644 --- a/src/utils/fetchers.utils.ts +++ b/src/utils/fetchers.utils.ts @@ -1,6 +1,6 @@ import { RoomResolvable } from '../models/room' +import StoredUser from '../schemas/user.schema' import { extractRoomId } from './helpers.utils' -import StoredUser from '../schemas/user.schema' export const fetchRoomMemberIds = (room: RoomResolvable) => StoredUser.distinct('info.id', { 'info.room': extractRoomId(room) }) diff --git a/src/utils/log.utils.ts b/src/utils/log.utils.ts index 1d24d7f..27511ed 100644 --- a/src/utils/log.utils.ts +++ b/src/utils/log.utils.ts @@ -17,8 +17,8 @@ type LogColor = 'BLACK' | 'RED' | 'GREEN' | 'YELLOW' | 'BLUE' | 'MAGENTA' | 'CYA export const logColors: LogColor[] = ['RED', 'GREEN', 'YELLOW', 'BLUE', 'MAGENTA', 'CYAN'] export interface ILogPrefix { - content: string - color?: LogColor + content: string; + color?: LogColor; } export default (msg: string, prefixes: ILogPrefix[] | string, color: LogColor = 'GREEN') => { @@ -30,17 +30,21 @@ export default (msg: string, prefixes: ILogPrefix[] | string, color: LogColor = // If a prefix / multiple prefixes are defined if (prefixes) - // If the first item of the possible array contains properties that could indicate a LogPrefix object + // If the first item of the possible array contains properties that could indicate a LogPrefix object + { if ((prefixes[0] as ILogPrefix).content) - // For every log prefix item - prefix = (prefixes as ILogPrefix[]).map(prefixItem => { - // Construct the prefix as a string, and add it to the 'prefix' string - return `[${prefixItem.color ? colors[prefixItem.color] : colors.GREEN}${prefixItem.content.toUpperCase()}${colors.RESET}]` - }).join(' ') + // For every log prefix item + { + prefix = (prefixes as ILogPrefix[]).map(prefixItem => + // Construct the prefix as a string, and add it to the 'prefix' string + `[${prefixItem.color ? colors[prefixItem.color] : colors.GREEN}${prefixItem.content.toUpperCase()}${colors.RESET}]` + ).join(' ') + } // If the prefixes item is a string else - // Construct the prefix as a string, and add it to the 'prefix' string + // Construct the prefix as a string, and add it to the 'prefix' string prefix = `[${color ? colors[color] : colors.GREEN}${(prefixes as string).toUpperCase()}${colors.RESET}]` + } // Log the env, prefix and message // tslint:disable-next-line: no-console diff --git a/src/utils/validate.utils.ts b/src/utils/validate.utils.ts index 6e6568e..72d2a41 100644 --- a/src/utils/validate.utils.ts +++ b/src/utils/validate.utils.ts @@ -2,9 +2,9 @@ const validateKeyControllerEvent = data => { /** * TODO: Add proper key code range validation */ - const isKeyCodeValid = true, - isCtrlKeyValid = typeof data.ctrlKey === 'boolean', - isShiftKeyValid = typeof data.shiftKey === 'boolean' + const isKeyCodeValid = true + const isCtrlKeyValid = typeof data.ctrlKey === 'boolean' + const isShiftKeyValid = typeof data.shiftKey === 'boolean' return isKeyCodeValid && isCtrlKeyValid && isShiftKeyValid } @@ -20,22 +20,22 @@ const validateControllerButton = (button: number) => button === 1 || button === export const validateControllerEvent = (data, type) => { switch (type) { - case 'KEY_DOWN': - return validateKeyControllerEvent(data) - case 'KEY_UP': - return validateKeyControllerEvent(data) - case 'PASTE_TEXT': - // TODO: Validation - return true - case 'MOUSE_MOVE': - return validateControllerPosition(data) - case 'MOUSE_SCROLL': - return typeof data.scrollUp === 'boolean' - case 'MOUSE_DOWN': - return validateControllerPosition(data) && validateControllerButton(data.button) - case 'MOUSE_UP': - return validateControllerPosition(data) && validateControllerButton(data.button) - default: - return false + case 'KEY_DOWN': + return validateKeyControllerEvent(data) + case 'KEY_UP': + return validateKeyControllerEvent(data) + case 'PASTE_TEXT': + // TODO: Validation + return true + case 'MOUSE_MOVE': + return validateControllerPosition(data) + case 'MOUSE_SCROLL': + return typeof data.scrollUp === 'boolean' + case 'MOUSE_DOWN': + return validateControllerPosition(data) && validateControllerButton(data.button) + case 'MOUSE_UP': + return validateControllerPosition(data) && validateControllerButton(data.button) + default: + return false } } diff --git a/src/utils/verifications.utils.ts b/src/utils/verifications.utils.ts index 0eafa18..5c5cb80 100644 --- a/src/utils/verifications.utils.ts +++ b/src/utils/verifications.utils.ts @@ -1,4 +1,4 @@ -export const verify_env = (...vars) => { +export const verifyEnv = (...vars) => { vars.forEach(evar => { if (!process.env[evar.toUpperCase()]) throw new Error(`No value was found for ${evar} - make sure .env is setup correctly!`) diff --git a/yarn.lock b/yarn.lock index f5ac3ec..5aa874b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3,54 +3,64 @@ "@babel/code-frame@^7.0.0": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.5.5.tgz#bc0782f6d69f7b7d49531219699b988f669a8f9d" - integrity sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw== + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a" + integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg== dependencies: - "@babel/highlight" "^7.0.0" + "@babel/highlight" "^7.10.4" -"@babel/highlight@^7.0.0": - version "7.5.0" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.5.0.tgz#56d11312bd9248fa619591d02472be6e8cb32540" - integrity sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ== +"@babel/helper-validator-identifier@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2" + integrity sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw== + +"@babel/highlight@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.4.tgz#7d1bdfd65753538fabe6c38596cdb76d9ac60143" + integrity sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA== dependencies: + "@babel/helper-validator-identifier" "^7.10.4" chalk "^2.0.0" - esutils "^2.0.2" js-tokens "^4.0.0" "@cryb/mesa@^1.4.3": - version "1.4.3" - resolved "https://registry.yarnpkg.com/@cryb/mesa/-/mesa-1.4.3.tgz#565f5c4eaf41f8d80de849c8c220017327215254" - integrity sha512-kv6ncqZ2QgbTYvAbKCdY+0F9NCsBQQYUwuRCbn9hsEXJcbaZh6gGqiCJUS97xS7o8s7lRUVJujMzD+P5mIAo6w== + version "1.4.7" + resolved "https://registry.yarnpkg.com/@cryb/mesa/-/mesa-1.4.7.tgz#ff37f5956932977e1748f4e403620fbc04b8c2e5" + integrity sha512-ybMjn4SUqwxWuB67YesHLDrzhLXJf8S8saLffV7qHnV/knZGnAXD50KRd9ICfgKUbYA/pnxecx5YyyinC5NOMg== dependencies: death "^1.1.0" ioredis "^4.14.1" ws "^7.2.1" "@types/body-parser@*": - version "1.17.0" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.17.0.tgz#9f5c9d9bd04bb54be32d5eb9fc0d8c974e6cf58c" - integrity sha512-a2+YeUjPkztKJu5aIF2yArYFQQp8d51wZ7DavSHjFuY1mqVgidGyzEQ41JIVNy82fXj8yPgy2vJmfIywgESW6w== + version "1.19.0" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.0.tgz#0685b3c47eb3006ffed117cdd55164b61f80538f" + integrity sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ== dependencies: "@types/connect" "*" "@types/node" "*" "@types/bson@*": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@types/bson/-/bson-4.0.0.tgz#9073772679d749116eb1dfca56f8eaac6d59cc7a" - integrity sha512-pq/rqJwJWkbS10crsG5bgnrisL8pML79KlMKQMoQwLUjlPAkrUHMvHJ3oGwE7WHR61Lv/nadMwXVAD2b+fpD8Q== + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/bson/-/bson-4.0.2.tgz#7accb85942fc39bbdb7515d4de437c04f698115f" + integrity sha512-+uWmsejEHfmSjyyM/LkrP0orfE2m5Mx9Xel4tXNeqi1ldK5XMQcDsFkBmLDtuyKUbxj2jGDo0H240fbCRJZo7Q== dependencies: "@types/node" "*" "@types/chance@^1.0.6": - version "1.0.6" - resolved "https://registry.yarnpkg.com/@types/chance/-/chance-1.0.6.tgz#873a4b646ce30616cd2fdf4c7753a8bf4210ac03" - integrity sha512-CQ+PccoeLogLC1n9PezDXHMJ6wZVTE3liQPqKTtWQcTGUQK463gdPRWfyRhwBf0fi91IsOo2QlBacVa5+ppsTw== + version "1.1.0" + resolved "https://registry.yarnpkg.com/@types/chance/-/chance-1.1.0.tgz#7d8e6bd0506344d94c042f692d59d20f8eb7d66d" + integrity sha512-j/9aaLU6JaaN2iFiSZgvD+G0nju1Fi2/f2WM+WwS+8+cpTdzFhXFH3+SHZgfjgum6wNW80sfcawUx+Rx7tH26w== + +"@types/color-name@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" + integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== "@types/connect@*": - version "3.4.32" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.32.tgz#aa0e9616b9435ccad02bc52b5b454ffc2c70ba28" - integrity sha512-4r8qa0quOvh7lGD0pre62CAb1oni1OO6ecJLGCezTmhQ8Fz50Arx9RUszryR8KlgK6avuSXvviL6yWyViQABOg== + version "3.4.33" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.33.tgz#31610c901eca573b8713c3330abc6e6b9f588546" + integrity sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A== dependencies: "@types/node" "*" @@ -68,86 +78,93 @@ dependencies: dotenv "*" -"@types/events@*": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" - integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== +"@types/eslint-visitor-keys@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" + integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== "@types/express-serve-static-core@*": - version "4.16.7" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.16.7.tgz#50ba6f8a691c08a3dd9fa7fba25ef3133d298049" - integrity sha512-847KvL8Q1y3TtFLRTXcVakErLJQgdpFSaq+k043xefz9raEf0C7HalpSY7OW5PyjCnY8P7bPW5t/Co9qqp+USg== + version "4.17.9" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.9.tgz#2d7b34dcfd25ec663c25c85d76608f8b249667f1" + integrity sha512-DG0BYg6yO+ePW+XoDENYz8zhNGC3jDDEpComMYn7WJc4mY1Us8Rw9ax2YhJXxpyk2SF47PQAoQ0YyVT1a0bEkA== dependencies: "@types/node" "*" + "@types/qs" "*" "@types/range-parser" "*" "@types/express@*", "@types/express@^4.17.0": - version "4.17.0" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.0.tgz#49eaedb209582a86f12ed9b725160f12d04ef287" - integrity sha512-CjaMu57cjgjuZbh9DpkloeGxV45CnMGlVd+XpG7Gm9QgVrd7KFq+X4HY0vM+2v0bczS48Wg7bvnMY5TN+Xmcfw== + version "4.17.7" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.7.tgz#42045be6475636d9801369cd4418ef65cdb0dd59" + integrity sha512-dCOT5lcmV/uC2J9k0rPafATeeyz+99xTt54ReX11/LObZgfzJqZNcW27zGhYyX+9iSEGXGt5qLPwRSvBZcLvtQ== dependencies: "@types/body-parser" "*" "@types/express-serve-static-core" "*" + "@types/qs" "*" "@types/serve-static" "*" "@types/ioredis@^4.0.17": - version "4.0.17" - resolved "https://registry.yarnpkg.com/@types/ioredis/-/ioredis-4.0.17.tgz#d677459f078c46acc77a4a3249fb612fca19a24b" - integrity sha512-Lq/lG64wTc6A3uu4tj8zKtVHZw2GPLIJgmWweMbMLwwIx34KycyyQJtbeUZBOtD4a8K5RCPr1kWau0x81rKQNw== + version "4.17.2" + resolved "https://registry.yarnpkg.com/@types/ioredis/-/ioredis-4.17.2.tgz#2a9b0aeba5cf8d5f38afdeb85ef77ec95040d80c" + integrity sha512-T0sEKyqkhr4/RfgM2iTtmy0uPI4QZ9c0syq3mmAPNS5ZZMzjdtKv1ziuTdyNUvh0mZihXfKcRcWZI2wRYnxO7Q== dependencies: "@types/node" "*" +"@types/json-schema@^7.0.3": + version "7.0.5" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.5.tgz#dcce4430e64b443ba8945f0290fb564ad5bac6dd" + integrity sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ== + "@types/jsonwebtoken@*": - version "8.3.2" - resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-8.3.2.tgz#e3d5245197152346fae7ee87d5541aa5a92d0362" - integrity sha512-Mkjljd9DTpkPlrmGfTJvcP4aBU7yO2QmW7wNVhV4/6AEUxYoacqU7FJU/N0yFEHTsIrE4da3rUrjrR5ejicFmA== + version "8.5.0" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-8.5.0.tgz#2531d5e300803aa63279b232c014acf780c981c5" + integrity sha512-9bVao7LvyorRGZCw0VmH/dr7Og+NdjYSsKAxB43OQoComFbBgsEpoR9JW6+qSq/ogwVBg8GI2MfAlk4SYI4OLg== dependencies: "@types/node" "*" "@types/lodash@^4.14.144": - version "4.14.144" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.144.tgz#12e57fc99064bce45e5ab3c8bc4783feb75eab8e" - integrity sha512-ogI4g9W5qIQQUhXAclq6zhqgqNUr7UlFaqDHbch7WLSLeeM/7d3CRaw7GLajxvyFvhJqw4Rpcz5bhoaYtIx6Tg== + version "4.14.158" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.158.tgz#b38ea8b6fe799acd076d7a8d7ab71c26ef77f785" + integrity sha512-InCEXJNTv/59yO4VSfuvNrZHt7eeNtWQEgnieIA+mIC+MOWM9arOWG2eQ8Vhk6NbOre6/BidiXhkZYeDY9U35w== "@types/md5@^2.1.33": - version "2.1.33" - resolved "https://registry.yarnpkg.com/@types/md5/-/md5-2.1.33.tgz#8c8dba30df4ad0e92296424f08c4898dd808e8df" - integrity sha512-8+X960EtKLoSblhauxLKy3zzotagjoj3Jt1Tx9oaxUdZEPIBl+mkrUz6PNKpzJgkrKSN9YgkWTA29c0KnLshmA== + version "2.2.0" + resolved "https://registry.yarnpkg.com/@types/md5/-/md5-2.2.0.tgz#cd82e16b95973f94bb03dee40c5b6be4a7fb7fb4" + integrity sha512-JN8OVL/wiDlCWTPzplsgMPu0uE9Q6blwp68rYsfk2G8aokRUQ8XD9MEhZwihfAiQvoyE+m31m6i3GFXwYWomKQ== dependencies: "@types/node" "*" "@types/mime@*": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d" - integrity sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw== + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.3.tgz#c893b73721db73699943bfc3653b1deb7faa4a3a" + integrity sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q== "@types/mongodb@*": - version "3.2.3" - resolved "https://registry.yarnpkg.com/@types/mongodb/-/mongodb-3.2.3.tgz#c26bbabc7930f8b352c311ce6f214882c815a472" - integrity sha512-46ai7KWL5/Ls/x+gzU908hhpkXzB2SlrAh/07wHkMkGUdpRwYjbjxb8/Yq2WEZ33Sn5s/o9BCO+OkBTCS6Cfmw== + version "3.5.25" + resolved "https://registry.yarnpkg.com/@types/mongodb/-/mongodb-3.5.25.tgz#ab187db04d79f8e3f15af236327dc9139d9d4736" + integrity sha512-2H/Owt+pHCl9YmBOYnXc3VdnxejJEjVdH+QCWL5ZAfPehEn3evygKBX3/vKRv7aTwfNbUd0E5vjJdQklH/9a6w== dependencies: "@types/bson" "*" "@types/node" "*" "@types/mongoose@^5.5.12": - version "5.5.12" - resolved "https://registry.yarnpkg.com/@types/mongoose/-/mongoose-5.5.12.tgz#030eb1401e5a449ff95f7b208ee201b44a0c9479" - integrity sha512-41TDORylr6M9Kytli/Qry28cQjD919+w+jHNR9rca95xH1/joNLUkLg9okKJ1I4mIR6Af28ESjBssed7RKElVA== + version "5.7.32" + resolved "https://registry.yarnpkg.com/@types/mongoose/-/mongoose-5.7.32.tgz#66265946d07a3418626843e7e93a00995675adb2" + integrity sha512-o1qijffQipTtYMJEYF8BOd+D8fy6ZGtGKP654udSEp6wysU3r1O2T8wHSP9QIC//QwQgKQGolu2y9vc9KXaq4w== dependencies: "@types/mongodb" "*" "@types/node" "*" "@types/morgan@^1.7.36": - version "1.7.36" - resolved "https://registry.yarnpkg.com/@types/morgan/-/morgan-1.7.36.tgz#75b2bbf85cb33233a812ceff997efa08b2af13a1" - integrity sha512-Hc2UfTpnqS3gfGZFPk6aaQf/nwxFHboC/o1O25W29UsENPLv8qd/GJUBqzrBuczgaIS3/vZxZRHTfFF28uFNeQ== + version "1.9.1" + resolved "https://registry.yarnpkg.com/@types/morgan/-/morgan-1.9.1.tgz#6457872df95647c1dbc6b3741e8146b71ece74bf" + integrity sha512-2j5IKrgJpEP6xw/uiVb2Xfga0W0sSVD9JP9t7EZLvpBENdB0OKgcnoKS8IsjNeNnZ/86robdZ61Orl0QCFGOXg== dependencies: - "@types/express" "*" + "@types/node" "*" "@types/node@*": - version "12.6.9" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.6.9.tgz#ffeee23afdc19ab16e979338e7b536fdebbbaeaf" - integrity sha512-+YB9FtyxXGyD54p8rXwWaN1EWEyar5L58GlGWgtH2I9rGmLGBQcw63+0jw+ujqVavNuO47S1ByAjm9zdHMnskw== + version "14.0.26" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.26.tgz#22a3b8a46510da8944b67bfc27df02c34a35331c" + integrity sha512-W+fpe5s91FBGE0pEa0lnqGLL4USgpLgs4nokw16SrBBco/gQxuua7KnArSEOd5iaMqbbSHV10vUDkJYJJqpXKA== "@types/oauth@*": version "0.9.1" @@ -166,18 +183,18 @@ "@types/passport-oauth2" "*" "@types/passport-jwt@^3.0.1": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/passport-jwt/-/passport-jwt-3.0.1.tgz#bc4c2610815565de977ea1a580c047d71c646084" - integrity sha512-JwF9U/Rmr6YicHSu/MITmHNDy2KeiedxKW2bhz6wZts3y4cq48NiN0UD98zO56TyM5Vm6BpyjFxcs6jh68ni/A== + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/passport-jwt/-/passport-jwt-3.0.3.tgz#47b6c43af668852d7c83e56a23ed9f4515dd1814" + integrity sha512-RlOCXiTitE8kazj9jZc6/BfGCSqnv2w/eYPDm3+3iNsquHn7ratu7oIUskZx9ZtnwMdpvdpy+Z/QYClocH5NvQ== dependencies: "@types/express" "*" "@types/jsonwebtoken" "*" "@types/passport-strategy" "*" "@types/passport-oauth2@*": - version "1.4.8" - resolved "https://registry.yarnpkg.com/@types/passport-oauth2/-/passport-oauth2-1.4.8.tgz#b11cb3323970a88db3d170fe3d4dcdd0af02d8bf" - integrity sha512-tlX16wyFE5YJR2pHpZ308dgB1MV9/Ra2wfQh71eWk+/umPoD1Rca2D4N5M27W7nZm1wqUNGTk1I864nHvEgiFA== + version "1.4.9" + resolved "https://registry.yarnpkg.com/@types/passport-oauth2/-/passport-oauth2-1.4.9.tgz#134007c4b505a82548c9cb19094c5baeb2205c92" + integrity sha512-QP0q+NVQOaIu2r0e10QWkiUA0Ya5mOBHRJN0UrI+LolMLOP1/VN4EVIpJ3xVwFo+xqNFRoFvFwJhBvKnk7kpUA== dependencies: "@types/express" "*" "@types/oauth" "*" @@ -192,33 +209,97 @@ "@types/passport" "*" "@types/passport@*", "@types/passport@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/passport/-/passport-1.0.0.tgz#747fa127a747a145ff279f3df3e07c425e5ff297" - integrity sha512-R2FXqM+AgsMIym0PuKj08Ybx+GR6d2rU3b1/8OcHolJ+4ga2pRPX105wboV6hq1AJvMo2frQzYKdqXS5+4cyMw== + version "1.0.4" + resolved "https://registry.yarnpkg.com/@types/passport/-/passport-1.0.4.tgz#1b35c4e197560d3974fa5f71711b6e9cce0711f0" + integrity sha512-h5OfAbfBBYSzjeU0GTuuqYEk9McTgWeGQql9g3gUw2/NNCfD7VgExVRYJVVeU13Twn202Mvk9BT0bUrl30sEgA== dependencies: "@types/express" "*" +"@types/qs@*": + version "6.9.4" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.4.tgz#a59e851c1ba16c0513ea123830dd639a0a15cb6a" + integrity sha512-+wYo+L6ZF6BMoEjtf8zB2esQsqdV6WsjRK/GP9WOgLPrq87PbNWgIxS76dS5uvl/QXtHGakZmwTznIfcPXcKlQ== + "@types/range-parser@*": version "1.2.3" resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c" integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA== "@types/serve-static@*": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.2.tgz#f5ac4d7a6420a99a6a45af4719f4dcd8cd907a48" - integrity sha512-/BZ4QRLpH/bNYgZgwhKEh+5AsboDBcUdlBYgzoLX0fpj3Y2gp6EApyOlM3bK53wQS/OE1SrdSYBAbux2D1528Q== + version "1.13.4" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.4.tgz#6662a93583e5a6cabca1b23592eb91e12fa80e7c" + integrity sha512-jTDt0o/YbpNwZbQmE/+2e+lfjJEJJR0I3OFaKQKPWkASkCoW3i6fsUnqudSMcNAfbtmADGu8f4MV4q+GqULmug== dependencies: "@types/express-serve-static-core" "*" "@types/mime" "*" "@types/ws@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-6.0.1.tgz#ca7a3f3756aa12f62a0a62145ed14c6db25d5a28" - integrity sha512-EzH8k1gyZ4xih/MaZTXwT2xOkPiIMSrhQ9b8wrlX88L0T02eYsddatQlwVFlEPyEqV0ChpdpNnE51QPH6NVT4Q== + version "6.0.4" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-6.0.4.tgz#7797707c8acce8f76d8c34b370d4645b70421ff1" + integrity sha512-PpPrX7SZW9re6+Ha8ojZG4Se8AZXgf0GK6zmfqEuCsY49LFDNXO3SByp44X3dFEqtB73lkCDAdUazhAjVPiNwg== dependencies: - "@types/events" "*" "@types/node" "*" +"@typescript-eslint/eslint-plugin@^3.1.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.7.0.tgz#0f91aa3c83d019591719e597fbdb73a59595a263" + integrity sha512-4OEcPON3QIx0ntsuiuFP/TkldmBGXf0uKxPQlGtS/W2F3ndYm8Vgdpj/woPJkzUc65gd3iR+qi3K8SDQP/obFg== + dependencies: + "@typescript-eslint/experimental-utils" "3.7.0" + debug "^4.1.1" + functional-red-black-tree "^1.0.1" + regexpp "^3.0.0" + semver "^7.3.2" + tsutils "^3.17.1" + +"@typescript-eslint/experimental-utils@3.7.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.7.0.tgz#0ee21f6c48b2b30c63211da23827725078d5169a" + integrity sha512-xpfXXAfZqhhqs5RPQBfAFrWDHoNxD5+sVB5A46TF58Bq1hRfVROrWHcQHHUM9aCBdy9+cwATcvCbRg8aIRbaHQ== + dependencies: + "@types/json-schema" "^7.0.3" + "@typescript-eslint/types" "3.7.0" + "@typescript-eslint/typescript-estree" "3.7.0" + eslint-scope "^5.0.0" + eslint-utils "^2.0.0" + +"@typescript-eslint/parser@^3.1.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-3.7.0.tgz#3e9cd9df9ea644536feb6e5acdb8279ecff96ce9" + integrity sha512-2LZauVUt7jAWkcIW7djUc3kyW+fSarNEuM3RF2JdLHR9BfX/nDEnyA4/uWz0wseoWVZbDXDF7iF9Jc342flNqQ== + dependencies: + "@types/eslint-visitor-keys" "^1.0.0" + "@typescript-eslint/experimental-utils" "3.7.0" + "@typescript-eslint/types" "3.7.0" + "@typescript-eslint/typescript-estree" "3.7.0" + eslint-visitor-keys "^1.1.0" + +"@typescript-eslint/types@3.7.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.7.0.tgz#09897fab0cb95479c01166b10b2c03c224821077" + integrity sha512-reCaK+hyKkKF+itoylAnLzFeNYAEktB0XVfSQvf0gcVgpz1l49Lt6Vo9x4MVCCxiDydA0iLAjTF/ODH0pbfnpg== + +"@typescript-eslint/typescript-estree@3.7.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.7.0.tgz#66872e6da120caa4b64e6b4ca5c8702afc74738d" + integrity sha512-xr5oobkYRebejlACGr1TJ0Z/r0a2/HUf0SXqPvlgUMwiMqOCu/J+/Dr9U3T0IxpE5oLFSkqMx1FE/dKaZ8KsOQ== + dependencies: + "@typescript-eslint/types" "3.7.0" + "@typescript-eslint/visitor-keys" "3.7.0" + debug "^4.1.1" + glob "^7.1.6" + is-glob "^4.0.1" + lodash "^4.17.15" + semver "^7.3.2" + tsutils "^3.17.1" + +"@typescript-eslint/visitor-keys@3.7.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-3.7.0.tgz#ac0417d382a136e4571a0b0dcfe52088cb628177" + integrity sha512-k5PiZdB4vklUpUX4NBncn5RBKty8G3ihTY+hqJsCdMuD0v4jofI5xuqwnVcWxfv6iTm2P/dfEa2wMUnsUY8ODw== + dependencies: + eslint-visitor-keys "^1.1.0" + accepts@~1.3.7: version "1.3.7" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" @@ -227,18 +308,61 @@ accepts@~1.3.7: mime-types "~2.1.24" negotiator "0.6.2" +acorn-jsx@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.2.0.tgz#4c66069173d6fdd68ed85239fc256226182b2ebe" + integrity sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ== + +acorn@^7.3.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.3.1.tgz#85010754db53c3fbaf3b9ea3e083aa5c5d147ffd" + integrity sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA== + +ajv@^6.10.0, ajv@^6.10.2: + version "6.12.3" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.3.tgz#18c5af38a111ddeb4f2697bd78d68abc1cabd706" + integrity sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-colors@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== + ansi-regex@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= -ansi-styles@^3.2.1: +ansi-regex@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" + integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + +ansi-regex@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" + integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== + +ansi-styles@^3.2.0, ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== dependencies: color-convert "^1.9.0" +ansi-styles@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" + integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== + dependencies: + "@types/color-name" "^1.1.1" + color-convert "^2.0.1" + argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -251,18 +375,22 @@ array-flatten@1.1.1: resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= +astral-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" + integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= axios@^0.19.0: - version "0.19.0" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.0.tgz#8e09bff3d9122e133f7b8101c8fbdd00ed3d2ab8" - integrity sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ== + version "0.19.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27" + integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA== dependencies: follow-redirects "1.5.10" - is-buffer "^2.0.2" balanced-match@^1.0.0: version "1.0.0" @@ -274,7 +402,7 @@ base64url@3.x.x: resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d" integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A== -basic-auth@~2.0.0: +basic-auth@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a" integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg== @@ -282,9 +410,17 @@ basic-auth@~2.0.0: safe-buffer "5.1.2" biguint-format@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/biguint-format/-/biguint-format-1.0.1.tgz#0ad9a81a48de96a79084c1a2fcc11bf27b80e284" - integrity sha512-EyEAXbBXdXHTmysr9bFjiTiFa7nDwew4N2qZ2Ka0aAM9oTidS5lNH3jVTBPFcaXyRCzLpw1cb3ATD+fzg89/fQ== + version "1.0.2" + resolved "https://registry.yarnpkg.com/biguint-format/-/biguint-format-1.0.2.tgz#21e8d6c8259a8c9b39874ba6a060a1a8ec137f30" + integrity sha512-A9/8NoHnszWRAp4WCrgwe70wMd0FVDB3pXTdKs78z3pgwHRhc/BGU3uO7Wmfp9ueWD5WpmncL1OpypGnvc1R3w== + +bl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-2.2.0.tgz#e1a574cdf528e4053019bb800b041c0ac88da493" + integrity sha512-wbgvOpqopSr7uq6fJrLH8EsvYMJf9gzfo2jCsL2eTy75qXPukA4pCgHamOQkZtY5vmfVtjB+P3LNlMHW5CEZXA== + dependencies: + readable-stream "^2.3.5" + safe-buffer "^5.1.1" bluebird@3.5.1: version "3.5.1" @@ -307,10 +443,10 @@ body-parser@1.19.0: raw-body "2.4.0" type-is "~1.6.17" -bowser@2.5.4: - version "2.5.4" - resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.5.4.tgz#850fccfebde92165440279b5ab19be3c7f05cfe1" - integrity sha512-74GGwfc2nzYD19JCiA0RwCxdq7IY5jHeEaSrrgm/5kusEuK+7UK0qDG3gyzN47c4ViNyO4osaKtZE+aSV6nlpQ== +bowser@2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.9.0.tgz#3bed854233b419b9a7422d9ee3e85504373821c9" + integrity sha512-2ld76tuLBNFekRgmJfT2+3j5MIrP6bFict8WAIT3beq+srz1gcKNAdNKMqHqauQt63NmAa88HfP1/Ypa9Er3HA== brace-expansion@^1.1.7: version "1.1.11" @@ -320,10 +456,10 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -bson@^1.1.1, bson@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/bson/-/bson-1.1.1.tgz#4330f5e99104c4e751e7351859e2d408279f2f13" - integrity sha512-jCGVYLoYMHDkOsbwJZBCqwMHyH4c+wzgI9hG7Z6SZJRXWr+x58pdIbm2i9a/jFGCkRJqRUr8eoI7lDWa0hTkxg== +bson@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/bson/-/bson-1.1.4.tgz#f76870d799f15b854dffb7ee32f0a874797f7e89" + integrity sha512-S/yKGU1syOMzO86+dGpg2qGoDL0zvzcb262G+gqEy6TgP6rt6z6qxSFX/8X6vLC91P7G7C3nLs0+bvDzmvBA3Q== buffer-equal-constant-time@1.0.1: version "1.0.1" @@ -340,6 +476,11 @@ bytes@3.1.0: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + camelize@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.0.tgz#164a5483e630fa4321e5af07020e531831b2609b" @@ -354,10 +495,18 @@ chalk@^2.0.0, chalk@^2.3.0: escape-string-regexp "^1.0.5" supports-color "^5.3.0" +chalk@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" + integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chance@^1.0.18: - version "1.0.18" - resolved "https://registry.yarnpkg.com/chance/-/chance-1.0.18.tgz#79788fe6fca4c338bf404321c347eecc80f969ee" - integrity sha512-g9YLQVHVZS/3F+zIicfB58vjcxopvYQRp7xHzvyDFDhXH1aRZI/JhwSAO0X5qYiQluoGnaNAU6wByD2KTxJN1A== + version "1.1.6" + resolved "https://registry.yarnpkg.com/chance/-/chance-1.1.6.tgz#967a0a129e0f342f7c65cd5d20f5ae870a26b8af" + integrity sha512-DXLzaGjasDWbvlFAJyQBIwlzdQZuPdz4of9TTTxmHTjja88ZU/vBwUwxxjalSt43zWTPrhiJT0z0N4bZqfZS9w== charenc@~0.0.1: version "0.0.2" @@ -376,11 +525,23 @@ color-convert@^1.9.0: dependencies: color-name "1.1.3" +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + color-name@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + combined-stream@^1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -457,6 +618,15 @@ cross-spawn@^5.1.0: shebang-command "^1.2.0" which "^1.2.9" +cross-spawn@^7.0.2: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + crypt@~0.0.1: version "0.0.2" resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" @@ -493,7 +663,7 @@ debug@^3.1.0: dependencies: ms "^2.1.1" -debug@^4.1.1: +debug@^4.0.1, debug@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== @@ -505,17 +675,22 @@ decode-uri-component@^0.2.0: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= +deep-is@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= -denque@^1.1.0: +denque@^1.1.0, denque@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/denque/-/denque-1.4.1.tgz#6744ff7641c148c3f8a69c307e51235c1f4a37cf" integrity sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ== -depd@2.0.0: +depd@2.0.0, depd@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== @@ -531,30 +706,27 @@ destroy@~1.0.4: integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= diff@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.1.tgz#0c667cb467ebbb5cea7f14f135cc2dba7780a8ff" - integrity sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q== + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== -dns-prefetch-control@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/dns-prefetch-control/-/dns-prefetch-control-0.2.0.tgz#73988161841f3dcc81f47686d539a2c702c88624" - integrity sha512-hvSnros73+qyZXhHFjx2CMLwoj3Fe7eR9EJsFsqmcI1bB2OBWL/+0YzaEaKssCHnj/6crawNnUyw74Gm2EKe+Q== +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" dont-sniff-mimetype@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/dont-sniff-mimetype/-/dont-sniff-mimetype-1.1.0.tgz#c7d0427f8bcb095762751252af59d148b0a623b2" integrity sha512-ZjI4zqTaxveH2/tTlzS1wFp+7ncxNZaIEWYg3lzZRHkKf5zPT/MnEG6WL0BhHMJUabkh8GeU5NL5j+rEUCb7Ug== -dotenv@*: +dotenv@*, dotenv@^8.0.0: version "8.2.0" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== -dotenv@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.0.0.tgz#ed310c165b4e8a97bb745b0a9d99c31bda566440" - integrity sha512-30xVGqjLjiUOArT4+M5q9sYdvuR4riM6yK9wMcas9Vbp6zZa+ocC9dp6QoftuhTPhFAiLK/0C5Ni2nou/Bk8lg== - duplexer@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" @@ -572,11 +744,23 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= +emoji-regex@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" + integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== + encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= +enquirer@^2.3.5: + version "2.3.6" + resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" + integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== + dependencies: + ansi-colors "^4.1.1" + escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -587,11 +771,106 @@ escape-string-regexp@^1.0.5: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= +eslint-scope@^5.0.0, eslint-scope@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.0.tgz#d0f971dfe59c69e0cada684b23d49dbf82600ce5" + integrity sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w== + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + +eslint-utils@^2.0.0, eslint-utils@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" + integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== + dependencies: + eslint-visitor-keys "^1.1.0" + +eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" + integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== + +eslint@^7.1.0: + version "7.5.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.5.0.tgz#9ecbfad62216d223b82ac9ffea7ef3444671d135" + integrity sha512-vlUP10xse9sWt9SGRtcr1LAC67BENcQMFeV+w5EvLEoFe3xJ8cF1Skd0msziRx/VMC+72B4DxreCE+OR12OA6Q== + dependencies: + "@babel/code-frame" "^7.0.0" + ajv "^6.10.0" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.0.1" + doctrine "^3.0.0" + enquirer "^2.3.5" + eslint-scope "^5.1.0" + eslint-utils "^2.1.0" + eslint-visitor-keys "^1.3.0" + espree "^7.2.0" + esquery "^1.2.0" + esutils "^2.0.2" + file-entry-cache "^5.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^5.0.0" + globals "^12.1.0" + ignore "^4.0.6" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + js-yaml "^3.13.1" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash "^4.17.19" + minimatch "^3.0.4" + natural-compare "^1.4.0" + optionator "^0.9.1" + progress "^2.0.0" + regexpp "^3.1.0" + semver "^7.2.1" + strip-ansi "^6.0.0" + strip-json-comments "^3.1.0" + table "^5.2.3" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + +espree@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-7.2.0.tgz#1c263d5b513dbad0ac30c4991b93ac354e948d69" + integrity sha512-H+cQ3+3JYRMEIOl87e7QdHX70ocly5iW4+dttuR8iYSPr/hXKFb+7dBsZ7+u1adC4VrnPlTkv0+OwuPnDop19g== + dependencies: + acorn "^7.3.1" + acorn-jsx "^5.2.0" + eslint-visitor-keys "^1.3.0" + esprima@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== +esquery@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57" + integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf" + integrity sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ== + dependencies: + estraverse "^4.1.0" + +estraverse@^4.1.0, estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.1.0.tgz#374309d39fd935ae500e7b92e8a6b4c720e59642" + integrity sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw== + esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" @@ -615,11 +894,6 @@ event-stream@=3.3.4: stream-combiner "~0.0.4" through "~2.3.1" -expect-ct@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/expect-ct/-/expect-ct-0.2.0.tgz#3a54741b6ed34cc7a93305c605f63cd268a54a62" - integrity sha512-6SK3MG/Bbhm8MsgyJAylg+ucIOU71/FzyFalcfu5nY19dH8y/z0tBJU0wrNBXD4B27EoQtqPF/9wqH0iYAd04g== - express@^4.17.1: version "4.17.1" resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" @@ -661,11 +935,33 @@ extend@^3.0.0: resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + feature-policy@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/feature-policy/-/feature-policy-0.3.0.tgz#7430e8e54a40da01156ca30aaec1a381ce536069" integrity sha512-ZtijOTFN7TzCujt1fnNhfWPFPSHeZkesff9AXZj+UEjYBynWNUIYpC87Ve4wHzyexQsImicLu7WsC2LHq7/xrQ== +file-entry-cache@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" + integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== + dependencies: + flat-cache "^2.0.1" + finalhandler@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" @@ -680,9 +976,23 @@ finalhandler@~1.1.2: unpipe "~1.0.0" flake-idgen@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/flake-idgen/-/flake-idgen-1.1.2.tgz#ada07c42359a3bed0bbae3786810355202412b2c" - integrity sha512-VNhbzA/dUfWNn8Vko4iWRFonl8bdyNFkU11936KCsjyesa8ScDuYSr7LlC8XCAZ7TfEnhwpbDjPMOW+kvdZCnw== + version "1.4.0" + resolved "https://registry.yarnpkg.com/flake-idgen/-/flake-idgen-1.4.0.tgz#a89db0f987d83e33b88bf9a8ac1a26d0c0b03b63" + integrity sha512-l+dtBcIAwAPqmtDelZAAw0NzKOBosiu/3hAFH69W3n1rH8wkKN1hUp6SaR1tmH/NmzaCUQs1BFKULKVhqGWdMw== + +flat-cache@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" + integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== + dependencies: + flatted "^2.0.0" + rimraf "2.6.3" + write "1.0.3" + +flatted@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" + integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== follow-redirects@1.5.10: version "1.5.10" @@ -692,29 +1002,24 @@ follow-redirects@1.5.10: debug "=3.1.0" form-data@^2.3.1: - version "2.5.0" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.0.tgz#094ec359dc4b55e7d62e0db4acd76e89fe874d37" - integrity sha512-WXieX3G/8side6VIqx44ablyULoGruSde5PNTxoUyo5CeyAMX6nVWUd0rgist/EuX655cjhUhTo1Fo3tRYqbcA== + version "2.5.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" + integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA== dependencies: asynckit "^0.4.0" combined-stream "^1.0.6" mime-types "^2.1.12" formidable@^1.2.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.1.tgz#70fb7ca0290ee6ff961090415f4b3df3d2082659" - integrity sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg== + version "1.2.2" + resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.2.tgz#bf69aea2972982675f00865342b982986f6b8dd9" + integrity sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q== forwarded@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= -frameguard@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/frameguard/-/frameguard-3.1.0.tgz#bd1442cca1d67dc346a6751559b6d04502103a22" - integrity sha512-TxgSKM+7LTA6sidjOiSZK9wxY0ffMPY3Wta//MqwmX0nZuEHc8QrkV8Fh3ZhMJeiH+Uyh/tcaarImRy8u77O7g== - fresh@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" @@ -730,7 +1035,19 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -glob@^7.1.1: +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= + +glob-parent@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" + integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== + dependencies: + is-glob "^4.0.1" + +glob@^7.1.1, glob@^7.1.3, glob@^7.1.6: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== @@ -742,43 +1059,51 @@ glob@^7.1.1: once "^1.3.0" path-is-absolute "^1.0.0" +globals@^12.1.0: + version "12.4.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" + integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== + dependencies: + type-fest "^0.8.1" + has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + helmet-crossdomain@0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/helmet-crossdomain/-/helmet-crossdomain-0.4.0.tgz#5f1fe5a836d0325f1da0a78eaa5fd8429078894e" integrity sha512-AB4DTykRw3HCOxovD1nPR16hllrVImeFp5VBV9/twj66lJ2nU75DP8FPL0/Jp4jj79JhTfG+pFI2MD02kWJ+fA== -helmet-csp@2.9.1: - version "2.9.1" - resolved "https://registry.yarnpkg.com/helmet-csp/-/helmet-csp-2.9.1.tgz#39939a84ca3657ee3cba96f296169ccab02f97d5" - integrity sha512-HgdXSJ6AVyXiy5ohVGpK6L7DhjI9KVdKVB1xRoixxYKsFXFwoVqtLKgDnfe3u8FGGKf9Ml9k//C9rnncIIAmyA== +helmet-csp@2.10.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/helmet-csp/-/helmet-csp-2.10.0.tgz#685dde1747bc16c5e28ad9d91e229a69f0a85e84" + integrity sha512-Rz953ZNEFk8sT2XvewXkYN0Ho4GEZdjAZy4stjiEQV3eN7GDxg1QKmYggH7otDyIA7uGA6XnUMVSgeJwbR5X+w== dependencies: - bowser "2.5.4" + bowser "2.9.0" camelize "1.0.0" content-security-policy-builder "2.1.0" dasherize "2.0.0" helmet@^3.21.0: - version "3.21.0" - resolved "https://registry.yarnpkg.com/helmet/-/helmet-3.21.0.tgz#e7c5e2ed3b8b7f42d2e387004a87198b295132cc" - integrity sha512-TS3GryQMPR7n/heNnGC0Cl3Ess30g8C6EtqZyylf+Y2/kF4lM8JinOR90rzIICsw4ymWTvji4OhDmqsqxkLrcg== + version "3.23.3" + resolved "https://registry.yarnpkg.com/helmet/-/helmet-3.23.3.tgz#5ba30209c5f73ded4ab65746a3a11bedd4579ab7" + integrity sha512-U3MeYdzPJQhtvqAVBPntVgAvNSOJyagwZwyKsFdyRa8TV3pOKVFljalPOCxbw5Wwf2kncGhmP0qHjyazIdNdSA== dependencies: depd "2.0.0" - dns-prefetch-control "0.2.0" dont-sniff-mimetype "1.1.0" - expect-ct "0.2.0" feature-policy "0.3.0" - frameguard "3.1.0" helmet-crossdomain "0.4.0" - helmet-csp "2.9.1" + helmet-csp "2.10.0" hide-powered-by "1.1.0" hpkp "2.0.0" hsts "2.2.0" - ienoopen "1.1.0" nocache "2.1.0" referrer-policy "1.2.0" x-xss-protection "1.3.0" @@ -829,10 +1154,23 @@ iconv-lite@0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" -ienoopen@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/ienoopen/-/ienoopen-1.1.0.tgz#411e5d530c982287dbdc3bb31e7a9c9e32630974" - integrity sha512-MFs36e/ca6ohEKtinTJ5VvAJ6oDRAYFdYXweUnGY9L9vcoqFOU4n2ZhmJ0C4z/cwGZ3YIQRSB3XZ1+ghZkY5NQ== +ignore@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== + +import-fresh@^3.0.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" + integrity sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= inflight@^1.0.4: version "1.0.6" @@ -853,9 +1191,9 @@ inherits@2.0.3: integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= ioredis@^4.14.1: - version "4.14.1" - resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.14.1.tgz#b73ded95fcf220f106d33125a92ef6213aa31318" - integrity sha512-94W+X//GHM+1GJvDk6JPc+8qlM7Dul+9K+lg3/aHixPN7ZGkW6qlvX0DG6At9hWtH2v3B32myfZqWoANUJYGJA== + version "4.17.3" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.17.3.tgz#9938c60e4ca685f75326337177bdc2e73ae9c9dc" + integrity sha512-iRvq4BOYzNFkDnSyhx7cmJNOi1x/HWYe+A4VXHBu4qpwJaGT1Mp+D2bVGJntH9K/Z/GeOM/Nprb8gB3bmitz1Q== dependencies: cluster-key-slot "^1.1.0" debug "^4.1.1" @@ -867,21 +1205,33 @@ ioredis@^4.14.1: redis-parser "^3.0.0" standard-as-callback "^2.0.1" -ipaddr.js@1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.0.tgz#37df74e430a0e47550fe54a2defe30d8acd95f65" - integrity sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA== - -is-buffer@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.3.tgz#4ecf3fcf749cbd1e472689e109ac66261a25e725" - integrity sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw== +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== is-buffer@~1.1.1: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +is-glob@^4.0.0, is-glob@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -898,13 +1248,23 @@ js-tokens@^4.0.0: integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== js-yaml@^3.13.1: - version "3.13.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" - integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== + version "3.14.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" + integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== dependencies: argparse "^1.0.7" esprima "^4.0.0" +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= + jsonwebtoken@^8.2.0, jsonwebtoken@^8.5.1: version "8.5.1" resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" @@ -943,6 +1303,14 @@ kareem@2.3.1: resolved "https://registry.yarnpkg.com/kareem/-/kareem-2.3.1.tgz#def12d9c941017fabfb00f873af95e9c99e1be87" integrity sha512-l3hLhffs9zqoDe8zjmb/mAN4B8VT3L56EUvKNqLFVs9YlFA+zx7ke1DO8STAdDyYNkeSo1nKmjuvQeI12So8Xw== +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + lodash.defaults@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" @@ -988,10 +1356,10 @@ lodash.once@^4.0.0: resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= -lodash@^4.17.15: - version "4.17.15" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" - integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== +lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19: + version "4.17.19" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" + integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== lru-cache@^4.0.1: version "4.1.5" @@ -1020,6 +1388,11 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= +memory-pager@^1.0.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/memory-pager/-/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5" + integrity sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg== + merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" @@ -1030,17 +1403,17 @@ methods@^1.1.1, methods@^1.1.2, methods@~1.1.2: resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= -mime-db@1.40.0: - version "1.40.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32" - integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA== +mime-db@1.44.0: + version "1.44.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" + integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== mime-types@^2.1.12, mime-types@~2.1.24: - version "2.1.24" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81" - integrity sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ== + version "2.1.27" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" + integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== dependencies: - mime-db "1.40.0" + mime-db "1.44.0" mime@1.6.0, mime@^1.4.1: version "1.6.0" @@ -1054,26 +1427,30 @@ minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" -minimist@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" - integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= +minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== mkdirp@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" - integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= + version "0.5.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== dependencies: - minimist "0.0.8" + minimist "^1.2.5" -mongodb@3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-3.3.2.tgz#ff086b5f552cf07e24ce098694210f3d42d668b2" - integrity sha512-fqJt3iywelk4yKu/lfwQg163Bjpo5zDKhXiohycvon4iQHbrfflSAz9AIlRE6496Pm/dQKQK5bMigdVo2s6gBg== +mongodb@3.5.9: + version "3.5.9" + resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-3.5.9.tgz#799b72be8110b7e71a882bb7ce0d84d05429f772" + integrity sha512-vXHBY1CsGYcEPoVWhwgxIBeWqP3dSu9RuRDsoLRPTITrcrgm1f0Ubu1xqF9ozMwv53agmEiZm0YGo+7WL3Nbug== dependencies: - bson "^1.1.1" + bl "^2.2.0" + bson "^1.1.4" + denque "^1.4.1" require_optional "^1.0.1" safe-buffer "^5.1.2" + optionalDependencies: + saslprep "^1.0.0" mongoose-legacy-pluralize@1.0.2: version "1.0.2" @@ -1081,37 +1458,37 @@ mongoose-legacy-pluralize@1.0.2: integrity sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ== mongoose@^5.7.5: - version "5.7.5" - resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-5.7.5.tgz#b787b47216edf62036aa358c3ef0f1869c46cdc2" - integrity sha512-BZ4FxtnbTurc/wcm/hLltLdI4IDxo4nsE0D9q58YymTdZwreNzwO62CcjVtaHhmr8HmJtOInp2W/T12FZaMf8g== + version "5.9.25" + resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-5.9.25.tgz#620da737ec9a667f84404ad4f35bb60338dd0b4b" + integrity sha512-vz/DqJ3mrHqEIlfRbKmDZ9TzQ1a0hCtSQpjHScIxr4rEtLs0tjsXDeEWcJ/vEEc3oLfP6vRx9V+uYSprXDUvFQ== dependencies: - bson "~1.1.1" + bson "^1.1.4" kareem "2.3.1" - mongodb "3.3.2" + mongodb "3.5.9" mongoose-legacy-pluralize "1.0.2" - mpath "0.6.0" + mpath "0.7.0" mquery "3.2.2" ms "2.1.2" regexp-clone "1.0.0" - safe-buffer "5.1.2" + safe-buffer "5.2.1" sift "7.0.1" sliced "1.0.1" morgan@^1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.9.1.tgz#0a8d16734a1d9afbc824b99df87e738e58e2da59" - integrity sha512-HQStPIV4y3afTiCYVxirakhlCfGkI161c76kKFca7Fk1JusM//Qeo1ej2XaMniiNeaZklMVrh3vTtIzpzwbpmA== + version "1.10.0" + resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.10.0.tgz#091778abc1fc47cd3509824653dae1faab6b17d7" + integrity sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ== dependencies: - basic-auth "~2.0.0" + basic-auth "~2.0.1" debug "2.6.9" - depd "~1.1.2" + depd "~2.0.0" on-finished "~2.3.0" - on-headers "~1.0.1" + on-headers "~1.0.2" -mpath@0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/mpath/-/mpath-0.6.0.tgz#aa922029fca4f0f641f360e74c5c1b6a4c47078e" - integrity sha512-i75qh79MJ5Xo/sbhxrDrPSEG0H/mr1kcZXJ8dH6URU5jD/knFxCVqVC/gVSW7GIXL/9hHWlT9haLbCXWOll3qw== +mpath@0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/mpath/-/mpath-0.7.0.tgz#20e8102e276b71709d6e07e9f8d4d0f641afbfb8" + integrity sha512-Aiq04hILxhz1L+f7sjGyn7IxYzWm1zLNNXcfhDtx04kZ2Gk7uvFdgZ8ts1cWa/6d0TQmag2yR8zSGZUmp0tFNg== mquery@3.2.2: version "3.2.2" @@ -1139,6 +1516,11 @@ ms@2.1.2, ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + negotiator@0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" @@ -1171,7 +1553,7 @@ on-finished@~2.3.0: dependencies: ee-first "1.1.1" -on-headers@~1.0.1: +on-headers@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== @@ -1183,17 +1565,36 @@ once@^1.3.0: dependencies: wrappy "1" +optionator@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.3" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== passport-discord@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/passport-discord/-/passport-discord-0.1.3.tgz#669cc4a770b592f57eb17002ca1743a22e8d7c38" - integrity sha512-o8+KiQrV63DCJvYVceyVJgyFhHxqOPlcxaiB8Hlrrm0uq83KfQOMPUcXal0pao5Qx8ZeOH/RBJEbJV9aB0F/hw== + version "0.1.4" + resolved "https://registry.yarnpkg.com/passport-discord/-/passport-discord-0.1.4.tgz#9265be11952cdd54d77c47eaae352834444cf0f6" + integrity sha512-VJWPYqSOmh7SaCLw/C+k1ZqCzJnn2frrmQRx1YrcPJ3MQ+Oa31XclbbmqFICSvl8xv3Fqd6YWQ4H4p1MpIN9rA== dependencies: - passport-oauth2 "^1.2.0" + passport-oauth2 "^1.5.0" passport-jwt@^4.0.0: version "4.0.0" @@ -1203,7 +1604,7 @@ passport-jwt@^4.0.0: jsonwebtoken "^8.2.0" passport-strategy "^1.0.0" -passport-oauth2@^1.2.0: +passport-oauth2@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.5.0.tgz#64babbb54ac46a4dcab35e7f266ed5294e3c4108" integrity sha512-kqBt6vR/5VlCK8iCx1/KpY42kQ+NEHZwsSyt4Y6STiNjU+wWICG1i8ucc1FapXDGO15C5O5VZz7+7vRzrDPXXQ== @@ -1220,9 +1621,9 @@ passport-strategy@1.x.x, passport-strategy@^1.0.0: integrity sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ= passport@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/passport/-/passport-0.4.0.tgz#c5095691347bd5ad3b5e180238c3914d16f05811" - integrity sha1-xQlWkTR71a07XhgCOMORTRbwWBE= + version "0.4.1" + resolved "https://registry.yarnpkg.com/passport/-/passport-0.4.1.tgz#941446a21cb92fc688d97a0861c38ce9f738f270" + integrity sha512-IxXgZZs8d7uFSt3eqNjM9NQ3g3uQCW5avD8mRNoXV99Yig50vjuaez6dQK2qC0kVWPRTujxY0dWgGfT09adjYg== dependencies: passport-strategy "1.x.x" pause "0.0.1" @@ -1232,6 +1633,11 @@ path-is-absolute@^1.0.0: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + path-parse@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" @@ -1254,18 +1660,28 @@ pause@0.0.1: resolved "https://registry.yarnpkg.com/pause/-/pause-0.0.1.tgz#1d408b3fdb76923b9543d96fb4c9dfd535d9cb5d" integrity sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10= +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== +progress@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + proxy-addr@~2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.5.tgz#34cbd64a2d81f4b1fd21e76f9f06c8a45299ee34" - integrity sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ== + version "2.0.6" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" + integrity sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw== dependencies: forwarded "~0.1.2" - ipaddr.js "1.9.0" + ipaddr.js "1.9.1" ps-tree@^1.2.0: version "1.2.0" @@ -1279,20 +1695,25 @@ pseudomap@^1.0.2: resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= -qs@6.7.0, qs@^6.5.1: +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +qs@6.7.0: version "6.7.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== -qs@^6.8.0: - version "6.8.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.8.0.tgz#87b763f0d37ca54200334cd57bb2ef8f68a1d081" - integrity sha512-tPSkj8y92PfZVbinY1n84i1Qdx75lZjMQYx9WZhnkofyxzw2r7Ho39G3/aEvSUdebxpnnM4LZJCtvE/Aq3+s9w== +qs@^6.5.1, qs@^6.8.0: + version "6.9.4" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.4.tgz#9090b290d1f91728d3c22e54843ca44aea5ab687" + integrity sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ== query-string@^6.8.2: - version "6.8.2" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.8.2.tgz#36cb7e452ae11a4b5e9efee83375e0954407b2f6" - integrity sha512-J3Qi8XZJXh93t2FiKyd/7Ec6GNifsjKXUsVFkSBj/kjLsDylWhnCz4NT1bkPcKotttPW+QbKGqqPH8OoI2pdqw== + version "6.13.1" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.13.1.tgz#d913ccfce3b4b3a713989fe6d39466d92e71ccad" + integrity sha512-RfoButmcK+yCta1+FuU8REvisx1oEzhMKwhLUNcepQTPGcNMp1sIqjnfCtfnvGSQZQEhaBHvccujtWoUV3TTbA== dependencies: decode-uri-component "^0.2.0" split-on-first "^1.0.0" @@ -1314,9 +1735,9 @@ raw-body@2.4.0: unpipe "1.0.0" readable-stream@^2.3.5: - version "2.3.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" - integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== dependencies: core-util-is "~1.0.0" inherits "~2.0.3" @@ -1353,6 +1774,11 @@ regexp-clone@1.0.0, regexp-clone@^1.0.0: resolved "https://registry.yarnpkg.com/regexp-clone/-/regexp-clone-1.0.0.tgz#222db967623277056260b992626354a04ce9bf63" integrity sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw== +regexpp@^3.0.0, regexpp@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2" + integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== + require_optional@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/require_optional/-/require_optional-1.0.1.tgz#4cf35a4247f64ca3df8c2ef208cc494b1ca8fc2e" @@ -1366,38 +1792,57 @@ resolve-from@^2.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-2.0.0.tgz#9480ab20e94ffa1d9e80a804c7ea147611966b57" integrity sha1-lICrIOlP+h2egKgEx+oUdhGWa1c= +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + resolve@^1.3.2: - version "1.14.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.14.1.tgz#9e018c540fcf0c427d678b9931cbf45e984bcaff" - integrity sha512-fn5Wobh4cxbLzuHaE+nphztHy43/b++4M6SsGFC2gB8uYwf0C8LcarfCz1un7UTW8OFQg9iNjZ4xpcFVGebDPg== + version "1.17.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" + integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== dependencies: path-parse "^1.0.6" +rimraf@2.6.3: + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== + dependencies: + glob "^7.1.3" + safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@^5.0.1, safe-buffer@^5.1.2: - version "5.2.0" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" - integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== +safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== "safer-buffer@>= 2.1.2 < 3": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -semver@^5.1.0: - version "5.7.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b" - integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA== +saslprep@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/saslprep/-/saslprep-1.0.3.tgz#4c02f946b56cf54297e347ba1093e7acac4cf226" + integrity sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag== + dependencies: + sparse-bitfield "^3.0.3" -semver@^5.3.0, semver@^5.6.0: +semver@^5.1.0, semver@^5.3.0, semver@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== +semver@^7.2.1, semver@^7.3.2: + version "7.3.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" + integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== + send@0.17.1: version "0.17.1" resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" @@ -1439,21 +1884,49 @@ shebang-command@^1.2.0: dependencies: shebang-regex "^1.0.0" +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + shebang-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + sift@7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/sift/-/sift-7.0.1.tgz#47d62c50b159d316f1372f8b53f9c10cd21a4b08" integrity sha512-oqD7PMJ+uO6jV9EQCl0LrRw1OwsiPsiFQR5AR30heR+4Dl7jBBbDLnNvWiak20tzZlSE1H7RB30SX/1j/YYT7g== +slice-ansi@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" + integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== + dependencies: + ansi-styles "^3.2.0" + astral-regex "^1.0.0" + is-fullwidth-code-point "^2.0.0" + sliced@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/sliced/-/sliced-1.0.1.tgz#0b3a662b5d04c3177b1926bea82b03f837a2ef41" integrity sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E= +sparse-bitfield@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz#ff4ae6e68656056ba4b3e792ab3334d38273ca11" + integrity sha1-/0rm5oZWBWuks+eSqzM004JzyhE= + dependencies: + memory-pager "^1.0.2" + split-on-first@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" @@ -1498,6 +1971,15 @@ string-argv@^0.1.1: resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.1.2.tgz#c5b7bc03fb2b11983ba3a72333dd0559e77e4738" integrity sha512-mBqPGEOMNJKXRo7z0keX0wlAhbBAjilUdPW13nN0PecVryZxdHIeM7TqbsSUA7VYuS00HGC6mojP7DlQzfa9ZA== +string-width@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" + integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== + dependencies: + emoji-regex "^7.0.1" + is-fullwidth-code-point "^2.0.0" + strip-ansi "^5.1.0" + string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" @@ -1512,6 +1994,25 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" +strip-ansi@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + +strip-ansi@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" + integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== + dependencies: + ansi-regex "^5.0.0" + +strip-json-comments@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + superagent@^3.8.3: version "3.8.3" resolved "https://registry.yarnpkg.com/superagent/-/superagent-3.8.3.tgz#460ea0dbdb7d5b11bc4f78deba565f86a178e128" @@ -1543,6 +2044,28 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" +supports-color@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" + integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== + dependencies: + has-flag "^4.0.0" + +table@^5.2.3: + version "5.4.6" + resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" + integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== + dependencies: + ajv "^6.10.2" + lodash "^4.17.14" + slice-ansi "^2.1.0" + string-width "^3.0.0" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= + through@2, through@~2.3, through@~2.3.1: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" @@ -1554,9 +2077,9 @@ toidentifier@1.0.0: integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== tsc-watch@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/tsc-watch/-/tsc-watch-2.2.1.tgz#6e41a091e07d26dbcf5c815bfb514435ded33286" - integrity sha512-E61+ozEutLDgmXN+1+rkoCaUmd2g/cpa4mpEPMA3gPi89PBJiAcIUtH0xdCxeChXlR9TcuwOuTqu5jZDRhgfRw== + version "2.4.0" + resolved "https://registry.yarnpkg.com/tsc-watch/-/tsc-watch-2.4.0.tgz#0b3b9d98d8032ba02acae1d38a7ebb16da0591f7" + integrity sha512-HTNRQm/P2YdP34b7bhaSOjVQ/YdTJGkNYyAD0scEcK7qXX7BWsBG8n3YlYHC9a6mX//gs5ouWQ3DXrBWjVp6rQ== dependencies: cross-spawn "^5.1.0" node-cleanup "^2.1.2" @@ -1565,9 +2088,9 @@ tsc-watch@^2.2.1: strip-ansi "^4.0.0" tslib@^1.8.0, tslib@^1.8.1: - version "1.10.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" - integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== + version "1.13.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" + integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q== tslint@^5.20.1: version "5.20.1" @@ -1595,6 +2118,25 @@ tsutils@^2.29.0: dependencies: tslib "^1.8.1" +tsutils@^3.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" + integrity sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g== + dependencies: + tslib "^1.8.1" + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-fest@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + type-is@~1.6.17, type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" @@ -1604,9 +2146,9 @@ type-is@~1.6.17, type-is@~1.6.18: mime-types "~2.1.24" typescript@^3.5.3: - version "3.5.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977" - integrity sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g== + version "3.9.7" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa" + integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw== uid2@0.0.x: version "0.0.3" @@ -1618,6 +2160,13 @@ unpipe@1.0.0, unpipe@~1.0.0: resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= +uri-js@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" + integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== + dependencies: + punycode "^2.1.0" + util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -1628,6 +2177,11 @@ utils-merge@1.0.1, utils-merge@1.x.x: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= +v8-compile-cache@^2.0.3: + version "2.1.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745" + integrity sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ== + vary@^1, vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" @@ -1640,15 +2194,34 @@ which@^1.2.9: dependencies: isexe "^2.0.0" +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +word-wrap@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -ws@^7.2.1: - version "7.2.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.1.tgz#03ed52423cd744084b2cf42ed197c8b65a936b8e" - integrity sha512-sucePNSafamSKoOqoNfBd8V0StlkzJKL2ZAhGQinCfNQ+oacw+Pk7lcdAElecBF2VkLNZRiIb5Oi1Q5lVUVt2A== +write@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" + integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== + dependencies: + mkdirp "^0.5.1" + +ws@^7.1.1, ws@^7.2.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.1.tgz#d0547bf67f7ce4f12a72dfe31262c68d7dc551c8" + integrity sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA== x-xss-protection@1.3.0: version "1.3.0" From 5f4d2e31feb86e8bb5e947a930f52899c1614f7f Mon Sep 17 00:00:00 2001 From: William Gibson Date: Mon, 27 Jul 2020 00:17:06 +0100 Subject: [PATCH 8/8] ESLint fixes --- .eslintrc.js | 3 +++ src/server/routes.ts | 2 ++ 2 files changed, 5 insertions(+) diff --git a/.eslintrc.js b/.eslintrc.js index 6478ef5..3765691 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -25,6 +25,9 @@ module.exports = { 'error', 2 ], + 'no-async-promise-executor': [ + 'off' + ], '@typescript-eslint/no-explicit-any': [ 'off' ], diff --git a/src/server/routes.ts b/src/server/routes.ts index 8dd3f92..8f3bdc3 100644 --- a/src/server/routes.ts +++ b/src/server/routes.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ + import { Application } from 'express' export default (app: Application) => {