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/.eslintrc.js b/.eslintrc.js index 487f9ca..3765691 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,241 +1,68 @@ -/* -👋 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': [ - '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`?' - } - } - } - ], - '@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/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/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' - ], - '@typescript-eslint/semi': [ + 'rules': { + 'indent': [ 'error', - 'never' + 2 ], - '@typescript-eslint/triple-slash-reference': [ - 'error', - { - path: 'always', - types: 'prefer-import', - lib: 'always' - } + 'no-async-promise-executor': [ + 'off' ], - '@typescript-eslint/type-annotation-spacing': 'error', - '@typescript-eslint/unified-signatures': 'error', - 'arrow-body-style': 'error', - 'arrow-parens': [ - 'error', - 'as-needed' + '@typescript-eslint/no-explicit-any': [ + 'off' ], - 'brace-style': [ - 'error', - '1tbs' + '@typescript-eslint/explicit-module-boundary-types': [ + 'off' ], - 'camelcase': 'off', - '@typescript-eslint/camelcase': 'off', - 'comma-dangle': 'error', - 'complexity': 'off', - 'constructor-super': 'error', - 'curly': [ - 'error', - 'multi-or-nest' + 'prefer-const': [ + 'error' ], - 'eol-last': 'error', - 'eqeqeq': [ - 'error', - 'smart' + 'nonblock-statement-body-position': [ + 'error', 'below' ], - 'guard-for-in': 'error', - 'id-blacklist': 'off', - 'id-match': 'off', - 'import/order': 'error', - 'max-classes-per-file': [ - 'error', - 1 + 'no-sequences': [ + 'error' ], - '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' - } + 'eol-last': [ + 'error', 'always' ], - '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 04b5ba3..58b15d2 100644 --- a/package.json +++ b/package.json @@ -10,9 +10,10 @@ "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", "axios": "^0.19.0", "biguint-format": "^1.0.1", "chance": "^1.0.18", @@ -49,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/defaults.ts b/src/config/defaults.ts index 968cf3c..ba30ddb 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 new file mode 100644 index 0000000..8d10479 --- /dev/null +++ b/src/config/dispatcher.config.ts @@ -0,0 +1,6 @@ +import { Dispatcher } from '@cryb/mesa' + +import config from '../config/defaults' +import { getOptions } from './redis.config' + +export default new Dispatcher(getOptions(), { namespace: config.mesa_namespace }) diff --git a/src/config/passport.config.ts b/src/config/passport.config.ts index e379d1d..68cfbb3 100644 --- a/src/config/passport.config.ts +++ b/src/config/passport.config.ts @@ -40,14 +40,14 @@ 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 { - passport.authenticate('jwt', { session: false }, (err, user: User) => { + passport.authenticate('jwt', { session: false }, async (err, user: User) => { if (err) return handleError(err, res) @@ -64,9 +64,9 @@ 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.includes(endpoint)) return handleError(UserBanned, res) diff --git a/src/config/redis.config.ts b/src/config/redis.config.ts index 6a7178f..18d9f74 100644 --- a/src/config/redis.config.ts +++ b/src/config/redis.config.ts @@ -1,38 +1,39 @@ import { URL } from 'url' import Redis from 'ioredis' -interface Sentinel { - host: string - port: number +interface ISentinel { + host: string; + port: number; } const parseSentinels = (sentinels: string) => - sentinels.split(',').map(uri => ({ - host: uri.split(':')[1].replace('//', ''), - port: parseInt(uri.split(':')[2]) - } as Sentinel)), // 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 - } + 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 } +} export const createPubSubClient = () => new Redis(getOptions()) diff --git a/src/controllers/auth.controller.ts b/src/controllers/auth.controller.ts index bd738e6..253ce32 100644 --- a/src/controllers/auth.controller.ts +++ b/src/controllers/auth.controller.ts @@ -6,8 +6,8 @@ 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.includes(req.get('origin'))) @@ -16,9 +16,9 @@ app.post('/discord', async (req, res) => { 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) { @@ -26,7 +26,7 @@ app.post('/discord', async (req, res) => { } }) -app.get('/discord/redirect', (req, res) => { +app.get('/discord/redirect', async (req, res) => { let state = '' if (req.query.invite) 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 29ad963..e754dab 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() @@ -32,39 +34,35 @@ app.post('/portal', authenticate, async (req, res) => { * Existing Portal Status Update */ app.put('/portal', authenticate, async (req, res) => { - const { id, status, janusId, janusIp } = req.body as { id: string; status: PortalAllocationStatus; janusId?: number; janusIp?: string } + 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 }) + if (!doc) return RoomNotFound // console.log('room found, updating status...') const room = new Room(doc) - const { portal: allocation } = await room.updatePortalAllocation({ janusId, janusIp, status }), - { online } = await room.fetchOnlineMemberIds() + const { portal: allocation } = await room.updatePortalAllocation({ status }) + const { online } = await room.fetchOnlineMemberIds() // console.log('status updated and online members fetched:', online) if (online.length > 0) { /** - * Broadcast allocation to all online clients - */ - const updateMessage = new WSMessage(0, allocation, 'PORTAL_UPDATE') - await updateMessage.broadcast(online) + * Broadcast allocation to all online clients + */ + const updateMessage = new Message(0, allocation, 'PORTAL_UPDATE') + dispatcher.dispatch(updateMessage, online) if (status === 'open') { - // JanusId is -1 when a janus instance is not running. - if (allocation.janusId === -1) { - const token = signApertureToken(id), - apertureMessage = new WSMessage(0, { ws: process.env.APERTURE_WS_URL, t: token }, 'APERTURE_CONFIG') - await apertureMessage.broadcast(online) - } else { - const janusMessage = new WSMessage(0, { id: janusId }, 'JANUS_CONFIG') - await janusMessage.broadcast(online) - } + const token = signApertureToken(id) + const apertureMessage = new Message(0, { ws: process.env.APERTURE_WS_URL, t: token }, 'APERTURE_CONFIG') + + dispatcher.dispatch(apertureMessage, online) } } @@ -75,27 +73,17 @@ app.put('/portal', authenticate, async (req, res) => { }) app.post('/queue', authenticate, (req, res) => { - const { - dequeuedRoomIds, - currentQueueLength, - roomIdsInQueue - } = req.body as { - dequeuedRoomIds: string[] - currentQueueLength: number - lastMovementLength: number - roomIdsInQueue: string[] - } + const { queue } = req.body as { queue: string[] } - const dequeuedlength = dequeuedRoomIds.length + queue.forEach(async (id, i) => { + try { + const op = 0; const d = { pos: i, len: queue.length }; const t = 'PORTAL_QUEUE_UPDATE' + const message = new Message(op, d, t) - roomIdsInQueue.forEach((roomId, index) => { - const queueMessage = new WSMessage(0, { - currentPositionInQueue: index + 1, - currentQueueLength, - dequeuedLength: dequeuedlength - }, 'QUEUE_UPDATE') - - queueMessage.broadcastRoom(roomId) + dispatcher.dispatch(message, await fetchRoomMemberIds(id)) + } catch (error) { + handleError(error, res) + } }) res.sendStatus(200) 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 c228b96..ec17581 100644 --- a/src/controllers/member.controller.ts +++ b/src/controllers/member.controller.ts @@ -9,8 +9,7 @@ import { extractUserId } from '../utils/helpers.utils' const app = express() app.post('/:id/kick', authenticate, async (req, res) => { - const { user: u } = req - const user = u as User + const { user } = req as { user: User } if (!user.room) return handleError(UserNotInRoom, res) @@ -24,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) @@ -38,6 +37,6 @@ app.post('/:id/kick', authenticate, async (req, res) => { } }) -app.post('/:id/report', authenticate, (_, res) => res.sendStatus(200)) +app.post('/:id/report', authenticate, async (_, res) => res.sendStatus(200)) export default app 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 23f571e..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) diff --git a/src/drivers/portals.driver.ts b/src/drivers/portals.driver.ts index 9d2a5cd..cdd5a96 100644 --- a/src/drivers/portals.driver.ts +++ b/src/drivers/portals.driver.ts @@ -1,27 +1,31 @@ +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 url = `${process.env.PORTALS_API_URL}/`; const + key = process.env.PORTALS_API_KEY -const generateRoomToken = (room: Room) => jwt.sign({ roomId: room.id }, key), - generateHeaders = (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 { - const headers = generateHeaders(room) + 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 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) { @@ -32,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 = generateHeaders(room), - { portal } = room + const headers = await generateHeaders(room) + const { portal } = room if (!portal.id) return 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 1029cd7..09c326f 100644 --- a/src/models/message/index.ts +++ b/src/models/message/index.ts @@ -1,132 +1,134 @@ +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 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' 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 WSMessage(0, this, 'MESSAGE_CREATE') - message.broadcastRoom(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 WSMessage(0, { id: this.id }, 'MESSAGE_DESTROY') - message.broadcastRoom(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') + 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 bc3423a..47ac80e 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 { - t?: PortalEventType +export interface IPortalEvent { + 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 362bcbf..a71bb1e 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,15 +12,17 @@ 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 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' @@ -26,484 +30,484 @@ import { extractUserId, GroupedMessage, groupMessages } from '../../utils/helper export type RoomResolvable = Room | string export default class Room { - public id: string - public createdAt: number - public endedAt?: number + public id: string + public createdAt: number + public endedAt?: number - public type: RoomType - public active: boolean - public invites: Invite[] - public owner: UserResolvable + public type: RoomType + public active: boolean + public invites: Invite[] + public owner: UserResolvable - public portal?: IPortalAllocation - public controller: UserResolvable + public portal?: IPortalAllocation + public controller: UserResolvable - public name: string + public name: string - public members: User[] - public messages: GroupedMessage[] = [] + public members: User[] + public messages: GroupedMessage[] = [] - public online: string[] + public online: string[] - constructor(json?: IRoom) { - if(!json) return + constructor(json?: IRoom) { + 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) + 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) + 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) + 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(), + try { + const json: IRoom = { + info: { + id: generateFlake(), + createdAt: Date.now(), - type: 'vm', - portal: { - status: 'waiting', - lastUpdatedAt: Date.now() - }, + type: 'vm', + portal: { + status: 'waiting', + lastUpdatedAt: Date.now() + }, - owner: creator.id, - controller: creator.id - }, - profile: { - name - } - } + 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 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) - } - }) - - 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 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 WSMessage(0, { u: toId }, 'CONTROLLER_UPDATE') - message.broadcastRoom(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) - - 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 3046c79..3bcf546 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' @@ -10,345 +12,347 @@ import StoredBan from '../../schemas/ban.schema' import StoredMessage from '../../schemas/message.schema' import config from '../../config/defaults.js' +import dispatcher from '../../config/dispatcher.config' import client from '../../config/redis.config' -import WSMessage from '../../server/websocket/models/message' + import { constructAvatar, exchangeRefreshToken, fetchUserProfile } from '../../services/oauth2/discord.service' import { TooManyMembers, UserNotFound, UserNotInRoom } from '../../utils/errors.utils' +import { fetchRoomMemberIds } from '../../utils/fetchers.utils' import { generateFlake, signToken } from '../../utils/generate.utils' -import { extractUserId, UNALLOCATED_PORTALS_KEYS, extractRoomId } from '../../utils/helpers.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 WSMessage(0, this, 'USER_UPDATE') - message.broadcastRoom(this.room, [this.id]) - } - - resolve(this) - } catch (error) { - reject(error) - } - }) - - public signToken = () => new Promise(async (resolve, reject) => { - try { - const { id } = this, - token = await 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 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 + 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 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({ - '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/server/index.ts b/src/server/index.ts index 26da459..4e522a4 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -1,21 +1,19 @@ import dotenv from 'dotenv' dotenv.config() -// eslint-disable-next-line import/order import { createServer } from 'http' -import cors from 'cors' import express, { json } from 'express' +import { connect } from 'mongoose' +import passport from 'passport' + +import cors from 'cors' import helmet from 'helmet' import morgan from 'morgan' -import { Server } from 'ws' - -import { connect } from 'mongoose' -import passport from '../config/passport.config' import { verify_env } from '../utils/verifications.utils' +import mesa from './mesa' import routes from './routes' -import websocket from './websocket' verify_env( @@ -33,7 +31,6 @@ verify_env( const app = express() const server = createServer(app) -const wss = new Server({ server }) connect(process.env.MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true }) @@ -45,6 +42,6 @@ app.use(morgan('dev')) app.use(passport.initialize()) routes(app) -websocket(wss) +mesa(server) export default server diff --git a/src/server/mesa/index.ts b/src/server/mesa/index.ts new file mode 100644 index 0000000..962ddda --- /dev/null +++ b/src/server/mesa/index.ts @@ -0,0 +1,153 @@ +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] + ) + + log(`Authenticated ${id}`, 'ws', 'CYAN') + + 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 61f5239..8f3bdc3 100644 --- a/src/server/routes.ts +++ b/src/server/routes.ts @@ -1,8 +1,13 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ + 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('/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) 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 9324af7..0000000 --- a/src/server/websocket/handlers/message.ts +++ /dev/null @@ -1,76 +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 - - const currentTimestamp = new Date().getTime() - const oldTimestamp = socket.lastUserRefresh.getTime() - const timeDifference = Math.abs(currentTimestamp - oldTimestamp) - - if(timeDifference > 15000) { - socket.set('user', await new User().load(socket.user.id)) - socket.set('last_user_refresh', new Date()) - } - - 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 (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 deleted file mode 100644 index 0fd002a..0000000 --- a/src/server/websocket/index.ts +++ /dev/null @@ -1,139 +0,0 @@ -import WebSocket, { Server } from 'ws' - -import IWSEvent from './models/event' -import WSMessage from './models/message' -import WSSocket from './models/socket' - -import config from '../../config/defaults' -import client, { 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)) - -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') - - wss.on('connection', async (ws: WebSocket) => { - const socket = new WSSocket(ws) - - 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 - - // Hello Socket - const op = 10, d = { c_heartbeat_interval, c_reconnect_interval, c_authentication_timeout } - socket.send(new WSMessage(op, d)) - - const authentication_timeout = setTimeout(() => { - if (socket.authenticated) - return - - ws.close(1008) - }, c_authentication_timeout) - - const maxHeartbeatTries = 3 - let heartbeatTries = 0 - - const heartbeat_interval = setInterval(() => { - if (!socket.authenticated) - return - - const offset = c_heartbeat_interval * 1.25 // Set the offset (leeway) of the last heartbeat at - - if ((socket.lastHeartbeatAt - Date.now()) > -offset) - return heartbeatTries = 0 // If there has been a heartbeat update, return and reset the heartbeatTries variable - - // 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) - - ws.on('message', async data => { - let json: IWSEvent - - try { - json = JSON.parse(data.toString()) - } catch (error) { - return ws.close(1007) - } - - handleMessage(json, socket) - }) - - ws.on('close', async () => { - clearInterval(heartbeat_interval) - clearTimeout(authentication_timeout) - - if (socket.id) { - client.srem('connected_clients', socket.id) - client.hdel('client_sessions', socket.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]) - - if (typeof socket.user.room === 'string') - try { - await socket.user.fetchRoom() - } catch (error) { - return - } // Room doesn't exists - - if (typeof socket.user.room === 'string') - return - - const { room } = socket.user - - if (extractUserId(room.controller) === socket.user.id) - room.releaseControl(socket.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/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 14f991c..0000000 --- a/src/server/websocket/models/event.ts +++ /dev/null @@ -1,37 +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 = -'JANUS_CONFIG' | -'APERTURE_CONFIG' | -'ROOM_DESTROY' | -'MESSAGE_CREATE' | -'MESSAGE_DESTROY' | -'TYPING_UPDATE' | -'INVITE_UPDATE' | -'USER_JOIN' | -'USER_UPDATE' | -'USER_LEAVE' | -'OWNER_UPDATE' | -'CONTROLLER_UPDATE' | -'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 476794f..0000000 --- a/src/server/websocket/models/socket.ts +++ /dev/null @@ -1,142 +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' | 'last_user_refresh' -const socketKeys: SocketConfigKey[] = ['id', 'type', 'user', 'group', 'authenticated', 'last_heartbeat_at', 'last_user_refresh'] - -export default class WSSocket { - public id: string - - public user?: User - public room?: Room - public authenticated: boolean = false - public lastHeartbeatAt: number - public lastUserRefresh?: Date - - 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) - this.set('last_user_refresh', new Date()) - - // 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) { - //JanusId is -1 when a janus instance is not running. - if(room.portal.janusId == -1) { - const token = signApertureToken(room.portal.id), apertureMessage = new WSMessage(0, { ws: process.env.APERTURE_WS_URL, t: token }, 'APERTURE_CONFIG') - apertureMessage.broadcast([ extractUserId(user) ]) - } else { - const janusMessage = new WSMessage(0, { id: room.portal.janusId, ip: room.portal.janusIp }, 'JANUS_CONFIG') - janusMessage.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/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 8fbdcfb..1c35057 100644 --- a/src/utils/errors.utils.ts +++ b/src/utils/errors.utils.ts @@ -1,15 +1,15 @@ import { Response } from 'express' -interface APIResponse { - response: string +interface IAPIResponse { + response: string; error: { - title: string - description: string - } - status: number + title: string; + description: string; + }; + status: number; } -export const UserNoAuth: APIResponse = { +export const UserNoAuth: IAPIResponse = { response: 'USER_NO_AUTH', error: { title: 'User No Auth', @@ -18,7 +18,7 @@ export const UserNoAuth: APIResponse = { status: 401 } -export const UserBanned: APIResponse = { +export const UserBanned: IAPIResponse = { response: 'USER_BANNED', error: { title: 'User Banned', @@ -27,7 +27,7 @@ export const UserBanned: APIResponse = { status: 401 } -export const UserNotAuthorized: APIResponse = { +export const UserNotAuthorized: IAPIResponse = { response: 'USER_NOT_AUTHORIZED', error: { title: 'User Not Authorized', @@ -36,7 +36,7 @@ export const UserNotAuthorized: APIResponse = { status: 401 } -export const UserNotInRoom: APIResponse = { +export const UserNotInRoom: IAPIResponse = { response: 'USER_NOT_IN_ROOM', error: { title: 'User Not in Room', @@ -45,7 +45,7 @@ export const UserNotInRoom: APIResponse = { status: 410 } -export const UserAlreadyInRoom: APIResponse = { +export const UserAlreadyInRoom: IAPIResponse = { response: 'USER_ALREADY_IN_ROOM', error: { title: 'User Already in Room', @@ -54,7 +54,7 @@ export const UserAlreadyInRoom: APIResponse = { status: 409 } -export const InviteNotFound: APIResponse = { +export const InviteNotFound: IAPIResponse = { response: 'INVITE_NOT_FOUND', error: { title: 'Invite Not Found', @@ -63,7 +63,7 @@ export const InviteNotFound: APIResponse = { status: 404 } -export const RoomNotFound: APIResponse = { +export const RoomNotFound: IAPIResponse = { response: 'ROOM_NOT_FOUND', error: { title: 'Room Not Found', @@ -72,7 +72,7 @@ export const RoomNotFound: APIResponse = { status: 404 } -export const RoomNameTooLong: APIResponse = { +export const RoomNameTooLong: IAPIResponse = { response: 'ROOM_NAME_TOO_LONG', error: { title: 'Room Name Too Long', @@ -81,7 +81,7 @@ export const RoomNameTooLong: APIResponse = { status: 413 } -export const RoomNameTooShort: APIResponse = { +export const RoomNameTooShort: IAPIResponse = { response: 'ROOM_NAME_TOO_SHORT', error: { title: 'Room Name Too Long', @@ -90,7 +90,7 @@ export const RoomNameTooShort: APIResponse = { status: 413 } -export const UserNotFound: APIResponse = { +export const UserNotFound: IAPIResponse = { response: 'USER_NOT_FOUND', error: { title: 'User Not Found', @@ -99,7 +99,7 @@ export const UserNotFound: APIResponse = { status: 404 } -export const ControllerIsNotAvailable: APIResponse = { +export const ControllerIsNotAvailable: IAPIResponse = { response: 'CONTROLLER_IS_NOT_AVAILABLE', error: { title: 'Controller Is Not Available', @@ -108,7 +108,7 @@ export const ControllerIsNotAvailable: APIResponse = { status: 406 } -export const UserDoesNotHaveRemote: APIResponse = { +export const UserDoesNotHaveRemote: IAPIResponse = { response: 'USER_DOES_NOT_HAVE_REMOTE', error: { title: 'User Does Not Have Remote', @@ -117,7 +117,7 @@ export const UserDoesNotHaveRemote: APIResponse = { status: 417 } -export const UserIsNotPermitted: APIResponse = { +export const UserIsNotPermitted: IAPIResponse = { response: 'USER_IS_NOT_PERMITTED', error: { title: 'User Is Not Permitted', @@ -126,7 +126,7 @@ export const UserIsNotPermitted: APIResponse = { status: 401 } -export const MessageTooLong: APIResponse = { +export const MessageTooLong: IAPIResponse = { response: 'MESSAGE_TOO_LONG', error: { title: 'Message Too Long', @@ -135,7 +135,7 @@ export const MessageTooLong: APIResponse = { status: 413 } -export const MessageTooShort: APIResponse = { +export const MessageTooShort: IAPIResponse = { response: 'MESSAGE_TOO_SHORT', error: { title: 'Message Too Short', @@ -144,7 +144,7 @@ export const MessageTooShort: APIResponse = { status: 413 } -export const MessageNotFound: APIResponse = { +export const MessageNotFound: IAPIResponse = { response: 'MESSAGE_NOT_FOUND', error: { title: 'Message Not Found', @@ -153,7 +153,7 @@ export const MessageNotFound: APIResponse = { status: 404 } -export const ReportNotFound: APIResponse = { +export const ReportNotFound: IAPIResponse = { response: 'REPORT_NOT_FOUND', error: { title: 'Report Not Found', @@ -162,7 +162,7 @@ export const ReportNotFound: APIResponse = { status: 404 } -export const BanNotFound: APIResponse = { +export const BanNotFound: IAPIResponse = { response: 'BAN_NOT_FOUND', error: { title: 'Ban Not Found', @@ -171,7 +171,7 @@ export const BanNotFound: APIResponse = { status: 404 } -export const BanAlreadyExists: APIResponse = { +export const BanAlreadyExists: IAPIResponse = { response: 'BAN_ALREADY_EXISTS', error: { title: 'Ban Already Exists', @@ -180,7 +180,7 @@ export const BanAlreadyExists: APIResponse = { status: 409 } -export const TooManyMembers: APIResponse = { +export const TooManyMembers: IAPIResponse = { response: 'TOO_MANY_MEMBERS', error: { title: 'Too Many Members', @@ -189,7 +189,7 @@ export const TooManyMembers: APIResponse = { status: 409 } -export const TargetTypeNotFound: APIResponse = { +export const TargetTypeNotFound: IAPIResponse = { response: 'TARGET_TYPE_NOT_FOUND', error: { title: 'Target Type Not Found', @@ -198,7 +198,7 @@ export const TargetTypeNotFound: APIResponse = { status: 0 } -export const NoPortalFound: APIResponse = { +export const NoPortalFound: IAPIResponse = { response: 'NO_PORTAL_FOUND', error: { title: 'No Portal Found', @@ -207,7 +207,7 @@ export const NoPortalFound: APIResponse = { status: 404 } -export const PortalNotOpen: APIResponse = { +export const PortalNotOpen: IAPIResponse = { response: 'PORTAL_NOT_OPEN', error: { title: 'Portal Not Open', diff --git a/src/utils/fetchers.utils.ts b/src/utils/fetchers.utils.ts new file mode 100644 index 0000000..dde5569 --- /dev/null +++ b/src/utils/fetchers.utils.ts @@ -0,0 +1,6 @@ +import { RoomResolvable } from '../models/room' +import StoredUser from '../schemas/user.schema' +import { extractRoomId } from './helpers.utils' + + +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 909b6ef..ec4f8fd 100644 --- a/src/utils/helpers.utils.ts +++ b/src/utils/helpers.utils.ts @@ -42,7 +42,7 @@ export class GroupedMessage { public messages: Message[] public messageIds: string[] - public constructor(message: Message, author: UserResolvable) { + constructor(message: Message, author: UserResolvable) { this.id = message.id this.createdAt = message.createdAt diff --git a/src/utils/log.utils.ts b/src/utils/log.utils.ts index ca4b249..27511ed 100644 --- a/src/utils/log.utils.ts +++ b/src/utils/log.utils.ts @@ -16,12 +16,12 @@ type LogColor = 'BLACK' | 'RED' | 'GREEN' | 'YELLOW' | 'BLUE' | 'MAGENTA' | 'CYA export const logColors: LogColor[] = ['RED', 'GREEN', 'YELLOW', 'BLUE', 'MAGENTA', 'CYAN'] -export interface LogPrefix { - content: string - color?: LogColor +export interface ILogPrefix { + content: string; + color?: LogColor; } -export default (msg: string, prefixes: LogPrefix[] | string, color: LogColor = 'GREEN') => { +export default (msg: string, prefixes: ILogPrefix[] | string, color: LogColor = 'GREEN') => { // Create empty prefix string let prefix = '' @@ -29,16 +29,19 @@ export default (msg: string, prefixes: LogPrefix[] | string, color: LogColor = ' prefixes = [] // If a prefix / multiple prefixes are defined - if (prefixes) { + if (prefixes) // If the first item of the possible array contains properties that could indicate a LogPrefix object - if ((prefixes[0] as LogPrefix).content) { + { + if ((prefixes[0] as ILogPrefix).content) // For every log prefix item - prefix = (prefixes as LogPrefix[]).map(prefixItem => + { + 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}]` + `[${prefixItem.color ? colors[prefixItem.color] : colors.GREEN}${prefixItem.content.toUpperCase()}${colors.RESET}]` ).join(' ') - } else + } // 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}]` } diff --git a/src/utils/validate.utils.ts b/src/utils/validate.utils.ts index c0f2764..72d2a41 100644 --- a/src/utils/validate.utils.ts +++ b/src/utils/validate.utils.ts @@ -1,12 +1,10 @@ -import { WSEventType } from '../server/websocket/models/event' - 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 } @@ -15,12 +13,12 @@ const validateControllerPositionCoord = (pos: number) => typeof pos === 'number' const validateControllerPosition = data => ( validateControllerPositionCoord(data.x) && - validateControllerPositionCoord(data.y) + validateControllerPositionCoord(data.y) ) 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/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 5f109a1..5aa874b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3,45 +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.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" "*" @@ -59,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" @@ -157,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" "*" @@ -183,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" @@ -218,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" @@ -242,10 +375,10 @@ 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== +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" @@ -253,12 +386,11 @@ asynckit@^0.4.0: 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" @@ -270,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== @@ -278,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" @@ -303,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" @@ -316,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" @@ -336,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" @@ -350,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" @@ -372,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" @@ -453,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" @@ -463,6 +637,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" @@ -484,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== @@ -496,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== @@ -522,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" @@ -563,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" @@ -578,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" @@ -606,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" @@ -652,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" @@ -671,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" @@ -683,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" @@ -721,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== @@ -733,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" @@ -820,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" @@ -844,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" @@ -858,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" @@ -889,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" @@ -934,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" @@ -979,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" @@ -1011,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" @@ -1021,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" @@ -1045,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" @@ -1072,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" @@ -1130,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" @@ -1162,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== @@ -1174,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" @@ -1194,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== @@ -1211,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" @@ -1223,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" @@ -1245,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" @@ -1270,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" @@ -1305,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" @@ -1344,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" @@ -1357,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" @@ -1430,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" @@ -1489,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" @@ -1503,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" @@ -1534,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" @@ -1545,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" @@ -1556,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" @@ -1586,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" @@ -1595,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" @@ -1609,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" @@ -1619,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" @@ -1631,17 +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.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== +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: - async-limiter "^1.0.0" + 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"