├── server ├── src │ ├── database │ │ ├── card.json │ │ └── users.json │ ├── types │ │ ├── card.ts │ │ ├── user.ts │ │ └── emit.ts │ ├── services │ │ ├── transmit.ts │ │ └── users.ts │ ├── repository │ │ ├── card.ts │ │ └── user.ts │ ├── server.ts │ └── controllers │ │ ├── CardsController.ts │ │ ├── mainController.ts │ │ └── UsersController.ts ├── .gitignore ├── .env.example ├── public │ └── favicon.ico ├── tsconfig.json └── package.json ├── client ├── .gitignore ├── .env.example ├── public │ ├── assets │ │ ├── img │ │ │ ├── bg01.jpg │ │ │ ├── arrow_left02.png │ │ │ └── arrow_right02.png │ │ ├── sounds │ │ │ ├── draw.mp3 │ │ │ └── bg-music.mp3 │ │ └── fonts │ │ │ ├── Cabin-Bold.ttf │ │ │ └── Cabin-Regular.ttf │ ├── js │ │ ├── server │ │ │ ├── boot.js │ │ │ ├── emit.js │ │ │ ├── server.js │ │ │ └── listen.js │ │ ├── notificationDOM.js │ │ ├── scriptInjector.js │ │ ├── watch │ │ │ └── watch.js │ │ ├── form.js │ │ ├── effects.js │ │ └── game.js │ ├── index.html │ └── styles.css ├── readme.md ├── package.json ├── entrypoint.js ├── yarn-error.log └── yarn.lock ├── capa.jpg ├── docker-compose.yml ├── LICENSE └── README.md /server/src/database/card.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /server/src/database/users.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json -------------------------------------------------------------------------------- /client/.env.example: -------------------------------------------------------------------------------- 1 | PORT=7000 2 | DEBUGMODE=false 3 | AUTH_TOKEN= -------------------------------------------------------------------------------- /capa.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drelocatelli/uno-online/HEAD/capa.jpg -------------------------------------------------------------------------------- /server/.env.example: -------------------------------------------------------------------------------- 1 | API_PORT=3000 2 | PASSWORD=minhasenha 3 | CLIENT_URL="*" -------------------------------------------------------------------------------- /server/src/types/card.ts: -------------------------------------------------------------------------------- 1 | export interface ICard { 2 | color: string; 3 | symbol: string; 4 | } -------------------------------------------------------------------------------- /server/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drelocatelli/uno-online/HEAD/server/public/favicon.ico -------------------------------------------------------------------------------- /server/src/types/user.ts: -------------------------------------------------------------------------------- 1 | export interface IUser { 2 | id: string; 3 | username: string; 4 | cards: number; 5 | } -------------------------------------------------------------------------------- /client/public/assets/img/bg01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drelocatelli/uno-online/HEAD/client/public/assets/img/bg01.jpg -------------------------------------------------------------------------------- /client/public/assets/sounds/draw.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drelocatelli/uno-online/HEAD/client/public/assets/sounds/draw.mp3 -------------------------------------------------------------------------------- /client/readme.md: -------------------------------------------------------------------------------- 1 | ![image](https://user-images.githubusercontent.com/32282846/222990501-29994484-340d-4c68-ae8b-7bae267f2f27.png) 2 | -------------------------------------------------------------------------------- /server/src/types/emit.ts: -------------------------------------------------------------------------------- 1 | export interface IEmit { 2 | message?: string; 3 | isError: boolean; 4 | toId?: string; 5 | } -------------------------------------------------------------------------------- /client/public/assets/fonts/Cabin-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drelocatelli/uno-online/HEAD/client/public/assets/fonts/Cabin-Bold.ttf -------------------------------------------------------------------------------- /client/public/assets/img/arrow_left02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drelocatelli/uno-online/HEAD/client/public/assets/img/arrow_left02.png -------------------------------------------------------------------------------- /client/public/assets/img/arrow_right02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drelocatelli/uno-online/HEAD/client/public/assets/img/arrow_right02.png -------------------------------------------------------------------------------- /client/public/assets/sounds/bg-music.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drelocatelli/uno-online/HEAD/client/public/assets/sounds/bg-music.mp3 -------------------------------------------------------------------------------- /client/public/assets/fonts/Cabin-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drelocatelli/uno-online/HEAD/client/public/assets/fonts/Cabin-Regular.ttf -------------------------------------------------------------------------------- /server/src/services/transmit.ts: -------------------------------------------------------------------------------- 1 | import type { Socket } from "socket.io"; 2 | 3 | export default function transmit(socket: Socket, key: string, value: any) { 4 | socket.broadcast.emit(key, value); 5 | socket.emit(key, value); 6 | } -------------------------------------------------------------------------------- /client/public/js/server/boot.js: -------------------------------------------------------------------------------- 1 | window.onload = boot; 2 | 3 | function boot() { 4 | try { 5 | console.log('boot'); 6 | new ServerListen(server.socket); 7 | 8 | } catch(err) { 9 | console.log(err); 10 | alert('Não foi possível inicializar o jogo, tente novamente'); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /client/public/js/notificationDOM.js: -------------------------------------------------------------------------------- 1 | class NotificationDOM { 2 | static setNotification(text) { 3 | const audio = Effects.newAudioInstance('draw.mp3'); 4 | audio.volume = .4; 5 | audio.autoplay = true; 6 | notificationBar.style.opacity = 1; 7 | notificationBar.style.pointerEvents = 'all'; 8 | notificationBar.innerHTML = text; 9 | setTimeout(() => { 10 | notificationBar.style.pointerEvents = 'none'; 11 | notificationBar.style.opacity = 0; 12 | }, 5000); 13 | } 14 | } -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "1.0.0", 4 | "description": "![image](https://user-images.githubusercontent.com/32282846/222990501-29994484-340d-4c68-ae8b-7bae267f2f27.png)", 5 | "main": "entrypoint.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node .", 9 | "serve": "npm run start" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "chokidar": "^3.5.3", 16 | "dotenv": "^16.0.3", 17 | "express": "^4.18.2", 18 | "nodemon": "^2.0.21", 19 | "socket.io": "^4.6.1" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "CommonJS", 5 | "moduleResolution": "node", 6 | "allowJs": true, 7 | "outDir": "./dist", 8 | "rootDirs": ["./src"], 9 | "removeComments": true, 10 | "typeRoots": [ 11 | "./node_modules/@types", 12 | "./src/@types" 13 | ], 14 | "importsNotUsedAsValues": "error", 15 | "isolatedModules": true, 16 | "skipDefaultLibCheck": true, 17 | "esModuleInterop": true, 18 | "sourceMap": true, 19 | "strict": true, 20 | "resolveJsonModule": false, 21 | "experimentalDecorators": true, 22 | "emitDecoratorMetadata": true, 23 | "skipLibCheck": true, 24 | "forceConsistentCasingInFileNames": true, 25 | } 26 | } -------------------------------------------------------------------------------- /client/public/js/scriptInjector.js: -------------------------------------------------------------------------------- 1 | const injectScript = (name) => new ScriptInjector(name); 2 | class ScriptInjector { 3 | 4 | constructor(name) { 5 | this.inject(name); 6 | } 7 | 8 | inject(name = null) { 9 | let path = `js/${name}.js` 10 | const script = document.createElement('script'); 11 | script.src = path; 12 | let scriptInjectorEl = document.querySelector('script-injector'); 13 | scriptInjectorEl.append(script); 14 | } 15 | 16 | } 17 | 18 | injectScript('game'); 19 | injectScript('notificationDOM'); 20 | injectScript('server/server'); 21 | injectScript('server/listen'); 22 | injectScript('server/emit'); 23 | injectScript('form'); 24 | injectScript('effects'); 25 | injectScript('server/boot'); -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | server: 5 | image: node:latest 6 | volumes: 7 | - ./server:/app 8 | working_dir: /app 9 | ports: 10 | - "3000:3000" 11 | command: sh -c "npm install && npm run start" 12 | 13 | client: 14 | image: node:latest 15 | volumes: 16 | - ./client:/app 17 | working_dir: /app 18 | ports: 19 | - "${PORT:-7000}:${PORT:-7000}" 20 | env_file: 21 | - ./client/.env 22 | command: sh -c "npm install && npm run start" 23 | 24 | ngrok: 25 | image: shkoliar/ngrok:latest 26 | ports: 27 | - "4551:4551" 28 | env_file: 29 | - ./client/.env 30 | links: 31 | - client 32 | environment: 33 | - AUTH_TOKEN=${AUTH_TOKEN} 34 | - DOMAIN=client 35 | - PORT=${PORT:-7000} -------------------------------------------------------------------------------- /client/public/js/watch/watch.js: -------------------------------------------------------------------------------- 1 | // to watch file modifications then reload page 2 | class WatchClientServer { 3 | debugMode = false; 4 | 5 | static socket() { 6 | return io(`ws://${window.location.host}`, { transports: ['websocket', 'polling', 'flashsocket'] }); 7 | } 8 | 9 | socket() { 10 | return WatchClientServer.socket(); 11 | } 12 | 13 | constructor() { 14 | this.running(); 15 | this.saveStateDebug(); 16 | } 17 | 18 | running() { 19 | this.socket().on('reload', () => { 20 | console.log('Reload event...'); 21 | window.location.reload(); 22 | }); 23 | } 24 | 25 | saveStateDebug() { 26 | this.socket().on('debugMode', (state) => { 27 | console.log('Debug mode:', JSON.parse(state)) 28 | this.debugMode = JSON.parse(state); 29 | }); 30 | } 31 | 32 | } 33 | 34 | new WatchClientServer(); -------------------------------------------------------------------------------- /server/src/repository/card.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import type { ICard } from '../types/card'; 3 | 4 | export class CardRepository { 5 | static path = 'src/database/card.json'; 6 | 7 | static get() : ICard { 8 | return JSON.parse(fs.readFileSync(this.path, 'utf-8')); 9 | } 10 | 11 | static store(card: ICard) : IStore { 12 | return { 13 | onEmpty: () => { 14 | if(Object.keys(this.get()).length === 0) { 15 | fs.writeFileSync(this.path, JSON.stringify(card)); 16 | } 17 | }, 18 | replace: () => { 19 | fs.writeFileSync(this.path, JSON.stringify(card)); 20 | } 21 | }; 22 | } 23 | 24 | static reset() { 25 | fs.writeFileSync(this.path, '{}', 'utf-8'); 26 | console.log('all card data clean'); 27 | } 28 | 29 | } 30 | 31 | interface IStore { 32 | onEmpty: () => void, 33 | replace: () => void 34 | } -------------------------------------------------------------------------------- /client/entrypoint.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const fs = require('fs'); 3 | const express = require('express'); 4 | const http = require('http'); 5 | const chokidar = require('chokidar'); 6 | const socketio = require('socket.io'); 7 | 8 | const app = express(); 9 | const server = http.createServer(app); 10 | 11 | app.use('/', express.static('public')); 12 | 13 | app.get('/', (req, res) => { 14 | res.sendFile(__dirname + '/public/index.html'); 15 | }); 16 | 17 | const io = socketio(server); 18 | server.listen(process.env.PORT, '0.0.0.0', () => { 19 | console.log(`Running at http://localhost:${process.env.PORT}`); 20 | }); 21 | 22 | const debugMode = process.env.DEBUGMODE; 23 | 24 | io.on('connection', (socket) => { 25 | socket.emit('debugMode', debugMode); 26 | 27 | socket.on('started', (e, cb) => { 28 | console.log('Game server is ready'); 29 | cb({debugMode}); 30 | }); 31 | // socket.emit('started', true); 32 | }); 33 | 34 | chokidar.watch('public').on('change', () => { 35 | io.sockets.emit('reload'); 36 | }); -------------------------------------------------------------------------------- /server/src/server.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import { SocketControllers } from 'socket-controllers'; 3 | import Container from 'typedi'; 4 | import { CardsController } from './controllers/CardsController'; 5 | import { MainController } from './controllers/mainController'; 6 | import { UserController } from './controllers/UsersController'; 7 | import {Server} from 'socket.io'; 8 | import http from 'http'; 9 | import express from 'express'; 10 | import 'dotenv/config'; 11 | 12 | const app = express(); 13 | const server = http.createServer(app); 14 | 15 | const port = process.env.API_PORT ? parseInt(process.env.API_PORT) : 3000; 16 | const client = process.env.CLIENT_URL ?? 'http://localhost:5000'; 17 | 18 | const io = new Server(server, { 19 | cors: { 20 | origin: client, 21 | } 22 | }); 23 | 24 | new SocketControllers({ 25 | io, 26 | port: port, 27 | container: Container, 28 | controllers: [MainController, UserController, CardsController] 29 | }); 30 | 31 | server.listen(port, () => { 32 | console.log('Server listening'); 33 | }); -------------------------------------------------------------------------------- /client/public/js/server/emit.js: -------------------------------------------------------------------------------- 1 | class ServerEmit { 2 | socket; 3 | 4 | constructor(socket) { 5 | this.socket = socket; 6 | } 7 | 8 | clearData() { 9 | const password = prompt('Digite a senha do servidor:'); 10 | if(password) { 11 | this.socket.emit('data:reset', password); 12 | window.location.reload(); 13 | } 14 | } 15 | 16 | shareGlobalCard(card) { 17 | this.socket.emit('card:global', card, (response) => { 18 | console.log('card:global', response) 19 | }); 20 | } 21 | 22 | playerEnter(playerName) { 23 | this.socket.emit('user:login', playerName); 24 | this.socket.on('user:login', (e) => { 25 | console.log('user:login', e); 26 | if(!e.isError) { 27 | game.openGame(); 28 | } else { 29 | NotificationDOM.setNotification(e.message); 30 | } 31 | }); 32 | } 33 | 34 | shareCardsCount(quantity) { 35 | this.socket.emit('card:user-count', quantity); 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /server/src/controllers/CardsController.ts: -------------------------------------------------------------------------------- 1 | import { ConnectedSocket, EmitOnFail, EmitOnSuccess, MessageBody, OnMessage, SkipEmitOnEmptyResult, SocketController } from "socket-controllers"; 2 | import type { Socket } from "socket.io"; 3 | import { Service } from "typedi"; 4 | import { CardRepository } from "../repository/card"; 5 | import { UserRepository } from "../repository/user"; 6 | import type { ICard } from "../types/card"; 7 | 8 | @SocketController() 9 | @Service() 10 | export class CardsController { 11 | 12 | @OnMessage('card:global') 13 | @EmitOnSuccess('card:global') 14 | global(@ConnectedSocket() socket: Socket, @MessageBody() card: ICard) { 15 | CardRepository.store(card).onEmpty(); 16 | return CardRepository.get(); 17 | } 18 | 19 | @OnMessage('card:user-count') 20 | @EmitOnSuccess('card:user-count') 21 | shareUserCardCount(@ConnectedSocket() socket: Socket, @MessageBody() quantity: number) { 22 | const findUser = UserRepository.findById(socket.id)?.get(); 23 | socket.emit('users:all'); 24 | return findUser?.cards; 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 4ndr3224 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /server/src/services/users.ts: -------------------------------------------------------------------------------- 1 | import { UserRepository } from "../repository/user"; 2 | import type { IEmit } from "../types/emit"; 3 | import type { IUser } from "../types/user"; 4 | 5 | export class Users { 6 | remove({id} : {id: string}) : (IEmit | void) { 7 | const find = UserRepository.findById(id)?.get(); 8 | if(find != undefined) { 9 | UserRepository.findById(find.id)?.remove(); 10 | return {message: `${find.username} desconectou-se`, isError: false}; 11 | } 12 | } 13 | 14 | add(user : IUser) : (IEmit | void) { 15 | if(/[^\w\s]/.test(user.username)) { 16 | return {message: 'Não é permitido caracteres especiais', isError: true}; 17 | } 18 | 19 | if(UserRepository.findById(user.id)?.get() != undefined) { 20 | return {message: 'Você não pode utilizar duas contas', isError: true}; 21 | } 22 | 23 | if(UserRepository.findByUserName(user.username)?.get() == undefined) { 24 | UserRepository.add(user) 25 | return {message: `${user.username} entrou`, isError: false}; 26 | } else { 27 | return {message: 'Esse usuário já existe', isError: true}; 28 | } 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /client/public/js/server/server.js: -------------------------------------------------------------------------------- 1 | class Server { 2 | socket; 3 | 4 | constructor(server, port) { 5 | this.changeSocket(server, port); 6 | } 7 | 8 | changeSocket(server, port = 80) { 9 | if(localStorage.getItem('uno-server') != null) { 10 | server = localStorage.getItem('uno-server'); 11 | } else { 12 | server = server.concat(`:${port}`); 13 | localStorage.setItem('uno-server', server); 14 | } 15 | this.socket = io(server, { transports: ['websocket', 'polling', 'flashsocket'] }); 16 | (async() => { 17 | try { 18 | let {ip: yourIp} = await this.getMyIp(); 19 | if(yourIp === server) { 20 | yourIp = yourIp.concat(`:${port}`); 21 | this.socket = io('localhost'.concat(`:${port}`), { transports: ['websocket', 'polling', 'flashsocket'] }); 22 | } 23 | } catch(e) { 24 | console.log('unable to set localhost IP', e); 25 | } 26 | })(); 27 | } 28 | 29 | async getMyIp() { 30 | const response = await fetch('https://api.ipify.org/?format=json'); 31 | return response.json(); 32 | } 33 | 34 | } 35 | 36 | let server = new Server('localhost', '3000'); -------------------------------------------------------------------------------- /server/src/controllers/mainController.ts: -------------------------------------------------------------------------------- 1 | import { ConnectedSocket, MessageBody, OnConnect, OnDisconnect, OnMessage, SocketController } from "socket-controllers"; 2 | import type { Socket } from "socket.io"; 3 | import { Service } from "typedi"; 4 | import { CardRepository } from "../repository/card"; 5 | import { UserRepository } from "../repository/user"; 6 | 7 | @SocketController() 8 | @Service() 9 | export class MainController { 10 | @OnConnect() 11 | connection(@ConnectedSocket() socket: Socket) { 12 | console.log('user connected, socket id:', socket.id); 13 | } 14 | 15 | @OnDisconnect() 16 | disconnect(@ConnectedSocket() socket: Socket) { 17 | console.log('client disconected'); 18 | } 19 | 20 | @OnMessage('test') 21 | hello(@ConnectedSocket() socket: Socket, @MessageBody() message: any) { 22 | console.log('Hello,', socket.id, message); 23 | } 24 | 25 | @OnMessage('data:reset') 26 | reset(@ConnectedSocket() socket: Socket, @MessageBody() message: any) { 27 | if(process.env.PASSWORD === message) { 28 | UserRepository.reset(); 29 | CardRepository.reset(); 30 | socket.emit('data:reset', 'Dados limpos!'); 31 | } else { 32 | socket.emit('data:resetError', 'Acesso negado!'); 33 | } 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /client/public/js/form.js: -------------------------------------------------------------------------------- 1 | function preventSubmit(e) { 2 | e.preventDefault(); 3 | } 4 | 5 | class Forms { 6 | static define() { 7 | const forms = document.querySelectorAll('form'); 8 | for(let form of forms) { 9 | form.addEventListener('submit', (e) => { 10 | e.preventDefault(); 11 | switch(e.target.dataset.form) { 12 | case 'main': 13 | this.main(e.target); 14 | break; 15 | } 16 | }); 17 | } 18 | this.changeServer(); 19 | } 20 | 21 | static main(form) { 22 | let formData = new FormData(form); 23 | const userName = formData.get('userName'); 24 | new ServerEmit(server.socket).playerEnter(userName); 25 | } 26 | 27 | static changeServer() { 28 | document.querySelector('button[name="changeServer"]').onclick = function() { 29 | const serverIp = prompt("Digite o IP/URL do servidor", 'ws://'); 30 | if(serverIp.length > 0) { 31 | localStorage.setItem('uno-server', serverIp); 32 | window.location.reload(); 33 | } 34 | } 35 | 36 | } 37 | } 38 | Forms.define(); 39 | 40 | function reload() { 41 | const confirmReload = confirm('Deseja realmente sair?'); 42 | if(confirmReload) { 43 | window.location.reload(); 44 | } 45 | } 46 | 47 | 48 | /** 49 | * NOTIFICATIONS ------------------------------------ 50 | */ 51 | 52 | const notificationBar = document.querySelector('.notification-bar'); 53 | notificationBar.onclick = (e) => { 54 | notificationBar.style.pointerEvents = 'none'; 55 | notificationBar.style.opacity = 0; 56 | }; 57 | 58 | document.querySelector('button[name=clearUsers]').onclick = (e) => { 59 | new ServerEmit(server.socket).clearData(); 60 | }; -------------------------------------------------------------------------------- /server/src/controllers/UsersController.ts: -------------------------------------------------------------------------------- 1 | import { ConnectedSocket, EmitOnSuccess, MessageBody, OnDisconnect, OnMessage, SocketController } from "socket-controllers"; 2 | import type { Socket } from "socket.io"; 3 | import { Service } from "typedi"; 4 | import { CardRepository } from "../repository/card"; 5 | import { UserRepository } from "../repository/user"; 6 | import transmit from "../services/transmit"; 7 | import { Users } from "../services/users"; 8 | 9 | @SocketController() 10 | @Service() 11 | export class UserController { 12 | users : Users = new Users(); 13 | 14 | @OnMessage('user:login') 15 | enter(@ConnectedSocket() socket: Socket, @MessageBody() message: any) { 16 | // if hasn't user, clear all data 17 | if(UserRepository.count() === 0) { 18 | UserRepository.reset(); 19 | CardRepository.reset(); 20 | console.log('Started new game!'); 21 | } 22 | 23 | const addedUser = this.users.add({id: socket.id, username: message} as any); 24 | socket.broadcast.emit('user:logged', addedUser); 25 | socket.emit('user:login', addedUser); 26 | } 27 | 28 | @OnMessage('user:count') 29 | count(@ConnectedSocket() socket: Socket) { 30 | transmit(socket, 'user:count', UserRepository.count()); 31 | } 32 | 33 | @OnDisconnect() 34 | disconnect(@ConnectedSocket() socket: Socket) { 35 | transmit(socket, 'user:logout', this.users.remove({id: socket.id})); 36 | } 37 | 38 | @OnMessage('users:all') 39 | @EmitOnSuccess('users:all') 40 | shareOtherUsersCardCount(@ConnectedSocket() socket: Socket) { 41 | return UserRepository.findAll().filter(u => u.id != socket.id).map(({id, ...rest}) => rest); 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "whatsflow-backend", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "dist/server.js", 6 | "module": "dist/server.js", 7 | "type": "commonjs", 8 | "scripts": { 9 | "dev": "tsnd -r dotenv/config --respawn --exit-child --transpile-only --watch --ignore-watch node_modules src/server.ts", 10 | "build": "tsc", 11 | "start": "npm run dev", 12 | "desconsiderar": "node -r dotenv/config dist/server.js", 13 | "db:migrations": "npx prisma migrate dev --preview-feature", 14 | "db:reset": "npx prisma migrate reset --preview-feature", 15 | "db:migrate": "npx prisma generate", 16 | "db:studio": "npx prisma studio", 17 | "db:legacy": "npx prisma introspect", 18 | "formatter": "npx prettier --write src/**/*.ts", 19 | "pull": "git pull && npm i -D && npx prisma generate && npx prisma db push" 20 | }, 21 | "keywords": [], 22 | "author": "", 23 | "license": "ISC", 24 | "devDependencies": { 25 | "@types/cookie-parser": "^1.4.2", 26 | "@types/cors": "^2.8.12", 27 | "@types/cron": "^1.7.3", 28 | "@types/express": "^4.17.9", 29 | "@types/jsonwebtoken": "^8.5.8", 30 | "@types/node": "^18.0.0", 31 | "@types/uuid": "^8.3.4", 32 | "@typescript-eslint/parser": "^4.33.0", 33 | "eslint": "^7.32.0", 34 | "eslint-config-prettier": "^8.5.0", 35 | "eslint-plugin-prettier": "^4.0.0", 36 | "esm": "^3.2.25", 37 | "gts": "^3.1.0", 38 | "prettier": "^2.6.1", 39 | "ts-node": "^10.5.0", 40 | "ts-node-dev": "^1.1.1", 41 | "typescript": "^4.0.3" 42 | }, 43 | "dependencies": { 44 | "cors": "^2.8.5", 45 | "cron": "^1.8.2", 46 | "dotenv": "^16.0.3", 47 | "express": "^4.18.2", 48 | "express-async-errors": "^3.1.1", 49 | "express-rate-limit": "^6.3.0", 50 | "reflect-metadata": "^0.1.13", 51 | "socket-controllers": "^0.1.2", 52 | "socket.io": "^4.6.1", 53 | "typedi": "^0.10.0" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /server/src/repository/user.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import type { IUser } from '../types/user'; 3 | 4 | export class UserRepository { 5 | static path = 'src/database/users.json'; 6 | 7 | static add(user: IUser) : void { 8 | const fcontents = fs.readFileSync(this.path, 'utf-8'); 9 | let data : IUser[] = JSON.parse(fcontents); 10 | data.push({...user, cards: 7}); 11 | fs.writeFileSync(this.path, JSON.stringify(data), 'utf-8'); 12 | } 13 | 14 | static findAll() : IUser[] { 15 | const fcontents = fs.readFileSync(this.path, 'utf-8'); 16 | return JSON.parse(fcontents); 17 | } 18 | 19 | static reset() : void { 20 | fs.writeFileSync(this.path, '[]', 'utf-8'); 21 | console.log('all users data clean'); 22 | } 23 | 24 | static count() : number { 25 | return this.findAll().length; 26 | } 27 | 28 | static findById(id: string) : (IRepositoryFind | void) { 29 | const users = JSON.parse(fs.readFileSync(this.path, 'utf-8')) as IUser[]; 30 | const index = users.findIndex(u => u.id == id); 31 | 32 | if(index >= 0) { 33 | return { 34 | remove: () => { 35 | const newUsers = (index == 0) ? JSON.parse('[]') : users.splice(index, 1); 36 | fs.writeFileSync(this.path, JSON.stringify(newUsers), 'utf-8'); 37 | }, 38 | get: () => users[index] 39 | } 40 | } 41 | } 42 | 43 | static findByUserName(username: string) : (IRepositoryFind | void) { 44 | const users = JSON.parse(fs.readFileSync(this.path, 'utf-8')) as IUser[]; 45 | const index = users.findIndex(u => u.username == username); 46 | 47 | if(index >= 0) { 48 | return { 49 | remove: () => { 50 | const newUsers = users.splice(index, 1); 51 | fs.writeFileSync(this.path, JSON.stringify(newUsers), 'utf-8'); 52 | }, 53 | get: () => users[index] 54 | } 55 | } 56 | } 57 | 58 | } 59 | 60 | interface IRepositoryFind { 61 | remove: () => void; 62 | get: () => T; 63 | } -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Uno 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 | 13 |
14 |

15 | Digite seu nome de usuário: 16 |

17 |
18 | 19 | 20 | 21 |
22 |
23 | Contribua no Github. 24 |
25 |
26 | 27 | 59 | 60 | 61 | 62 |
63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /client/public/js/effects.js: -------------------------------------------------------------------------------- 1 | const cardsEl = document.querySelector('#my-cards .cards'); 2 | 3 | class Effects { 4 | static newAudioInstance(audioName) { 5 | const audio = document.createElement('audio'); 6 | audio.src = `../assets/sounds/${audioName}`; 7 | return audio; 8 | } 9 | 10 | static toggleMusic() { 11 | let audio = this.newAudioInstance('bg-music.mp3'); 12 | const musicBtn = document.querySelector('button[name="toggleMusic"]'); 13 | audio.autoplay = true; 14 | musicBtn.onclick = function(e) { 15 | if(audio.paused) { 16 | audio.play(); 17 | } else { 18 | audio.pause(); 19 | } 20 | } 21 | } 22 | 23 | static myCardsArrow(showing) { 24 | const arrowsEl = document.createElement('div'); 25 | arrowsEl.classList.add('myCards-arrows'); 26 | if(showing) { 27 | const leftArrow = document.createElement('img'); 28 | leftArrow.title = 'Arrastar pra esquerda'; 29 | leftArrow.src = 'assets/img/arrow_left02.png'; 30 | 31 | const rightArrow = document.createElement('img'); 32 | rightArrow.title = 'Arrastar pra direita'; 33 | rightArrow.src = 'assets/img/arrow_right02.png'; 34 | 35 | arrowsEl.appendChild(leftArrow); 36 | arrowsEl.appendChild(rightArrow); 37 | cardsEl.appendChild(arrowsEl); 38 | 39 | // scroll on border 40 | cardsEl.addEventListener("mousemove", function (event) { 41 | const containerRect = cardsEl.getBoundingClientRect(); 42 | const x = event.clientX - containerRect.left; 43 | const y = event.clientY - containerRect.top; 44 | 45 | if (x < 20) { 46 | cardsEl.scrollLeft -= 30; 47 | } else if (x > containerRect.width - 20) { 48 | cardsEl.scrollLeft += 30; 49 | } 50 | 51 | // window.requestAnimationFrame(scroll); 52 | }); 53 | 54 | leftArrow.onclick = (e) => { 55 | cardsEl.scrollLeft -= 100; 56 | } 57 | 58 | rightArrow.onclick = (e) => { 59 | cardsEl.scrollLeft += 100; 60 | } 61 | 62 | } else { 63 | arrowsEl.remove(); 64 | } 65 | } 66 | 67 | static myCardsSlideshow() { 68 | if(cardsEl.clientWidth < cardsEl.scrollWidth) { 69 | this.myCardsArrow(true); 70 | } else { 71 | this.myCardsArrow(false); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /client/public/js/server/listen.js: -------------------------------------------------------------------------------- 1 | class ServerListen { 2 | socket; 3 | preventAutoExecution = ['constructor']; 4 | 5 | constructor(socket) { 6 | this.socket = socket; 7 | 8 | // auto execute all methods listed 9 | Object.getOwnPropertyNames(Object.getPrototypeOf(this)) 10 | .filter(f => !f.includes(this.preventAutoExecution) && typeof this[f] === 'function') 11 | .forEach(propName => this[propName]()); 12 | } 13 | 14 | listenGlobalNotification() { 15 | this.socket.on('notification', (e) => { 16 | NotificationDOM.setNotification(e); 17 | }); 18 | } 19 | 20 | listenDataReset() { 21 | this.socket.on('data:resetError', (e) => { 22 | NotificationDOM.setNotification(e); 23 | }); 24 | this.socket.on('data:reset', (e) => { 25 | NotificationDOM.setNotification(e); 26 | }); 27 | } 28 | 29 | listenOnConnectOrDisconnect() { 30 | this.socket.on('connect', () => { 31 | console.log('Connected to server'); 32 | NotificationDOM.setNotification('Servidor conectado'); 33 | WatchClientServer.socket().emit('started', true, (response) => { 34 | const mainForm = document.querySelector('form[data-form="main"]'); 35 | if(JSON.parse(response.debugMode)) { 36 | mainForm[0].value = 'debugUser_'.concat(Math.floor(Math.random() * 501)); 37 | setTimeout(() => { 38 | mainForm[1].click(); 39 | }, 300); 40 | } 41 | }); 42 | }); 43 | this.socket.on('disconnect', () => { 44 | console.log('Disconnected from server'); 45 | }); 46 | } 47 | 48 | listenOnPlayerEnter() { 49 | this.socket.on('user:logged', (e) => { 50 | NotificationDOM.setNotification(e.message); 51 | console.log('user:logged', e); 52 | }); 53 | } 54 | 55 | listenOnPlayerLeave() { 56 | this.socket.on('user:logout', (e) => { 57 | console.log(e); 58 | NotificationDOM.setNotification(e.message); 59 | }); 60 | } 61 | 62 | listenOnCardGlobalShared() { 63 | this.socket.on('card:global', (e) => { 64 | console.log('card:global', e); 65 | game.syncGlobalCard(e); 66 | }); 67 | } 68 | 69 | listenSharedPlayerCard() { 70 | // when game started, give 7 cards for player 71 | game.giveMeCards(7); 72 | this.socket.on('card:user-count', (cardsQuantity) => { 73 | if(cardsQuantity != game.playerCards) { 74 | game.giveMeCards(cardsQuantity); 75 | } 76 | }); 77 | } 78 | 79 | listenOtherUsersCard() { 80 | // this.socket.on('users:all', (response) => { 81 | // }); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Uno-Online 2 | 3 | 4 | 5 | 10 | 11 | Capa do UNO 13 | 14 | > Uno Online para se divertir com os amigos. 15 | 16 | Tabela de conteúdos 17 | ================= 18 | 19 | * [Sobre](#sobre) 20 | * [Tabela de Conteudo](#tabela-de-conteudo) 21 | * [Ajustes e Melhorias](#ajustes-e-melhorias) 22 | * [Documentação Server Side](#documentação-server-side) 23 | * [Pré Requisitos](#pré-requisitos) 24 | * [Instalando Uno Online](#instalando-uno-online) 25 | * [Usando Uno Online](#usando-uno-online-e-jogando-com-amigos) 26 | * [Contribuindo](#contribuindo-para-uno-online) 27 | * [Colaboradores](#colaboradores) 28 | * [Licença](#licença) 29 | 30 | 31 | ## 🔧 Ajustes e melhorias 32 | 33 | O projeto ainda está em desenvolvimento e as próximas atualizações serão voltadas nas seguintes tarefas: 34 | 35 | - [x] Tarefa 1 36 | 37 | ## 📚 Documentação do server-side 38 | [Ler documentação](https://free-borogovia-69a.notion.site/Uno-Online-server-docs-978b591e1aec4cb8816c0209e91c217d) 39 | 40 | ## 💻 Pré-requisitos 41 | 42 | Antes de começar, verifique se você atendeu aos seguintes requisitos: 43 | 44 | - [Docker](https://www.docker.com/) 45 | - [Docker-compose](https://docs.docker.com/compose/install/) 46 | 47 | Importante⚠️: Dentro das pastas `server` e `client`, copie o conteúdo do arquivo `.env.example` e crie um arquivo `.env` com o conteúdo copiado. 48 | 49 | ## 🚀 Instalando Uno Online 50 | 51 | 1. Acesse a pasta do projeto. 52 | 2. Execute o comando abaixo para subir a máquina: 53 | 54 | ```bash 55 | docker-compose up 56 | ``` 57 | 58 | ## ☕ Usando Uno Online e Jogando com amigos! 59 | 60 | ### 🎮 Jogando com amigos [método 1] 61 | 62 | - Para convidar seus amigos para jogar, você precisará abrir a DMZ (conhecido também como abrir portas) do seu roteador. 63 | - Após isso, acesse esse site e copie o endereço de IP Público: https://meuip.com.br/ 64 | - Copie o IP e adicione a porta nele, por exemplo: 65 | ``` 66 | 127.0.0.1:PORTA 67 | ``` 68 | - Agora, basta enviar o link para seus amigos e começar a jogar! 69 | 70 | ### 🎮 Jogando com amigos [método 2] 71 | 72 | - Esse projeto tem um recurso chamado **ngrok** que permite que você crie um link público para jogar com seus amigos. 73 | - Para isso, basta acessar o site https://ngrok.com/ e criar uma conta. 74 | - Após criar a conta, copie o token e preencha `AUTH_TOKEN=` dentro do arquivo `/client/.env`. 75 | - Agora, basta executar o comando abaixo para subir o projeto com as configurações do ngrok: 76 | 77 | ```bash 78 | docker-compose --env-file=client/.env up 79 | ``` 80 | 81 | - O link que você vai enviar para seu amigos você pegará em: [localhost:4551](http://localhost:4551) 82 | - Obs: Envie para seu amigo o link em **http** e não em https. 83 | 84 | ## 📫 Contribuindo para Uno Online 85 | 86 | 87 | 88 | Para contribuir com Uno Online, siga estas etapas: 89 | 90 | 1. Bifurque este repositório. 91 | 2. Crie um branch: `git checkout -b `. 92 | 3. Faça suas alterações e confirme-as: `git commit -m ''` 93 | 4. Envie para o branch original: `git push origin uno-online / ` 94 | 5. Crie a solicitação de pull. 95 | 96 | Como alternativa, consulte a documentação do GitHub em [como criar uma solicitação pull](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request). 97 | 98 | ## 🤝 Colaboradores 99 | 100 | Agradecemos às seguintes pessoas que contribuíram para este projeto: 101 | 102 | 103 | 104 | 112 | 120 | 121 |
105 | 106 | Foto da 4ndr3224 no GitHub
107 | 108 | 4ndr3224 109 | 110 |
111 |
113 | 114 | Foto do Guilherme S. no GitHub
115 | 116 | Guilherme S. Barros 117 | 118 |
119 |
122 | 123 | ## 📝 Licença 124 | 125 | Esse projeto está sob licença. Veja o arquivo [LICENÇA](LICENSE.md) para mais detalhes. 126 | 127 | [⬆ Voltar ao topo](#Uno-Online)
128 | -------------------------------------------------------------------------------- /client/public/js/game.js: -------------------------------------------------------------------------------- 1 | const app = document.getElementById("app"); 2 | 3 | class Game { 4 | effects; 5 | 6 | section = 0; 7 | colors = ["#ED1A22", "#FDDE03", "#06A553", "#0B93D3"]; 8 | cards = [ 9 | "0", 10 | "1", 11 | "2", 12 | "3", 13 | "4", 14 | "5", 15 | "6", 16 | "7", 17 | "8", 18 | "9", 19 | "⥄", 20 | "+2", 21 | "+4", 22 | "❖", 23 | "⍉" 24 | ]; 25 | 26 | playerCards = 0; 27 | inGameCards = []; 28 | 29 | isRunning() { 30 | const gameEl = document.querySelector('#game'); 31 | if(gameEl.style.display === 'block') { 32 | return true; 33 | } 34 | 35 | return false; 36 | } 37 | 38 | openGame() { 39 | document.querySelector('#main').remove(); 40 | document.querySelector('#game').style.display = 'block'; 41 | const card = this.generateGlobalCard(); 42 | Effects.toggleMusic(); 43 | new ServerEmit(server.socket).shareGlobalCard(card); 44 | } 45 | 46 | getGlobalCard() { 47 | const globalCard = app.querySelector('#main-cards .card'); 48 | return globalCard; 49 | } 50 | 51 | setGlobalCard(symbol, color) { 52 | let middleSymbol = this.getGlobalCard().querySelector('.middle-number'); 53 | let topSymbol = this.getGlobalCard().querySelector('.simbol-top'); 54 | let botSymbol = this.getGlobalCard().querySelector('.simbol-bottom'); 55 | let content = this.getGlobalCard().querySelector('.content'); 56 | 57 | if(symbol === "❖" || symbol === "+4" ) { 58 | content.classList.add('colorfulBg'); 59 | } else { 60 | content.classList.remove('colorfulBg'); 61 | middleSymbol.style.textShadow = '5px 7px 1px #000'; 62 | } 63 | 64 | console.log(this.getGlobalCard()) 65 | console.log(symbol) 66 | 67 | middleSymbol.innerHTML = symbol; 68 | topSymbol.innerHTML = symbol; 69 | botSymbol.innerHTML = symbol; 70 | content.style.backgroundColor = color; 71 | } 72 | 73 | syncGlobalCard(card) { 74 | this.setGlobalCard(card.symbol, card.color); 75 | } 76 | 77 | generateCardSymbolNumber() { 78 | return Math.floor( 79 | Math.random() * (this.cards.length - 1 - 0 + 1) + 0 80 | ); 81 | } 82 | 83 | generateCardSymbol() { 84 | return this.cards[this.generateCardSymbolNumber()]; 85 | } 86 | 87 | generateCardColorNumber() { 88 | return Math.floor( 89 | Math.random() * (this.colors.length - 1 - 0 + 1) + 0 90 | ); 91 | } 92 | 93 | generateCardColor() { 94 | return this.colors[this.generateCardColorNumber()]; 95 | } 96 | 97 | generateGlobalCard() { 98 | const generateSymbol = this.generateCardSymbol(); 99 | const generateColor = this.generateCardColor(); 100 | if(generateSymbol == "❖" || generateSymbol == "+4") { 101 | return this.generateGlobalCard(); 102 | } 103 | this.generateCard(1, {showing: true, canHide: false, cardSymbol: generateSymbol, cardColor: generateColor}); 104 | return {symbol: generateSymbol, color: generateColor}; 105 | } 106 | 107 | giveMeCards(quantity = 7, signal = 'positive') { 108 | const box = document.querySelector('#my-cards .cards'); 109 | this.playerCards = (signal == 'positive') ? + quantity : - quantity; 110 | this.generateCard(quantity, {box}); 111 | Effects.myCardsSlideshow(); 112 | } 113 | 114 | generateCard(quantity = 1, {box, showing = false, canHide = true, cardSymbol, cardColor}) { 115 | for (let q = 1; q <= quantity; q++) { 116 | 117 | // limit cards 118 | if (this.limitCards(cardSymbol)) { 119 | continue; 120 | } else { 121 | // create card object in DOM 122 | const index = this.inGameCards.length; 123 | this.addCardInDOM(cardSymbol, index, {box, showing, canHide, cardColor}); 124 | } 125 | } 126 | this.section += 1; 127 | this.showHideCards(); 128 | const cardsQuantityEl = document.querySelector('cards-quantity'); 129 | if(cardsQuantityEl != null) { 130 | cardsQuantityEl.innerText = `Você possui ${this.playerCards} cartas`; 131 | new ServerEmit(server.socket).shareCardsCount(this.playerCards); 132 | } 133 | } 134 | 135 | generateFriendCard(quantity = 1, {box, showing = false, canHide = true, cardSymbol, cardColor}) { 136 | for (let q = 1; q <= quantity; q++) { 137 | 138 | // limit cards 139 | if (this.limitCards(cardSymbol)) { 140 | continue; 141 | } else { 142 | // create card object in DOM 143 | const index = this.inGameCards.length; 144 | this.addCardInDOM(cardSymbol, index, {box, showing, canHide, cardColor}); 145 | } 146 | } 147 | this.section += 1; 148 | } 149 | 150 | showHideCards() { 151 | const revealCards = document.querySelector('button[name="revealCards"]'); 152 | const hideCards = document.querySelector('button[name="hideCards"]'); 153 | if(game.inGameCards.length >= 1) { 154 | revealCards.disabled = false; 155 | } else { 156 | revealCards.disabled = true; 157 | } 158 | 159 | const allCards = document.querySelectorAll('#my-cards .card'); 160 | revealCards.onclick = function() { 161 | for(let card of allCards) { 162 | const hidden = card.querySelector('.hidden'); 163 | hidden.style.opacity = 0; 164 | } 165 | revealCards.disabled = true; 166 | hideCards.disabled = false; 167 | 168 | } 169 | 170 | hideCards.onclick = function() { 171 | for(let card of allCards) { 172 | const hidden = card.querySelector('.hidden'); 173 | hidden.style.opacity = 1; 174 | } 175 | hideCards.disabled = true; 176 | revealCards.disabled = false; 177 | 178 | } 179 | 180 | } 181 | 182 | addCardInDOM(symbol, index, {box, showing = false, canHide = true, cardColor}) { 183 | symbol = symbol ?? this.cards[this.generateCardSymbolNumber()]; 184 | 185 | cardColor = cardColor ?? this.generateCardColor(); 186 | // symbol == "❖" || symbol == "+4" ? "#000" : this.colors[randomNumber]; 187 | box = box ?? document.querySelector('#main-cards #mesa'); 188 | 189 | const card = document.createElement("div"); 190 | card.classList.add("card"); 191 | card.setAttribute("draggable", "true"); 192 | card.dataset.cardId = `${index}`; 193 | 194 | const content = document.createElement("div"); 195 | content.classList.add("content"); 196 | content.style.backgroundColor = cardColor; 197 | 198 | if(symbol == "❖" || symbol == "+4" ) { 199 | content.classList.add('colorfulBg'); 200 | } 201 | card.appendChild(content); 202 | 203 | const symbolDiv = document.createElement("div"); 204 | symbolDiv.classList.add("simbol"); 205 | symbolDiv.classList.add("simbol-top"); 206 | symbolDiv.innerText = symbol; 207 | content.appendChild(symbolDiv); 208 | 209 | const middle = document.createElement("div"); 210 | middle.classList.add("middle"); 211 | content.appendChild(middle); 212 | 213 | const middleNumber = document.createElement("div"); 214 | // if(symbol == "❖" || symbol == "+4" ) { 215 | // middleNumber.classList.add('colorful'); 216 | // } 217 | middleNumber.classList.add("middle-number"); 218 | middleNumber.innerText = symbol; 219 | // middleNumber.style.color = cardColor; 220 | middleNumber.style.color = "#fff"; 221 | content.appendChild(middleNumber); 222 | 223 | const simbolBottom = document.createElement("div"); 224 | simbolBottom.classList.add("simbol"); 225 | simbolBottom.classList.add("simbol-bottom"); 226 | simbolBottom.innerText = symbol; 227 | content.appendChild(simbolBottom); 228 | 229 | if (symbol == "❖" || symbol == "+4") { 230 | middleNumber.style.textShadow = "none"; 231 | } 232 | card.setAttribute('title', `Carta N°${index + 1}`); 233 | 234 | // add hidden card 235 | if(canHide) { 236 | const hiddenEl = document.createElement('div'); 237 | hiddenEl.classList.add('hidden'); 238 | hiddenEl.style.opacity = (!showing) ? 1 : 0; 239 | content.prepend(hiddenEl); 240 | } 241 | 242 | box.prepend(card); 243 | this.inGameCards.push(symbol); 244 | } 245 | 246 | limitCards(lastSymbol) { 247 | const findQuantity = this.cardsByQuatity(lastSymbol); 248 | 249 | if (lastSymbol == "⥄" && findQuantity == 8) { 250 | return true; 251 | } 252 | 253 | if (lastSymbol == "+2" && findQuantity == 8) { 254 | return true; 255 | } 256 | 257 | if (lastSymbol == "⍉" && findQuantity == 8) { 258 | return true; 259 | } 260 | 261 | if (lastSymbol == "❖" && findQuantity == 4) { 262 | return true; 263 | } 264 | 265 | if (lastSymbol == "+4" && findQuantity == 4) { 266 | return true; 267 | } 268 | 269 | // default numeric cards 270 | if (findQuantity == 76) { 271 | return true; 272 | } 273 | 274 | return false; 275 | } 276 | 277 | cardsByQuatity(symbol) { 278 | const find = this.inGameCards.filter((card) => card == symbol); 279 | return find.length; 280 | } 281 | } 282 | 283 | const game = new Game(); 284 | 285 | -------------------------------------------------------------------------------- /client/public/styles.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Cabin'; 3 | src: url('assets/fonts/Cabin-Regular.ttf') format('truetype'); 4 | font-weight: normal; 5 | font-style: normal; 6 | } 7 | 8 | @font-face { 9 | font-family: 'Cabin'; 10 | src: url('assets/fonts/Cabin-Bold.ttf') format('truetype'); 11 | font-weight: bold; 12 | font-style: normal; 13 | } 14 | 15 | body { 16 | font-family: sans-serif; 17 | background: url('assets/img/bg01.jpg') no-repeat; 18 | background-size: cover; 19 | } 20 | 21 | #app { 22 | padding: 18px 10px; 23 | } 24 | 25 | .card { 26 | display: inline-block; 27 | margin: 5px 20px; 28 | overflow: hidden; 29 | font-family: "Cabin", Courier, monospace; 30 | user-select: none; 31 | position: relative; 32 | background: #fff; 33 | width: 30vh; 34 | height: 50vh; 35 | padding: 14px; 36 | border: 1px solid #e7e7e7; 37 | box-shadow: 0px 0px 8px rgb(228, 228, 228); 38 | border-radius: 5px; 39 | } 40 | 41 | .card .content { 42 | position: relative; 43 | background-color: #ccc; 44 | width: -webkit-fill-available; 45 | height: 100%; 46 | cursor: pointer; 47 | } 48 | 49 | .hidden { 50 | transition: opacity .3s; 51 | display: block; 52 | position: absolute; 53 | top: 0; 54 | left: 0; 55 | width: -webkit-fill-available; 56 | background: rgb(34, 34, 34); 57 | height: 100%; 58 | z-index: 2; 59 | } 60 | 61 | .card .simbol { 62 | color: #fff; 63 | font-size: 290%; 64 | padding: 5px 8px; 65 | font-weight: bold; 66 | } 67 | 68 | .card .simbol-top { 69 | position: absolute; 70 | rotate: -180deg; 71 | text-shadow: -3px -2px 0px #000; 72 | } 73 | 74 | .card .simbol-top::before { 75 | content: ""; 76 | box-shadow: -3px -2px 0px #000; 77 | width: 20px; 78 | height: 5px; 79 | background: #fff; 80 | position: absolute; 81 | right: 3%; 82 | transform: translateX(-50%); 83 | } 84 | 85 | .card .simbol-bottom::before { 86 | box-shadow: -3px -2px 0px #000; 87 | content: ""; 88 | width: 20px; 89 | height: 4px; 90 | background: #fff; 91 | position: absolute; 92 | right: 3%; 93 | transform: translateX(-50%); 94 | } 95 | 96 | .card .simbol-bottom { 97 | text-shadow: -3px -2px 0px #000; 98 | position: absolute; 99 | bottom: 0; 100 | right: 0; 101 | } 102 | 103 | .card .middle { 104 | position: absolute; 105 | top: 4%; 106 | left: 0%; 107 | width: 100%; 108 | border-radius: 134px / 196px; 109 | rotate: 36deg; 110 | height: 92%; 111 | /* background: #fff; */ 112 | border: 5px solid #fff; 113 | background: transparent; 114 | } 115 | 116 | .card .middle-number { 117 | position: absolute; 118 | top: 49%; 119 | left: 50%; 120 | transform: translate(-50%, -50%); 121 | font-size: 700%; 122 | text-shadow: 5px 7px 1px #000; 123 | font-family: "Cabin", courier, monospace; 124 | font-weight: bold; 125 | color: #ccc; 126 | } 127 | 128 | .form { 129 | padding: 28px; 130 | text-align: center; 131 | } 132 | 133 | .card .colorful { 134 | animation: colorful 2s linear infinite; 135 | 136 | } 137 | 138 | .card .colorfulBg { 139 | animation: colorfulBg 2s linear infinite; 140 | } 141 | 142 | .card .rotating { 143 | -webkit-animation: rotating 2s linear infinite; 144 | -moz-animation: rotating 2s linear infinite; 145 | -ms-animation: rotating 2s linear infinite; 146 | -o-animation: rotating 2s linear infinite; 147 | animation: rotating 2s linear infinite; 148 | top: 28%; 149 | left: 25%; 150 | transform: translate(-50%, -50%); 151 | } 152 | 153 | @keyframes colorful { 154 | 0% { 155 | color: #ED1A22; 156 | /* background-color: #ED1A22; */ 157 | } 158 | 25% { 159 | color: #FDDE03; 160 | /* background-color: #FDDE03; */ 161 | } 162 | 50% { 163 | color: #06A553; 164 | /* background-color: #06A553; */ 165 | } 166 | 75% { 167 | color: #0B93D3; 168 | /* background-color: #0B93D3; */ 169 | } 170 | } 171 | 172 | @keyframes colorfulBg { 173 | 0% { 174 | /* color: #ED1A22; */ 175 | background-color: #ED1A22; 176 | } 177 | 25% { 178 | /* color: #FDDE03; */ 179 | background-color: #FDDE03; 180 | } 181 | 50% { 182 | /* color: #06A553; */ 183 | background-color: #06A553; 184 | } 185 | 75% { 186 | /* color: #0B93D3; */ 187 | background-color: #0B93D3; 188 | } 189 | } 190 | 191 | @keyframes rotating { 192 | from { 193 | -ms-transform: rotate(0deg); 194 | -moz-transform: rotate(0deg); 195 | -webkit-transform: rotate(0deg); 196 | -o-transform: rotate(0deg); 197 | transform: rotate(0deg); 198 | } 199 | to { 200 | -ms-transform: rotate(360deg); 201 | -moz-transform: rotate(360deg); 202 | -webkit-transform: rotate(360deg); 203 | -o-transform: rotate(360deg); 204 | transform: rotate(360deg); 205 | } 206 | } 207 | 208 | .notification-bar { 209 | position: fixed; 210 | z-index: 2; 211 | right: 15px; 212 | bottom: 15px; 213 | background: #f9f9f9ba; 214 | padding: 1rem 1rem; 215 | border: 1px solid #888; 216 | border-radius: 5px; 217 | cursor: pointer; 218 | user-select: none; 219 | transition: opacity .8s; 220 | } 221 | 222 | .notification-bar:empty { 223 | display: none; 224 | pointer-events: none; 225 | } 226 | 227 | 228 | .notification-bar:hover { 229 | opacity: .6; 230 | } 231 | 232 | #main { 233 | user-select: none; 234 | position: absolute; 235 | top: 50%; 236 | transform: translateY(-50%); 237 | padding-left: 5rem; 238 | } 239 | 240 | #main h3 { 241 | font-size: 32px; 242 | color: #fff; 243 | } 244 | 245 | .text-outlined { 246 | text-shadow: 3px 5px 1px #000; 247 | text-transform: uppercase; 248 | } 249 | 250 | input { 251 | padding: 0.7rem 0.8rem; 252 | border-radius: 15px; 253 | border:0; 254 | outline: 2px solid #000; 255 | outline-offset: -4px; 256 | } 257 | 258 | button { 259 | cursor: pointer; 260 | background: #9f0f0d; 261 | padding: .6rem 0.8rem; 262 | font-size: 18px; 263 | color: #fff; 264 | margin: 5px; 265 | text-shadow: 0px 3px 1px #333; 266 | font-weight: bold; 267 | border: 3px solid #000; 268 | box-shadow: inset 2px 2px 1px #fff, 3px 3px 17px #464646ba; 269 | outline: none; 270 | border-radius: 10px; 271 | } 272 | 273 | button:active { 274 | box-shadow: inset -2px -2px 1px #fff, 3px 3px 17px #464646ba; 275 | } 276 | 277 | button[name="clearUsers"] { 278 | z-index: 2; 279 | position: fixed; 280 | top: 0; 281 | left: 0; 282 | } 283 | 284 | #app #main-cards { 285 | display: flex; 286 | flex-direction: row-reverse; 287 | gap: 10%; 288 | justify-content: center; 289 | position: relative; 290 | text-align: center; 291 | } 292 | 293 | #app #mesa { 294 | display: flex; 295 | flex-direction: column-reverse; 296 | } 297 | 298 | #app #main-cards .friends_cards { 299 | } 300 | 301 | #app #main-cards .card { 302 | transition: transform .5s; 303 | } 304 | 305 | #app #main-cards .card:hover { 306 | transform: scale(1.05); 307 | } 308 | 309 | 310 | .card[data-card-id="0"] { 311 | width: 213px; 312 | height: 335px; 313 | } 314 | 315 | #app #my-cards { 316 | position: fixed; 317 | bottom: 0; 318 | left: 50%; 319 | transform: translateX(-50%); 320 | width: 80%; 321 | height: 34%; 322 | pointer-events: none; 323 | } 324 | 325 | #app #my-cards .cards { 326 | padding-top: 20px; 327 | overflow-x: scroll; 328 | /* overflow: visible; */ 329 | /* overflow-x: clip; */ 330 | -ms-overflow-style: none; /* Internet Explorer 10+ */ 331 | scrollbar-width: none; /* Firefox */ 332 | display: flex; 333 | flex-direction: row; 334 | width: auto; 335 | height: max-content; 336 | } 337 | 338 | #app #my-cards .cards .scrollable { 339 | overscroll-behavior: contain; 340 | scroll-behavior: smooth; 341 | overflow-x: scroll; 342 | } 343 | 344 | #app #my-cards::-webkit-scrollbar { 345 | display: none; /* Safari and Chrome */ 346 | } 347 | 348 | #app #my-cards .card { 349 | position: relative; 350 | pointer-events: all; 351 | transition: top ease-out .3s; 352 | /* transform: rotate(-10deg); */ 353 | margin: 5px 3px; 354 | top: 0; 355 | min-width: 219px; 356 | width: 219px; 357 | height: 300px; 358 | } 359 | 360 | #app #my-cards .card:hover { 361 | top: -20px; 362 | } 363 | 364 | h1[name="youSubtle"], .accent-text { 365 | pointer-events: all; 366 | color: #fff065; 367 | margin: 0; 368 | text-align: left; 369 | text-shadow: 2px 3px 0px #000, 1px 1px 5px #000; 370 | font-family: "Cabin", cursive; 371 | font-size: 2rem; 372 | margin: 5px 0; 373 | font-weight: bold; 374 | } 375 | 376 | .myCards-arrows { 377 | position: absolute; 378 | overflow: visible; 379 | left: 0; 380 | /* top: 50%; */ 381 | z-index: 3; 382 | display: flex; 383 | width: -webkit-fill-available; 384 | justify-content: space-between; 385 | height: -webkit-fill-available; 386 | align-items: center; 387 | } 388 | 389 | .myCards-arrows img:first-child { 390 | right: 25px; 391 | } 392 | 393 | .myCards-arrows img:last-child { 394 | left: 25px; 395 | } 396 | 397 | .myCards-arrows img { 398 | position: relative; 399 | cursor: pointer; 400 | width: 70px; 401 | height: 70px; 402 | pointer-events: all; 403 | } 404 | 405 | .form[data-form="game"] { 406 | pointer-events: all; 407 | position: absolute; 408 | z-index: 3; 409 | top: 10px; 410 | transform: translateY(-50%); 411 | right: 15px; 412 | padding: 0; 413 | } 414 | -------------------------------------------------------------------------------- /client/yarn-error.log: -------------------------------------------------------------------------------- 1 | Arguments: 2 | /home/andressa/.nvm/versions/node/v16.19.0/bin/node /usr/local/bin/yarn add chockidar 3 | 4 | PATH: 5 | /home/andressa/.local/share/pnpm:/home/andressa/.nvm/versions/node/v16.19.0/bin:/home/andressa/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin 6 | 7 | Yarn version: 8 | 1.22.19 9 | 10 | Node version: 11 | 16.19.0 12 | 13 | Platform: 14 | linux x64 15 | 16 | Trace: 17 | Error: https://registry.yarnpkg.com/chockidar: Not found 18 | at Request.params.callback [as _callback] (/usr/local/lib/node_modules/yarn/lib/cli.js:66145:18) 19 | at Request.self.callback (/usr/local/lib/node_modules/yarn/lib/cli.js:140890:22) 20 | at Request.emit (node:events:513:28) 21 | at Request. (/usr/local/lib/node_modules/yarn/lib/cli.js:141862:10) 22 | at Request.emit (node:events:513:28) 23 | at IncomingMessage. (/usr/local/lib/node_modules/yarn/lib/cli.js:141784:12) 24 | at Object.onceWrapper (node:events:627:28) 25 | at IncomingMessage.emit (node:events:525:35) 26 | at endReadableNT (node:internal/streams/readable:1358:12) 27 | at processTicksAndRejections (node:internal/process/task_queues:83:21) 28 | 29 | npm manifest: 30 | { 31 | "name": "client", 32 | "version": "1.0.0", 33 | "description": "![image](https://user-images.githubusercontent.com/32282846/222990501-29994484-340d-4c68-ae8b-7bae267f2f27.png)", 34 | "main": "server.js", 35 | "scripts": { 36 | "test": "echo \"Error: no test specified\" && exit 1", 37 | "start": "node ." 38 | }, 39 | "keywords": [], 40 | "author": "", 41 | "license": "ISC", 42 | "dependencies": { 43 | "axios": "^1.3.4", 44 | "serve-handler": "^6.1.5" 45 | } 46 | } 47 | 48 | yarn manifest: 49 | No manifest 50 | 51 | Lockfile: 52 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 53 | # yarn lockfile v1 54 | 55 | 56 | asynckit@^0.4.0: 57 | version "0.4.0" 58 | resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" 59 | integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== 60 | 61 | axios@^1.3.4: 62 | version "1.3.4" 63 | resolved "https://registry.yarnpkg.com/axios/-/axios-1.3.4.tgz#f5760cefd9cfb51fd2481acf88c05f67c4523024" 64 | integrity sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ== 65 | dependencies: 66 | follow-redirects "^1.15.0" 67 | form-data "^4.0.0" 68 | proxy-from-env "^1.1.0" 69 | 70 | balanced-match@^1.0.0: 71 | version "1.0.2" 72 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" 73 | integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== 74 | 75 | brace-expansion@^1.1.7: 76 | version "1.1.11" 77 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 78 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 79 | dependencies: 80 | balanced-match "^1.0.0" 81 | concat-map "0.0.1" 82 | 83 | bytes@3.0.0: 84 | version "3.0.0" 85 | resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" 86 | integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw== 87 | 88 | combined-stream@^1.0.8: 89 | version "1.0.8" 90 | resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" 91 | integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== 92 | dependencies: 93 | delayed-stream "~1.0.0" 94 | 95 | concat-map@0.0.1: 96 | version "0.0.1" 97 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 98 | integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== 99 | 100 | content-disposition@0.5.2: 101 | version "0.5.2" 102 | resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" 103 | integrity sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA== 104 | 105 | delayed-stream@~1.0.0: 106 | version "1.0.0" 107 | resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" 108 | integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== 109 | 110 | fast-url-parser@1.1.3: 111 | version "1.1.3" 112 | resolved "https://registry.yarnpkg.com/fast-url-parser/-/fast-url-parser-1.1.3.tgz#f4af3ea9f34d8a271cf58ad2b3759f431f0b318d" 113 | integrity sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ== 114 | dependencies: 115 | punycode "^1.3.2" 116 | 117 | follow-redirects@^1.15.0: 118 | version "1.15.2" 119 | resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" 120 | integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== 121 | 122 | form-data@^4.0.0: 123 | version "4.0.0" 124 | resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" 125 | integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== 126 | dependencies: 127 | asynckit "^0.4.0" 128 | combined-stream "^1.0.8" 129 | mime-types "^2.1.12" 130 | 131 | mime-db@1.52.0: 132 | version "1.52.0" 133 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" 134 | integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== 135 | 136 | mime-db@~1.33.0: 137 | version "1.33.0" 138 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" 139 | integrity sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ== 140 | 141 | mime-types@2.1.18: 142 | version "2.1.18" 143 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8" 144 | integrity sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ== 145 | dependencies: 146 | mime-db "~1.33.0" 147 | 148 | mime-types@^2.1.12: 149 | version "2.1.35" 150 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" 151 | integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== 152 | dependencies: 153 | mime-db "1.52.0" 154 | 155 | minimatch@3.1.2: 156 | version "3.1.2" 157 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" 158 | integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== 159 | dependencies: 160 | brace-expansion "^1.1.7" 161 | 162 | path-is-inside@1.0.2: 163 | version "1.0.2" 164 | resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" 165 | integrity sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w== 166 | 167 | path-to-regexp@2.2.1: 168 | version "2.2.1" 169 | resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-2.2.1.tgz#90b617025a16381a879bc82a38d4e8bdeb2bcf45" 170 | integrity sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ== 171 | 172 | proxy-from-env@^1.1.0: 173 | version "1.1.0" 174 | resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" 175 | integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== 176 | 177 | punycode@^1.3.2: 178 | version "1.4.1" 179 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" 180 | integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ== 181 | 182 | range-parser@1.2.0: 183 | version "1.2.0" 184 | resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" 185 | integrity sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A== 186 | 187 | serve-handler@^6.1.5: 188 | version "6.1.5" 189 | resolved "https://registry.yarnpkg.com/serve-handler/-/serve-handler-6.1.5.tgz#a4a0964f5c55c7e37a02a633232b6f0d6f068375" 190 | integrity sha512-ijPFle6Hwe8zfmBxJdE+5fta53fdIY0lHISJvuikXB3VYFafRjMRpOffSPvCYsbKyBA7pvy9oYr/BT1O3EArlg== 191 | dependencies: 192 | bytes "3.0.0" 193 | content-disposition "0.5.2" 194 | fast-url-parser "1.1.3" 195 | mime-types "2.1.18" 196 | minimatch "3.1.2" 197 | path-is-inside "1.0.2" 198 | path-to-regexp "2.2.1" 199 | range-parser "1.2.0" 200 | -------------------------------------------------------------------------------- /client/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@socket.io/component-emitter@~3.1.0": 6 | version "3.1.0" 7 | resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553" 8 | integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg== 9 | 10 | "@types/cookie@^0.4.1": 11 | version "0.4.1" 12 | resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d" 13 | integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q== 14 | 15 | "@types/cors@^2.8.12": 16 | version "2.8.13" 17 | resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.13.tgz#b8ade22ba455a1b8cb3b5d3f35910fd204f84f94" 18 | integrity sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA== 19 | dependencies: 20 | "@types/node" "*" 21 | 22 | "@types/node@*", "@types/node@>=10.0.0": 23 | version "18.15.0" 24 | resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.0.tgz#286a65e3fdffd691e170541e6ecb0410b16a38be" 25 | integrity sha512-z6nr0TTEOBGkzLGmbypWOGnpSpSIBorEhC4L+4HeQ2iezKCi4f77kyslRwvHeNitymGQ+oFyIWGP96l/DPSV9w== 26 | 27 | abbrev@1: 28 | version "1.1.1" 29 | resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" 30 | integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== 31 | 32 | accepts@~1.3.4, accepts@~1.3.8: 33 | version "1.3.8" 34 | resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" 35 | integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== 36 | dependencies: 37 | mime-types "~2.1.34" 38 | negotiator "0.6.3" 39 | 40 | anymatch@~3.1.2: 41 | version "3.1.3" 42 | resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" 43 | integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== 44 | dependencies: 45 | normalize-path "^3.0.0" 46 | picomatch "^2.0.4" 47 | 48 | array-flatten@1.1.1: 49 | version "1.1.1" 50 | resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" 51 | integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== 52 | 53 | balanced-match@^1.0.0: 54 | version "1.0.2" 55 | resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" 56 | integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== 57 | 58 | base64id@2.0.0, base64id@~2.0.0: 59 | version "2.0.0" 60 | resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6" 61 | integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog== 62 | 63 | binary-extensions@^2.0.0: 64 | version "2.2.0" 65 | resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" 66 | integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== 67 | 68 | body-parser@1.20.1: 69 | version "1.20.1" 70 | resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" 71 | integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw== 72 | dependencies: 73 | bytes "3.1.2" 74 | content-type "~1.0.4" 75 | debug "2.6.9" 76 | depd "2.0.0" 77 | destroy "1.2.0" 78 | http-errors "2.0.0" 79 | iconv-lite "0.4.24" 80 | on-finished "2.4.1" 81 | qs "6.11.0" 82 | raw-body "2.5.1" 83 | type-is "~1.6.18" 84 | unpipe "1.0.0" 85 | 86 | brace-expansion@^1.1.7: 87 | version "1.1.11" 88 | resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" 89 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 90 | dependencies: 91 | balanced-match "^1.0.0" 92 | concat-map "0.0.1" 93 | 94 | braces@~3.0.2: 95 | version "3.0.2" 96 | resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" 97 | integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== 98 | dependencies: 99 | fill-range "^7.0.1" 100 | 101 | bytes@3.1.2: 102 | version "3.1.2" 103 | resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" 104 | integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== 105 | 106 | call-bind@^1.0.0: 107 | version "1.0.2" 108 | resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" 109 | integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== 110 | dependencies: 111 | function-bind "^1.1.1" 112 | get-intrinsic "^1.0.2" 113 | 114 | chokidar@^3.5.2, chokidar@^3.5.3: 115 | version "3.5.3" 116 | resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" 117 | integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== 118 | dependencies: 119 | anymatch "~3.1.2" 120 | braces "~3.0.2" 121 | glob-parent "~5.1.2" 122 | is-binary-path "~2.1.0" 123 | is-glob "~4.0.1" 124 | normalize-path "~3.0.0" 125 | readdirp "~3.6.0" 126 | optionalDependencies: 127 | fsevents "~2.3.2" 128 | 129 | concat-map@0.0.1: 130 | version "0.0.1" 131 | resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" 132 | integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== 133 | 134 | content-disposition@0.5.4: 135 | version "0.5.4" 136 | resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" 137 | integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== 138 | dependencies: 139 | safe-buffer "5.2.1" 140 | 141 | content-type@~1.0.4: 142 | version "1.0.5" 143 | resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" 144 | integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== 145 | 146 | cookie-signature@1.0.6: 147 | version "1.0.6" 148 | resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" 149 | integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== 150 | 151 | cookie@0.5.0: 152 | version "0.5.0" 153 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" 154 | integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== 155 | 156 | cookie@~0.4.1: 157 | version "0.4.2" 158 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" 159 | integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== 160 | 161 | cors@~2.8.5: 162 | version "2.8.5" 163 | resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" 164 | integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== 165 | dependencies: 166 | object-assign "^4" 167 | vary "^1" 168 | 169 | debug@2.6.9: 170 | version "2.6.9" 171 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" 172 | integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== 173 | dependencies: 174 | ms "2.0.0" 175 | 176 | debug@^3.2.7: 177 | version "3.2.7" 178 | resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" 179 | integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== 180 | dependencies: 181 | ms "^2.1.1" 182 | 183 | debug@~4.3.1, debug@~4.3.2: 184 | version "4.3.4" 185 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" 186 | integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== 187 | dependencies: 188 | ms "2.1.2" 189 | 190 | depd@2.0.0: 191 | version "2.0.0" 192 | resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" 193 | integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== 194 | 195 | destroy@1.2.0: 196 | version "1.2.0" 197 | resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" 198 | integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== 199 | 200 | dotenv@^16.0.3: 201 | version "16.0.3" 202 | resolved "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz" 203 | integrity sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ== 204 | 205 | ee-first@1.1.1: 206 | version "1.1.1" 207 | resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" 208 | integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== 209 | 210 | encodeurl@~1.0.2: 211 | version "1.0.2" 212 | resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" 213 | integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== 214 | 215 | engine.io-parser@~5.0.3: 216 | version "5.0.6" 217 | resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.0.6.tgz#7811244af173e157295dec9b2718dfe42a64ef45" 218 | integrity sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw== 219 | 220 | engine.io@~6.4.1: 221 | version "6.4.1" 222 | resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.4.1.tgz#8056b4526a88e779f9c280d820422d4e3eeaaae5" 223 | integrity sha512-JFYQurD/nbsA5BSPmbaOSLa3tSVj8L6o4srSwXXY3NqE+gGUNmmPTbhn8tjzcCtSqhFgIeqef81ngny8JM25hw== 224 | dependencies: 225 | "@types/cookie" "^0.4.1" 226 | "@types/cors" "^2.8.12" 227 | "@types/node" ">=10.0.0" 228 | accepts "~1.3.4" 229 | base64id "2.0.0" 230 | cookie "~0.4.1" 231 | cors "~2.8.5" 232 | debug "~4.3.1" 233 | engine.io-parser "~5.0.3" 234 | ws "~8.11.0" 235 | 236 | escape-html@~1.0.3: 237 | version "1.0.3" 238 | resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" 239 | integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== 240 | 241 | etag@~1.8.1: 242 | version "1.8.1" 243 | resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" 244 | integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== 245 | 246 | express@^4.18.2: 247 | version "4.18.2" 248 | resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" 249 | integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== 250 | dependencies: 251 | accepts "~1.3.8" 252 | array-flatten "1.1.1" 253 | body-parser "1.20.1" 254 | content-disposition "0.5.4" 255 | content-type "~1.0.4" 256 | cookie "0.5.0" 257 | cookie-signature "1.0.6" 258 | debug "2.6.9" 259 | depd "2.0.0" 260 | encodeurl "~1.0.2" 261 | escape-html "~1.0.3" 262 | etag "~1.8.1" 263 | finalhandler "1.2.0" 264 | fresh "0.5.2" 265 | http-errors "2.0.0" 266 | merge-descriptors "1.0.1" 267 | methods "~1.1.2" 268 | on-finished "2.4.1" 269 | parseurl "~1.3.3" 270 | path-to-regexp "0.1.7" 271 | proxy-addr "~2.0.7" 272 | qs "6.11.0" 273 | range-parser "~1.2.1" 274 | safe-buffer "5.2.1" 275 | send "0.18.0" 276 | serve-static "1.15.0" 277 | setprototypeof "1.2.0" 278 | statuses "2.0.1" 279 | type-is "~1.6.18" 280 | utils-merge "1.0.1" 281 | vary "~1.1.2" 282 | 283 | fill-range@^7.0.1: 284 | version "7.0.1" 285 | resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" 286 | integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== 287 | dependencies: 288 | to-regex-range "^5.0.1" 289 | 290 | finalhandler@1.2.0: 291 | version "1.2.0" 292 | resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" 293 | integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== 294 | dependencies: 295 | debug "2.6.9" 296 | encodeurl "~1.0.2" 297 | escape-html "~1.0.3" 298 | on-finished "2.4.1" 299 | parseurl "~1.3.3" 300 | statuses "2.0.1" 301 | unpipe "~1.0.0" 302 | 303 | forwarded@0.2.0: 304 | version "0.2.0" 305 | resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" 306 | integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== 307 | 308 | fresh@0.5.2: 309 | version "0.5.2" 310 | resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" 311 | integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== 312 | 313 | fsevents@~2.3.2: 314 | version "2.3.2" 315 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" 316 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== 317 | 318 | function-bind@^1.1.1: 319 | version "1.1.1" 320 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" 321 | integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== 322 | 323 | get-intrinsic@^1.0.2: 324 | version "1.2.0" 325 | resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.0.tgz#7ad1dc0535f3a2904bba075772763e5051f6d05f" 326 | integrity sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q== 327 | dependencies: 328 | function-bind "^1.1.1" 329 | has "^1.0.3" 330 | has-symbols "^1.0.3" 331 | 332 | glob-parent@~5.1.2: 333 | version "5.1.2" 334 | resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" 335 | integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== 336 | dependencies: 337 | is-glob "^4.0.1" 338 | 339 | has-flag@^3.0.0: 340 | version "3.0.0" 341 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" 342 | integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== 343 | 344 | has-symbols@^1.0.3: 345 | version "1.0.3" 346 | resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" 347 | integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== 348 | 349 | has@^1.0.3: 350 | version "1.0.3" 351 | resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" 352 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== 353 | dependencies: 354 | function-bind "^1.1.1" 355 | 356 | http-errors@2.0.0: 357 | version "2.0.0" 358 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" 359 | integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== 360 | dependencies: 361 | depd "2.0.0" 362 | inherits "2.0.4" 363 | setprototypeof "1.2.0" 364 | statuses "2.0.1" 365 | toidentifier "1.0.1" 366 | 367 | iconv-lite@0.4.24: 368 | version "0.4.24" 369 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" 370 | integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== 371 | dependencies: 372 | safer-buffer ">= 2.1.2 < 3" 373 | 374 | ignore-by-default@^1.0.1: 375 | version "1.0.1" 376 | resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" 377 | integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== 378 | 379 | inherits@2.0.4: 380 | version "2.0.4" 381 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 382 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 383 | 384 | ipaddr.js@1.9.1: 385 | version "1.9.1" 386 | resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" 387 | integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== 388 | 389 | is-binary-path@~2.1.0: 390 | version "2.1.0" 391 | resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" 392 | integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== 393 | dependencies: 394 | binary-extensions "^2.0.0" 395 | 396 | is-extglob@^2.1.1: 397 | version "2.1.1" 398 | resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" 399 | integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== 400 | 401 | is-glob@^4.0.1, is-glob@~4.0.1: 402 | version "4.0.3" 403 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" 404 | integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== 405 | dependencies: 406 | is-extglob "^2.1.1" 407 | 408 | is-number@^7.0.0: 409 | version "7.0.0" 410 | resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" 411 | integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== 412 | 413 | media-typer@0.3.0: 414 | version "0.3.0" 415 | resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" 416 | integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== 417 | 418 | merge-descriptors@1.0.1: 419 | version "1.0.1" 420 | resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" 421 | integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== 422 | 423 | methods@~1.1.2: 424 | version "1.1.2" 425 | resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" 426 | integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== 427 | 428 | mime-db@1.52.0: 429 | version "1.52.0" 430 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" 431 | integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== 432 | 433 | mime-types@~2.1.24, mime-types@~2.1.34: 434 | version "2.1.35" 435 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" 436 | integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== 437 | dependencies: 438 | mime-db "1.52.0" 439 | 440 | mime@1.6.0: 441 | version "1.6.0" 442 | resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" 443 | integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== 444 | 445 | minimatch@^3.1.2: 446 | version "3.1.2" 447 | resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" 448 | integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== 449 | dependencies: 450 | brace-expansion "^1.1.7" 451 | 452 | ms@2.0.0: 453 | version "2.0.0" 454 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 455 | integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== 456 | 457 | ms@2.1.2: 458 | version "2.1.2" 459 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 460 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 461 | 462 | ms@2.1.3, ms@^2.1.1: 463 | version "2.1.3" 464 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" 465 | integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== 466 | 467 | negotiator@0.6.3: 468 | version "0.6.3" 469 | resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" 470 | integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== 471 | 472 | nodemon@^2.0.21: 473 | version "2.0.21" 474 | resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.21.tgz#267edff25578da91075d6aa54346ef77ecb7b302" 475 | integrity sha512-djN/n2549DUtY33S7o1djRCd7dEm0kBnj9c7S9XVXqRUbuggN1MZH/Nqa+5RFQr63Fbefq37nFXAE9VU86yL1A== 476 | dependencies: 477 | chokidar "^3.5.2" 478 | debug "^3.2.7" 479 | ignore-by-default "^1.0.1" 480 | minimatch "^3.1.2" 481 | pstree.remy "^1.1.8" 482 | semver "^5.7.1" 483 | simple-update-notifier "^1.0.7" 484 | supports-color "^5.5.0" 485 | touch "^3.1.0" 486 | undefsafe "^2.0.5" 487 | 488 | nopt@~1.0.10: 489 | version "1.0.10" 490 | resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" 491 | integrity sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg== 492 | dependencies: 493 | abbrev "1" 494 | 495 | normalize-path@^3.0.0, normalize-path@~3.0.0: 496 | version "3.0.0" 497 | resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" 498 | integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== 499 | 500 | object-assign@^4: 501 | version "4.1.1" 502 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 503 | integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== 504 | 505 | object-inspect@^1.9.0: 506 | version "1.12.3" 507 | resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" 508 | integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== 509 | 510 | on-finished@2.4.1: 511 | version "2.4.1" 512 | resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" 513 | integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== 514 | dependencies: 515 | ee-first "1.1.1" 516 | 517 | parseurl@~1.3.3: 518 | version "1.3.3" 519 | resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" 520 | integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== 521 | 522 | path-to-regexp@0.1.7: 523 | version "0.1.7" 524 | resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" 525 | integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== 526 | 527 | picomatch@^2.0.4, picomatch@^2.2.1: 528 | version "2.3.1" 529 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" 530 | integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== 531 | 532 | proxy-addr@~2.0.7: 533 | version "2.0.7" 534 | resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" 535 | integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== 536 | dependencies: 537 | forwarded "0.2.0" 538 | ipaddr.js "1.9.1" 539 | 540 | pstree.remy@^1.1.8: 541 | version "1.1.8" 542 | resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" 543 | integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== 544 | 545 | qs@6.11.0: 546 | version "6.11.0" 547 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" 548 | integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== 549 | dependencies: 550 | side-channel "^1.0.4" 551 | 552 | range-parser@~1.2.1: 553 | version "1.2.1" 554 | resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" 555 | integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== 556 | 557 | raw-body@2.5.1: 558 | version "2.5.1" 559 | resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" 560 | integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== 561 | dependencies: 562 | bytes "3.1.2" 563 | http-errors "2.0.0" 564 | iconv-lite "0.4.24" 565 | unpipe "1.0.0" 566 | 567 | readdirp@~3.6.0: 568 | version "3.6.0" 569 | resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" 570 | integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== 571 | dependencies: 572 | picomatch "^2.2.1" 573 | 574 | safe-buffer@5.2.1: 575 | version "5.2.1" 576 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" 577 | integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== 578 | 579 | "safer-buffer@>= 2.1.2 < 3": 580 | version "2.1.2" 581 | resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" 582 | integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== 583 | 584 | semver@^5.7.1: 585 | version "5.7.1" 586 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" 587 | integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== 588 | 589 | semver@~7.0.0: 590 | version "7.0.0" 591 | resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" 592 | integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== 593 | 594 | send@0.18.0: 595 | version "0.18.0" 596 | resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" 597 | integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== 598 | dependencies: 599 | debug "2.6.9" 600 | depd "2.0.0" 601 | destroy "1.2.0" 602 | encodeurl "~1.0.2" 603 | escape-html "~1.0.3" 604 | etag "~1.8.1" 605 | fresh "0.5.2" 606 | http-errors "2.0.0" 607 | mime "1.6.0" 608 | ms "2.1.3" 609 | on-finished "2.4.1" 610 | range-parser "~1.2.1" 611 | statuses "2.0.1" 612 | 613 | serve-static@1.15.0: 614 | version "1.15.0" 615 | resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" 616 | integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== 617 | dependencies: 618 | encodeurl "~1.0.2" 619 | escape-html "~1.0.3" 620 | parseurl "~1.3.3" 621 | send "0.18.0" 622 | 623 | setprototypeof@1.2.0: 624 | version "1.2.0" 625 | resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" 626 | integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== 627 | 628 | side-channel@^1.0.4: 629 | version "1.0.4" 630 | resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" 631 | integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== 632 | dependencies: 633 | call-bind "^1.0.0" 634 | get-intrinsic "^1.0.2" 635 | object-inspect "^1.9.0" 636 | 637 | simple-update-notifier@^1.0.7: 638 | version "1.1.0" 639 | resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz#67694c121de354af592b347cdba798463ed49c82" 640 | integrity sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg== 641 | dependencies: 642 | semver "~7.0.0" 643 | 644 | socket.io-adapter@~2.5.2: 645 | version "2.5.2" 646 | resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz#5de9477c9182fdc171cd8c8364b9a8894ec75d12" 647 | integrity sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA== 648 | dependencies: 649 | ws "~8.11.0" 650 | 651 | socket.io-parser@~4.2.1: 652 | version "4.2.2" 653 | resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.2.tgz#1dd384019e25b7a3d374877f492ab34f2ad0d206" 654 | integrity sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw== 655 | dependencies: 656 | "@socket.io/component-emitter" "~3.1.0" 657 | debug "~4.3.1" 658 | 659 | socket.io@^4.6.1: 660 | version "4.6.1" 661 | resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.6.1.tgz#62ec117e5fce0692fa50498da9347cfb52c3bc70" 662 | integrity sha512-KMcaAi4l/8+xEjkRICl6ak8ySoxsYG+gG6/XfRCPJPQ/haCRIJBTL4wIl8YCsmtaBovcAXGLOShyVWQ/FG8GZA== 663 | dependencies: 664 | accepts "~1.3.4" 665 | base64id "~2.0.0" 666 | debug "~4.3.2" 667 | engine.io "~6.4.1" 668 | socket.io-adapter "~2.5.2" 669 | socket.io-parser "~4.2.1" 670 | 671 | statuses@2.0.1: 672 | version "2.0.1" 673 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" 674 | integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== 675 | 676 | supports-color@^5.5.0: 677 | version "5.5.0" 678 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" 679 | integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== 680 | dependencies: 681 | has-flag "^3.0.0" 682 | 683 | to-regex-range@^5.0.1: 684 | version "5.0.1" 685 | resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" 686 | integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== 687 | dependencies: 688 | is-number "^7.0.0" 689 | 690 | toidentifier@1.0.1: 691 | version "1.0.1" 692 | resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" 693 | integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== 694 | 695 | touch@^3.1.0: 696 | version "3.1.0" 697 | resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" 698 | integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA== 699 | dependencies: 700 | nopt "~1.0.10" 701 | 702 | type-is@~1.6.18: 703 | version "1.6.18" 704 | resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" 705 | integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== 706 | dependencies: 707 | media-typer "0.3.0" 708 | mime-types "~2.1.24" 709 | 710 | undefsafe@^2.0.5: 711 | version "2.0.5" 712 | resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" 713 | integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== 714 | 715 | unpipe@1.0.0, unpipe@~1.0.0: 716 | version "1.0.0" 717 | resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" 718 | integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== 719 | 720 | utils-merge@1.0.1: 721 | version "1.0.1" 722 | resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" 723 | integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== 724 | 725 | vary@^1, vary@~1.1.2: 726 | version "1.1.2" 727 | resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" 728 | integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== 729 | 730 | ws@~8.11.0: 731 | version "8.11.0" 732 | resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143" 733 | integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg== 734 | --------------------------------------------------------------------------------