diff --git a/package-lock.json b/package-lock.json index 6042904..ec6a3ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,24 +9,28 @@ "version": "0.0.1", "license": "UNLICENSED", "dependencies": { - "@nestjs/common": "^9.0.0", - "@nestjs/core": "^9.0.0", - "@nestjs/platform-express": "^9.0.0", - "@nestjs/platform-socket.io": "^9.4.0", - "@nestjs/websockets": "^9.4.0", + "@nestjs/common": "^10.3.7", + "@nestjs/core": "^10.3.7", + "@nestjs/platform-express": "^10.3.7", + "@nestjs/platform-ws": "^10.3.7", + "@nestjs/websockets": "^10.3.7", + "lodash": "^4.17.21", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", "rxjs": "^7.2.0", - "socket.io-client": "^4.6.1" + "uuid": "^11.0.2", + "ws": "^8.18.0" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", - "@nestjs/testing": "^9.0.0", + "@nestjs/testing": "^10.3.7", "@types/express": "^4.17.13", "@types/jest": "28.1.8", + "@types/lodash": "^4.17.13", "@types/node": "^16.0.0", "@types/supertest": "^2.0.11", + "@types/ws": "^8.5.12", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", @@ -186,12 +190,13 @@ "dev": true }, "node_modules/@babel/code-frame": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", - "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", + "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", "dev": true, "dependencies": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.24.2", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" @@ -237,23 +242,23 @@ } }, "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/generator": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.4.tgz", - "integrity": "sha512-NieM3pVIYW2SwGzKoqfPrQsf4xGs9M9AIG3ThppsSRmO+m7eQhmI6amajKMUeIO37wFfsvnvcxQFx6x6iqxDnA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.1.tgz", + "integrity": "sha512-DfCRfZsBcrPEHUfuBMgbJ1Ut01Y/itOs+hY2nFLgqsqXd52/iSiVq5TITtUasIUgm+IIKdY2/1I7auiQOEeC9A==", "dev": true, "dependencies": { - "@babel/types": "^7.21.4", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", + "@babel/types": "^7.24.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" }, "engines": { @@ -280,43 +285,43 @@ } }, "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", - "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "dependencies": { - "@babel/template": "^7.20.7", - "@babel/types": "^7.21.0" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -375,30 +380,30 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", - "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", + "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true, "engines": { "node": ">=6.9.0" @@ -428,14 +433,15 @@ } }, "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", + "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" @@ -513,9 +519,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.4.tgz", - "integrity": "sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.1.tgz", + "integrity": "sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -687,34 +693,34 @@ } }, "node_modules/@babel/template": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", - "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", + "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7" + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.24.0", + "@babel/types": "^7.24.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.4.tgz", - "integrity": "sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.21.4", - "@babel/generator": "^7.21.4", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.21.4", - "@babel/types": "^7.21.4", - "debug": "^4.1.0", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.1.tgz", + "integrity": "sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.24.1", + "@babel/generator": "^7.24.1", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.24.1", + "@babel/types": "^7.24.0", + "debug": "^4.3.1", "globals": "^11.1.0" }, "engines": { @@ -731,13 +737,13 @@ } }, "node_modules/@babel/types": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.4.tgz", - "integrity": "sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", + "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.19.4", - "@babel/helper-validator-identifier": "^7.19.1", + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -1281,14 +1287,14 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dev": true, "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" @@ -1304,9 +1310,9 @@ } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, "engines": { "node": ">=6.0.0" @@ -1329,21 +1335,15 @@ "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.18", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", - "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true - }, "node_modules/@lukeed/csprng": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz", @@ -1519,12 +1519,12 @@ } }, "node_modules/@nestjs/common": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-9.4.0.tgz", - "integrity": "sha512-RUcVAQsEF4WPrmzFXEOUfZnPwrLTe1UVlzXTlSyfqfqbdWDPKDGlIPVelBLfc5/+RRUQ0I5iE4+CQvpCmkqldw==", + "version": "10.3.7", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.3.7.tgz", + "integrity": "sha512-gKFtFzcJznrwsRYjtNZoPAvSOPYdNgxbTYoAyLTpoy393cIKgLmJTHu6ReH8/qIB9AaZLdGaFLkx98W/tFWFUw==", "dependencies": { "iterare": "1.2.1", - "tslib": "2.5.0", + "tslib": "2.6.2", "uid": "2.0.2" }, "funding": { @@ -1532,16 +1532,12 @@ "url": "https://opencollective.com/nest" }, "peerDependencies": { - "cache-manager": "<=5", "class-transformer": "*", "class-validator": "*", - "reflect-metadata": "^0.1.12", + "reflect-metadata": "^0.1.12 || ^0.2.0", "rxjs": "^7.1.0" }, "peerDependenciesMeta": { - "cache-manager": { - "optional": true - }, "class-transformer": { "optional": true }, @@ -1551,16 +1547,16 @@ } }, "node_modules/@nestjs/core": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-9.4.0.tgz", - "integrity": "sha512-yTLryCgFD0462wPe4HIzhyTcDgibt8Stfwb5YzcX7Ma0NM4m8uBIpcPG109KBubp8ZmV85e5mw4rl20qLQQVsQ==", + "version": "10.3.7", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.3.7.tgz", + "integrity": "sha512-hsdlnfiQ3kgqHL5k7js3CU0PV7hBJVi+LfFMgCkoagRxNMf67z0GFGeOV2jk5d65ssB19qdYsDa1MGVuEaoUpg==", "hasInstallScript": true, "dependencies": { "@nuxtjs/opencollective": "0.3.2", "fast-safe-stringify": "2.1.1", "iterare": "1.2.1", "path-to-regexp": "3.2.0", - "tslib": "2.5.0", + "tslib": "2.6.2", "uid": "2.0.2" }, "funding": { @@ -1568,11 +1564,11 @@ "url": "https://opencollective.com/nest" }, "peerDependencies": { - "@nestjs/common": "^9.0.0", - "@nestjs/microservices": "^9.0.0", - "@nestjs/platform-express": "^9.0.0", - "@nestjs/websockets": "^9.0.0", - "reflect-metadata": "^0.1.12", + "@nestjs/common": "^10.0.0", + "@nestjs/microservices": "^10.0.0", + "@nestjs/platform-express": "^10.0.0", + "@nestjs/websockets": "^10.0.0", + "reflect-metadata": "^0.1.12 || ^0.2.0", "rxjs": "^7.1.0" }, "peerDependenciesMeta": { @@ -1588,43 +1584,83 @@ } }, "node_modules/@nestjs/platform-express": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-9.4.0.tgz", - "integrity": "sha512-PpnfghpNq7mwG43z3+pacHulsabUCBMla4nUikntXT525ORpZSDvh/nLi1HLfE4w5+FcINc8/RBOyYTeRVmiRQ==", + "version": "10.3.7", + "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.3.7.tgz", + "integrity": "sha512-noNJ+PyIxQJLCKfuXz0tcQtlVAynfLIuKy62g70lEZ86UrIqSrZFqvWs/rFUgkbT6J8H7Rmv11hASOnX+7M2rA==", "dependencies": { "body-parser": "1.20.2", "cors": "2.8.5", - "express": "4.18.2", + "express": "4.19.2", "multer": "1.4.4-lts.1", - "tslib": "2.5.0" + "tslib": "2.6.2" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/nest" }, "peerDependencies": { - "@nestjs/common": "^9.0.0", - "@nestjs/core": "^9.0.0" + "@nestjs/common": "^10.0.0", + "@nestjs/core": "^10.0.0" } }, "node_modules/@nestjs/platform-socket.io": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-9.4.0.tgz", - "integrity": "sha512-pk5uWItnsrFKzvQrFcAmyfcb8cpGgoj4yR4+vbA5H/MLcv+8vGqruQO8riN8jAYGNPN9Y02ihBKbIvQqn92M5g==", + "version": "10.3.7", + "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.3.7.tgz", + "integrity": "sha512-T9VbVgEUnbid/RiywN9/8YQ8pAGDP++0nX73l4kIWeDWkz5DEh4aLB7O/JvLA3/xRHdjTZ4RiRZazwqSWi1Sog==", + "optional": true, + "peer": true, "dependencies": { - "socket.io": "4.6.1", - "tslib": "2.5.0" + "socket.io": "4.7.5", + "tslib": "2.6.2" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/nest" }, "peerDependencies": { - "@nestjs/common": "^9.0.0", - "@nestjs/websockets": "^9.0.0", + "@nestjs/common": "^10.0.0", + "@nestjs/websockets": "^10.0.0", "rxjs": "^7.1.0" } }, + "node_modules/@nestjs/platform-ws": { + "version": "10.3.7", + "resolved": "https://registry.npmjs.org/@nestjs/platform-ws/-/platform-ws-10.3.7.tgz", + "integrity": "sha512-lOvZ8u5UdL0FgAOdWosDXefVgDikPd4j5el81emkx+H0pFsysfHXSSoSvMLQijdhENqHSl3buRhO5n3M3uia1w==", + "dependencies": { + "tslib": "2.6.2", + "ws": "8.16.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0", + "@nestjs/websockets": "^10.0.0", + "rxjs": "^7.1.0" + } + }, + "node_modules/@nestjs/platform-ws/node_modules/ws": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/@nestjs/schematics": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-9.1.0.tgz", @@ -1641,22 +1677,22 @@ } }, "node_modules/@nestjs/testing": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-9.4.0.tgz", - "integrity": "sha512-xZWp363P4otcebg++gSjUcdCfTK0RorORzyFq3aLaSAQOlq8kxfFDRIKzEATR4aOUfqTMMsAA8lhnMJWf35N6A==", + "version": "10.3.7", + "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.3.7.tgz", + "integrity": "sha512-PmwZXyoCC/m3F3IFgpgD+SNN6cDPQa/vi3YQxFruvfX3cuHq+P6ZFvBB7hwaKKsLlhA0so42LsMm41oFBkdouw==", "dev": true, "dependencies": { - "tslib": "2.5.0" + "tslib": "2.6.2" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/nest" }, "peerDependencies": { - "@nestjs/common": "^9.0.0", - "@nestjs/core": "^9.0.0", - "@nestjs/microservices": "^9.0.0", - "@nestjs/platform-express": "^9.0.0" + "@nestjs/common": "^10.0.0", + "@nestjs/core": "^10.0.0", + "@nestjs/microservices": "^10.0.0", + "@nestjs/platform-express": "^10.0.0" }, "peerDependenciesMeta": { "@nestjs/microservices": { @@ -1668,19 +1704,19 @@ } }, "node_modules/@nestjs/websockets": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-9.4.0.tgz", - "integrity": "sha512-RATR9C0cKhXp3mTQAg75iItyUuRhVwU39Xe/kl0XLpvAhWzhnGrn6CxSTRRzBfp3F68DOKvs7/ODDY51f+rdXw==", + "version": "10.3.7", + "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.3.7.tgz", + "integrity": "sha512-iYdsWiRNPUy0XzPoW44bx2MW1griuraTr5fNhoe2rUSNO0mEW1aeXp4v56KeZDLAss31WbeckC5P3N223Fys5g==", "dependencies": { "iterare": "1.2.1", "object-hash": "3.0.0", - "tslib": "2.5.0" + "tslib": "2.6.2" }, "peerDependencies": { - "@nestjs/common": "^9.0.0", - "@nestjs/core": "^9.0.0", - "@nestjs/platform-socket.io": "^9.0.0", - "reflect-metadata": "^0.1.12", + "@nestjs/common": "^10.0.0", + "@nestjs/core": "^10.0.0", + "@nestjs/platform-socket.io": "^10.0.0", + "reflect-metadata": "^0.1.12 || ^0.2.0", "rxjs": "^7.1.0" }, "peerDependenciesMeta": { @@ -1768,7 +1804,9 @@ "node_modules/@socket.io/component-emitter": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", - "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", + "optional": true, + "peer": true }, "node_modules/@tsconfig/node10": { "version": "1.0.9", @@ -1857,7 +1895,9 @@ "node_modules/@types/cookie": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", + "optional": true, + "peer": true }, "node_modules/@types/cookiejar": { "version": "2.1.2", @@ -1866,9 +1906,11 @@ "dev": true }, "node_modules/@types/cors": { - "version": "2.8.13", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", - "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "optional": true, + "peer": true, "dependencies": { "@types/node": "*" } @@ -1971,6 +2013,13 @@ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", "dev": true }, + "node_modules/@types/lodash": { + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.13.tgz", + "integrity": "sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/mime": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", @@ -1986,7 +2035,8 @@ "node_modules/@types/node": { "version": "16.18.23", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.23.tgz", - "integrity": "sha512-XAMpaw1s1+6zM+jn2tmw8MyaRDIJfXxqmIQIS0HfoGYPuf7dUWeiUKopwq13KFX9lEp1+THGtlaaYx39Nxr58g==" + "integrity": "sha512-XAMpaw1s1+6zM+jn2tmw8MyaRDIJfXxqmIQIS0HfoGYPuf7dUWeiUKopwq13KFX9lEp1+THGtlaaYx39Nxr58g==", + "devOptional": true }, "node_modules/@types/normalize-package-data": { "version": "2.4.1", @@ -2059,6 +2109,16 @@ "@types/superagent": "*" } }, + "node_modules/@types/ws": { + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", + "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/yargs": { "version": "17.0.24", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", @@ -2882,6 +2942,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "optional": true, + "peer": true, "engines": { "node": "^4.5.0 || >= 5.9" } @@ -3408,9 +3470,9 @@ "dev": true }, "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "engines": { "node": ">= 0.6" } @@ -3483,6 +3545,7 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "devOptional": true, "dependencies": { "ms": "2.1.2" }, @@ -3696,9 +3759,11 @@ } }, "node_modules/engine.io": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.4.1.tgz", - "integrity": "sha512-JFYQurD/nbsA5BSPmbaOSLa3tSVj8L6o4srSwXXY3NqE+gGUNmmPTbhn8tjzcCtSqhFgIeqef81ngny8JM25hw==", + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.4.tgz", + "integrity": "sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==", + "optional": true, + "peer": true, "dependencies": { "@types/cookie": "^0.4.1", "@types/cors": "^2.8.12", @@ -3708,39 +3773,54 @@ "cookie": "~0.4.1", "cors": "~2.8.5", "debug": "~4.3.1", - "engine.io-parser": "~5.0.3", + "engine.io-parser": "~5.2.1", "ws": "~8.11.0" }, "engines": { - "node": ">=10.0.0" + "node": ">=10.2.0" } }, - "node_modules/engine.io-client": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.4.0.tgz", - "integrity": "sha512-GyKPDyoEha+XZ7iEqam49vz6auPnNJ9ZBfy89f+rMMas8AuiMWOZ9PVzu8xb9ZC6rafUqiGHSCfu22ih66E+1g==", - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1", - "engine.io-parser": "~5.0.3", - "ws": "~8.11.0", - "xmlhttprequest-ssl": "~2.0.0" + "node_modules/engine.io/node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "optional": true, + "peer": true, + "engines": { + "node": ">= 0.6" } }, - "node_modules/engine.io-parser": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.6.tgz", - "integrity": "sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw==", + "node_modules/engine.io/node_modules/engine.io-parser": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", + "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==", + "optional": true, + "peer": true, "engines": { "node": ">=10.0.0" } }, - "node_modules/engine.io/node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "node_modules/engine.io/node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "license": "MIT", + "optional": true, + "peer": true, "engines": { - "node": ">= 0.6" + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, "node_modules/enhanced-resolve": { @@ -3918,9 +3998,9 @@ } }, "node_modules/eslint-plugin-node/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -4208,16 +4288,16 @@ } }, "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.1", + "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -4248,29 +4328,6 @@ "node": ">= 0.10.0" } }, - "node_modules/express/node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -4289,20 +4346,6 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, - "node_modules/express/node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -5640,9 +5683,9 @@ } }, "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -6442,7 +6485,7 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "license": "MIT" }, "node_modules/lodash.memoize": { "version": "4.1.2", @@ -6527,9 +6570,9 @@ } }, "node_modules/make-dir/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -6774,7 +6817,8 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "devOptional": true }, "node_modules/multer": { "version": "1.4.4-lts.1", @@ -7609,9 +7653,9 @@ } }, "node_modules/read-pkg/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "bin": { "semver": "bin/semver" @@ -7924,9 +7968,9 @@ "dev": true }, "node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -8115,47 +8159,64 @@ } }, "node_modules/socket.io": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.6.1.tgz", - "integrity": "sha512-KMcaAi4l/8+xEjkRICl6ak8ySoxsYG+gG6/XfRCPJPQ/haCRIJBTL4wIl8YCsmtaBovcAXGLOShyVWQ/FG8GZA==", + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz", + "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==", + "optional": true, + "peer": true, "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", + "cors": "~2.8.5", "debug": "~4.3.2", - "engine.io": "~6.4.1", + "engine.io": "~6.5.2", "socket.io-adapter": "~2.5.2", - "socket.io-parser": "~4.2.1" + "socket.io-parser": "~4.2.4" }, "engines": { - "node": ">=10.0.0" + "node": ">=10.2.0" } }, "node_modules/socket.io-adapter": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", - "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.4.tgz", + "integrity": "sha512-wDNHGXGewWAjQPt3pyeYBtpWSq9cLE5UW1ZUPL/2eGK9jtse/FpXib7epSTsz0Q0m+6sg6Y4KtcFTlah1bdOVg==", + "optional": true, + "peer": true, "dependencies": { + "debug": "~4.3.4", "ws": "~8.11.0" } }, - "node_modules/socket.io-client": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.6.1.tgz", - "integrity": "sha512-5UswCV6hpaRsNg5kkEHVcbBIXEYoVbMQaHJBXJCyEQ+CiFPV1NIOY0XOFWG4XR4GZcB8Kn6AsRs/9cy9TbqVMQ==", - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.2", - "engine.io-client": "~6.4.0", - "socket.io-parser": "~4.2.1" - }, + "node_modules/socket.io-adapter/node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, "node_modules/socket.io-parser": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.2.tgz", - "integrity": "sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "optional": true, + "peer": true, "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1" @@ -8842,9 +8903,9 @@ } }, "node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/tsutils": { "version": "3.21.0", @@ -9015,6 +9076,19 @@ "node": ">= 0.4.0" } }, + "node_modules/uuid": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.2.tgz", + "integrity": "sha512-14FfcOJmqdjbBPdDjFQyk/SdT4NySW4eM0zcG+HqbHP5jzuH56xO3J1DGhgs/cEMCfwYi3HQI1gnTO62iaG+tQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, "node_modules/v8-compile-cache": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", @@ -9248,9 +9322,9 @@ } }, "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -9292,15 +9366,16 @@ } }, "node_modules/ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "license": "MIT", "engines": { "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { @@ -9311,14 +9386,6 @@ } } }, - "node_modules/xmlhttprequest-ssl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", - "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -9511,12 +9578,13 @@ } }, "@babel/code-frame": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", - "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", + "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", "dev": true, "requires": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.24.2", + "picocolors": "^1.0.0" } }, "@babel/compat-data": { @@ -9549,22 +9617,22 @@ }, "dependencies": { "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true } } }, "@babel/generator": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.4.tgz", - "integrity": "sha512-NieM3pVIYW2SwGzKoqfPrQsf4xGs9M9AIG3ThppsSRmO+m7eQhmI6amajKMUeIO37wFfsvnvcxQFx6x6iqxDnA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.1.tgz", + "integrity": "sha512-DfCRfZsBcrPEHUfuBMgbJ1Ut01Y/itOs+hY2nFLgqsqXd52/iSiVq5TITtUasIUgm+IIKdY2/1I7auiQOEeC9A==", "dev": true, "requires": { - "@babel/types": "^7.21.4", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", + "@babel/types": "^7.24.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" } }, @@ -9582,36 +9650,36 @@ }, "dependencies": { "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true } } }, "@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true }, "@babel/helper-function-name": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", - "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "requires": { - "@babel/template": "^7.20.7", - "@babel/types": "^7.21.0" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" } }, "@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" } }, "@babel/helper-module-imports": { @@ -9655,24 +9723,24 @@ } }, "@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" } }, "@babel/helper-string-parser": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", - "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", + "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", "dev": true }, "@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true }, "@babel/helper-validator-option": { @@ -9693,14 +9761,15 @@ } }, "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", + "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "dependencies": { "ansi-styles": { @@ -9762,9 +9831,9 @@ } }, "@babel/parser": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.4.tgz", - "integrity": "sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.1.tgz", + "integrity": "sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==", "dev": true }, "@babel/plugin-syntax-async-generators": { @@ -9885,31 +9954,31 @@ } }, "@babel/template": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", - "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", + "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", "dev": true, "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7" + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.24.0", + "@babel/types": "^7.24.0" } }, "@babel/traverse": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.4.tgz", - "integrity": "sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.21.4", - "@babel/generator": "^7.21.4", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.21.4", - "@babel/types": "^7.21.4", - "debug": "^4.1.0", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.1.tgz", + "integrity": "sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.24.1", + "@babel/generator": "^7.24.1", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.24.1", + "@babel/types": "^7.24.0", + "debug": "^4.3.1", "globals": "^11.1.0" }, "dependencies": { @@ -9922,13 +9991,13 @@ } }, "@babel/types": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.4.tgz", - "integrity": "sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", + "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", "dev": true, "requires": { - "@babel/helper-string-parser": "^7.19.4", - "@babel/helper-validator-identifier": "^7.19.1", + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" } }, @@ -10352,14 +10421,14 @@ } }, "@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dev": true, "requires": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" } }, "@jridgewell/resolve-uri": { @@ -10369,9 +10438,9 @@ "dev": true }, "@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true }, "@jridgewell/source-map": { @@ -10391,21 +10460,13 @@ "dev": true }, "@jridgewell/trace-mapping": { - "version": "0.3.18", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", - "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, "requires": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" - }, - "dependencies": { - "@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true - } + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "@lukeed/csprng": { @@ -10534,47 +10595,66 @@ } }, "@nestjs/common": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-9.4.0.tgz", - "integrity": "sha512-RUcVAQsEF4WPrmzFXEOUfZnPwrLTe1UVlzXTlSyfqfqbdWDPKDGlIPVelBLfc5/+RRUQ0I5iE4+CQvpCmkqldw==", + "version": "10.3.7", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.3.7.tgz", + "integrity": "sha512-gKFtFzcJznrwsRYjtNZoPAvSOPYdNgxbTYoAyLTpoy393cIKgLmJTHu6ReH8/qIB9AaZLdGaFLkx98W/tFWFUw==", "requires": { "iterare": "1.2.1", - "tslib": "2.5.0", + "tslib": "2.6.2", "uid": "2.0.2" } }, "@nestjs/core": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-9.4.0.tgz", - "integrity": "sha512-yTLryCgFD0462wPe4HIzhyTcDgibt8Stfwb5YzcX7Ma0NM4m8uBIpcPG109KBubp8ZmV85e5mw4rl20qLQQVsQ==", + "version": "10.3.7", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.3.7.tgz", + "integrity": "sha512-hsdlnfiQ3kgqHL5k7js3CU0PV7hBJVi+LfFMgCkoagRxNMf67z0GFGeOV2jk5d65ssB19qdYsDa1MGVuEaoUpg==", "requires": { "@nuxtjs/opencollective": "0.3.2", "fast-safe-stringify": "2.1.1", "iterare": "1.2.1", "path-to-regexp": "3.2.0", - "tslib": "2.5.0", + "tslib": "2.6.2", "uid": "2.0.2" } }, "@nestjs/platform-express": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-9.4.0.tgz", - "integrity": "sha512-PpnfghpNq7mwG43z3+pacHulsabUCBMla4nUikntXT525ORpZSDvh/nLi1HLfE4w5+FcINc8/RBOyYTeRVmiRQ==", + "version": "10.3.7", + "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.3.7.tgz", + "integrity": "sha512-noNJ+PyIxQJLCKfuXz0tcQtlVAynfLIuKy62g70lEZ86UrIqSrZFqvWs/rFUgkbT6J8H7Rmv11hASOnX+7M2rA==", "requires": { "body-parser": "1.20.2", "cors": "2.8.5", - "express": "4.18.2", + "express": "4.19.2", "multer": "1.4.4-lts.1", - "tslib": "2.5.0" + "tslib": "2.6.2" } }, "@nestjs/platform-socket.io": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-9.4.0.tgz", - "integrity": "sha512-pk5uWItnsrFKzvQrFcAmyfcb8cpGgoj4yR4+vbA5H/MLcv+8vGqruQO8riN8jAYGNPN9Y02ihBKbIvQqn92M5g==", + "version": "10.3.7", + "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.3.7.tgz", + "integrity": "sha512-T9VbVgEUnbid/RiywN9/8YQ8pAGDP++0nX73l4kIWeDWkz5DEh4aLB7O/JvLA3/xRHdjTZ4RiRZazwqSWi1Sog==", + "optional": true, + "peer": true, "requires": { - "socket.io": "4.6.1", - "tslib": "2.5.0" + "socket.io": "4.7.5", + "tslib": "2.6.2" + } + }, + "@nestjs/platform-ws": { + "version": "10.3.7", + "resolved": "https://registry.npmjs.org/@nestjs/platform-ws/-/platform-ws-10.3.7.tgz", + "integrity": "sha512-lOvZ8u5UdL0FgAOdWosDXefVgDikPd4j5el81emkx+H0pFsysfHXSSoSvMLQijdhENqHSl3buRhO5n3M3uia1w==", + "requires": { + "tslib": "2.6.2", + "ws": "8.16.0" + }, + "dependencies": { + "ws": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "requires": {} + } } }, "@nestjs/schematics": { @@ -10590,22 +10670,22 @@ } }, "@nestjs/testing": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-9.4.0.tgz", - "integrity": "sha512-xZWp363P4otcebg++gSjUcdCfTK0RorORzyFq3aLaSAQOlq8kxfFDRIKzEATR4aOUfqTMMsAA8lhnMJWf35N6A==", + "version": "10.3.7", + "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.3.7.tgz", + "integrity": "sha512-PmwZXyoCC/m3F3IFgpgD+SNN6cDPQa/vi3YQxFruvfX3cuHq+P6ZFvBB7hwaKKsLlhA0so42LsMm41oFBkdouw==", "dev": true, "requires": { - "tslib": "2.5.0" + "tslib": "2.6.2" } }, "@nestjs/websockets": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-9.4.0.tgz", - "integrity": "sha512-RATR9C0cKhXp3mTQAg75iItyUuRhVwU39Xe/kl0XLpvAhWzhnGrn6CxSTRRzBfp3F68DOKvs7/ODDY51f+rdXw==", + "version": "10.3.7", + "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.3.7.tgz", + "integrity": "sha512-iYdsWiRNPUy0XzPoW44bx2MW1griuraTr5fNhoe2rUSNO0mEW1aeXp4v56KeZDLAss31WbeckC5P3N223Fys5g==", "requires": { "iterare": "1.2.1", "object-hash": "3.0.0", - "tslib": "2.5.0" + "tslib": "2.6.2" } }, "@nodelib/fs.scandir": { @@ -10671,7 +10751,9 @@ "@socket.io/component-emitter": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", - "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", + "optional": true, + "peer": true }, "@tsconfig/node10": { "version": "1.0.9", @@ -10760,7 +10842,9 @@ "@types/cookie": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", + "optional": true, + "peer": true }, "@types/cookiejar": { "version": "2.1.2", @@ -10769,9 +10853,11 @@ "dev": true }, "@types/cors": { - "version": "2.8.13", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", - "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "optional": true, + "peer": true, "requires": { "@types/node": "*" } @@ -10874,6 +10960,12 @@ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", "dev": true }, + "@types/lodash": { + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.13.tgz", + "integrity": "sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==", + "dev": true + }, "@types/mime": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", @@ -10889,7 +10981,8 @@ "@types/node": { "version": "16.18.23", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.23.tgz", - "integrity": "sha512-XAMpaw1s1+6zM+jn2tmw8MyaRDIJfXxqmIQIS0HfoGYPuf7dUWeiUKopwq13KFX9lEp1+THGtlaaYx39Nxr58g==" + "integrity": "sha512-XAMpaw1s1+6zM+jn2tmw8MyaRDIJfXxqmIQIS0HfoGYPuf7dUWeiUKopwq13KFX9lEp1+THGtlaaYx39Nxr58g==", + "devOptional": true }, "@types/normalize-package-data": { "version": "2.4.1", @@ -10962,6 +11055,15 @@ "@types/superagent": "*" } }, + "@types/ws": { + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", + "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/yargs": { "version": "17.0.24", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", @@ -11545,7 +11647,9 @@ "base64id": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", - "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "optional": true, + "peer": true }, "binary-extensions": { "version": "2.2.0", @@ -11915,9 +12019,9 @@ "dev": true }, "cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==" }, "cookie-signature": { "version": "1.0.6", @@ -11978,6 +12082,7 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "devOptional": true, "requires": { "ms": "2.1.2" } @@ -12133,9 +12238,11 @@ } }, "engine.io": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.4.1.tgz", - "integrity": "sha512-JFYQurD/nbsA5BSPmbaOSLa3tSVj8L6o4srSwXXY3NqE+gGUNmmPTbhn8tjzcCtSqhFgIeqef81ngny8JM25hw==", + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.4.tgz", + "integrity": "sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==", + "optional": true, + "peer": true, "requires": { "@types/cookie": "^0.4.1", "@types/cors": "^2.8.12", @@ -12145,34 +12252,34 @@ "cookie": "~0.4.1", "cors": "~2.8.5", "debug": "~4.3.1", - "engine.io-parser": "~5.0.3", + "engine.io-parser": "~5.2.1", "ws": "~8.11.0" }, "dependencies": { "cookie": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "optional": true, + "peer": true + }, + "engine.io-parser": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", + "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==", + "optional": true, + "peer": true + }, + "ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "optional": true, + "peer": true, + "requires": {} } } }, - "engine.io-client": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.4.0.tgz", - "integrity": "sha512-GyKPDyoEha+XZ7iEqam49vz6auPnNJ9ZBfy89f+rMMas8AuiMWOZ9PVzu8xb9ZC6rafUqiGHSCfu22ih66E+1g==", - "requires": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1", - "engine.io-parser": "~5.0.3", - "ws": "~8.11.0", - "xmlhttprequest-ssl": "~2.0.0" - } - }, - "engine.io-parser": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.6.tgz", - "integrity": "sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw==" - }, "enhanced-resolve": { "version": "5.12.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", @@ -12349,9 +12456,9 @@ }, "dependencies": { "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true } } @@ -12509,16 +12616,16 @@ } }, "express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.1", + "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -12546,25 +12653,6 @@ "vary": "~1.1.2" }, "dependencies": { - "body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", - "requires": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - } - }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -12582,17 +12670,6 @@ "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" - }, - "raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } } } }, @@ -13578,9 +13655,9 @@ }, "dependencies": { "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true } } @@ -14193,8 +14270,7 @@ "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "lodash.memoize": { "version": "4.1.2", @@ -14258,9 +14334,9 @@ }, "dependencies": { "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true } } @@ -14437,7 +14513,8 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "devOptional": true }, "multer": { "version": "1.4.4-lts.1", @@ -14986,9 +15063,9 @@ } }, "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true }, "type-fest": { @@ -15263,9 +15340,9 @@ } }, "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -15421,41 +15498,48 @@ } }, "socket.io": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.6.1.tgz", - "integrity": "sha512-KMcaAi4l/8+xEjkRICl6ak8ySoxsYG+gG6/XfRCPJPQ/haCRIJBTL4wIl8YCsmtaBovcAXGLOShyVWQ/FG8GZA==", + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz", + "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==", + "optional": true, + "peer": true, "requires": { "accepts": "~1.3.4", "base64id": "~2.0.0", + "cors": "~2.8.5", "debug": "~4.3.2", - "engine.io": "~6.4.1", + "engine.io": "~6.5.2", "socket.io-adapter": "~2.5.2", - "socket.io-parser": "~4.2.1" + "socket.io-parser": "~4.2.4" } }, "socket.io-adapter": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", - "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.4.tgz", + "integrity": "sha512-wDNHGXGewWAjQPt3pyeYBtpWSq9cLE5UW1ZUPL/2eGK9jtse/FpXib7epSTsz0Q0m+6sg6Y4KtcFTlah1bdOVg==", + "optional": true, + "peer": true, "requires": { + "debug": "~4.3.4", "ws": "~8.11.0" - } - }, - "socket.io-client": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.6.1.tgz", - "integrity": "sha512-5UswCV6hpaRsNg5kkEHVcbBIXEYoVbMQaHJBXJCyEQ+CiFPV1NIOY0XOFWG4XR4GZcB8Kn6AsRs/9cy9TbqVMQ==", - "requires": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.2", - "engine.io-client": "~6.4.0", - "socket.io-parser": "~4.2.1" + }, + "dependencies": { + "ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "optional": true, + "peer": true, + "requires": {} + } } }, "socket.io-parser": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.2.tgz", - "integrity": "sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "optional": true, + "peer": true, "requires": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1" @@ -15946,9 +16030,9 @@ } }, "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "tsutils": { "version": "3.21.0", @@ -16065,6 +16149,11 @@ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" }, + "uuid": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.2.tgz", + "integrity": "sha512-14FfcOJmqdjbBPdDjFQyk/SdT4NySW4eM0zcG+HqbHP5jzuH56xO3J1DGhgs/cEMCfwYi3HQI1gnTO62iaG+tQ==" + }, "v8-compile-cache": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", @@ -16243,9 +16332,9 @@ } }, "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true }, "wrap-ansi": { @@ -16275,16 +16364,11 @@ } }, "ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "requires": {} }, - "xmlhttprequest-ssl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", - "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==" - }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index 0e01dac..d32fd2b 100644 --- a/package.json +++ b/package.json @@ -27,29 +27,34 @@ "posttest": "npm run lint" }, "dependencies": { - "@nestjs/common": "^9.0.0", - "@nestjs/core": "^9.0.0", - "@nestjs/platform-express": "^9.0.0", - "@nestjs/platform-socket.io": "^9.4.0", - "@nestjs/websockets": "^9.4.0", + "@nestjs/common": "^10.3.7", + "@nestjs/core": "^10.3.7", + "@nestjs/platform-express": "^10.3.7", + "@nestjs/platform-ws": "^10.3.7", + "@nestjs/websockets": "^10.3.7", + "lodash": "^4.17.21", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", "rxjs": "^7.2.0", - "socket.io-client": "^4.6.1" + "uuid": "^11.0.2", + "ws": "^8.18.0" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", - "@nestjs/testing": "^9.0.0", + "@nestjs/testing": "^10.3.7", "@types/express": "^4.17.13", "@types/jest": "28.1.8", + "@types/lodash": "^4.17.13", "@types/node": "^16.0.0", "@types/supertest": "^2.0.11", + "@types/ws": "^8.5.12", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", + "gts": "^3.1.1", "jest": "28.1.3", "prettier": "^2.3.2", "source-map-support": "^0.5.20", @@ -58,8 +63,7 @@ "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.0", - "typescript": "^4.7.4", - "gts": "^3.1.1" + "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ diff --git a/src/app.module.ts b/src/app.module.ts index a93eadf..f8eac6f 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -2,9 +2,10 @@ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { EventsModule } from './events/events.module'; +import { ClientModule } from './clients/client.module'; @Module({ - imports: [EventsModule], + imports: [EventsModule, ClientModule], controllers: [AppController], providers: [AppService], }) diff --git a/src/clients/client.module.ts b/src/clients/client.module.ts new file mode 100644 index 0000000..caf38f5 --- /dev/null +++ b/src/clients/client.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { ClientService } from './client.service'; + +@Module({ + providers: [ClientService], + exports: [ClientService], +}) +export class ClientModule {} diff --git a/src/clients/client.service.ts b/src/clients/client.service.ts new file mode 100644 index 0000000..daa32b3 --- /dev/null +++ b/src/clients/client.service.ts @@ -0,0 +1,86 @@ +import { Injectable } from '@nestjs/common'; +import WebSocket = require('ws'); +import { EventMessage } from '../events/events.types'; +import { SocketId, LobbyCode, ROOMMAN } from '../types/models.types'; +import { v4 as uuid } from 'uuid'; +@Injectable() +export class ClientService { + // Mapping from socketId to the lobby code for the spectators. + private clients: Record = {}; + + getSocketId(targetSocket: WebSocket): SocketId { + for (const [socketId, socket] of Object.entries(this.clients)) { + if (socket === targetSocket) return socketId; + } + throw new Error('Socket not found'); + } + + /** Sends a message to all connected clients */ + sendAll(response: EventMessage) { + for (const client of Object.values(this.clients)) { + if (client.readyState === WebSocket.OPEN) { + client.send(JSON.stringify(response)); + } + } + } + + /** Sends a message to a specific socket */ + sendSocket(response: EventMessage, socketId: SocketId) { + const socket = this.clients[socketId]; + if (!socket || socket.readyState !== WebSocket.OPEN) { + console.warn('Cannot send to socket, socket is not connected'); + return; + } + socket.send(JSON.stringify(response)); + } + + /** Sends a message to all clients in a particular lobby */ + sendLobby(response: EventMessage, code: LobbyCode) { + for (const [socketId, socket] of Object.entries(this.clients)) { + // skip clients not in the lobby + if (!ROOMMAN.isJoined(socketId, code)) return; + + if (socket.readyState === WebSocket.OPEN) { + socket.send(JSON.stringify(response)); + } + } + } + + disconnect(socketId: SocketId, reason?: string) { + console.log('Client disconnecting', socketId); + if (!this.clients[socketId]) { + console.warn(`Client ${socketId} not connected`); + return; + } + + const message: EventMessage = { + event: 'clientDisconnected', + data: { reason: reason || 'Just because' }, + }; + + const client = this.clients[socketId]; + if (!client) return; + + if (client.readyState === WebSocket.OPEN) { + client.close(1000, JSON.stringify(message)); + } + delete this.clients[socketId]; + } + + connect(socket: WebSocket): string { + // Assert we're not already connected + console.log('Client connecting'); + const entry = Object.entries(this.clients).find( + ([, value]) => socket === value, + ); + if (entry) { + console.warn(`Socket ${entry[0]} is already connected`); + return entry[0]; + } + + // Generate an id for the entry, set and return it + const socketId = uuid(); + this.clients[socketId] = socket; + return socketId; + } +} diff --git a/src/events/events.gateway.spec.ts b/src/events/events.gateway.spec.ts index a66c606..b3aaf8a 100644 --- a/src/events/events.gateway.spec.ts +++ b/src/events/events.gateway.spec.ts @@ -1,116 +1,329 @@ import { INestApplication } from '@nestjs/common'; import { Test } from '@nestjs/testing'; import { AppModule } from '../app.module'; -import { io, Socket } from 'socket.io-client'; -import { LobbyInfo, LOBBYMAN } from '../types/models.types'; +import { LOBBYMAN } from '../types/models.types'; +import { + CreateLobbyData, + LeaveLobbyPayload, + LobbyLeftPayload, + LobbySearchedPayload, + LobbySpectatedPayload, + ResponseStatusPayload, + EventMessage, + SearchLobbyPayload, + SpectateLobbyPayload, + UpdateMachinePayload, + SelectSongPayload, +} from './events.types'; +import { WebSocket } from 'ws'; +import { WsAdapter } from '@nestjs/platform-ws'; +import { omit } from 'lodash'; + +const port = 3001; describe('EventsGateway', () => { let app: INestApplication; - let socket: Socket; + let client: WebSocket; beforeAll(async () => { const moduleRef = await Test.createTestingModule({ imports: [AppModule], }).compile(); - app = moduleRef.createNestApplication(); - // Run tests on a different port than 3000 (the default for this app). - await app.listen(3001); + app.useWebSocketAdapter(new WsAdapter(app)); + await app.init(); + await app.listen(port); }); - beforeEach(() => { + beforeEach(async () => { // Clear out data before every test. LOBBYMAN.lobbies = {}; LOBBYMAN.machineConnections = {}; LOBBYMAN.spectatorConnections = {}; - socket = io('http://localhost:3001'); - socket.connect(); + // Create a new client + client = new WebSocket('ws://localhost:' + port); + client.on('error', (error) => { + console.error('WebSocket Error:', error); + }); + await new Promise((resolve) => { + client.on('open', resolve); + }); }); describe('generalLobbyUsage', () => { it('createLobby', async () => { - const code = await new Promise((resolve) => { - socket.emit( - 'createLobby', - { machine: { player1: { playerName: 'teejusb' } } }, - (data: string) => { - expect(data.length).toEqual(4); - resolve(data); + const create = await send(client, { + event: 'createLobby', + data: { + machine: { + player1: { + playerId: 'P1', + profileName: 'teejusb', + screenName: 'ScreenSelectMusic', + ready: false, + }, }, - ); + password: '', + }, }); + expect(create.event).toBe('lobbyState'); + expect(create.data).toHaveProperty('code'); + + const search = await send( + client, + { + event: 'searchLobby', + data: {}, + }, + ); + expect(search.event).toBe('lobbySearched'); + expect(search.data.lobbies.length).toBe(1); + const code = Object.keys(LOBBYMAN.lobbies)[0]; + expect(search.data.lobbies[0].code).toBe(code); + expect(search.data.lobbies[0].playerCount).toBe(1); + expect(search.data.lobbies[0].spectatorCount).toBe(0); + + const spectate = await send( + client, + { + event: 'spectateLobby', + data: { + spectator: { + profileName: 'E.Norma', + }, + code: search.data.lobbies[0].code, + password: '', + }, + }, + ); + expect(spectate.event).toBe('lobbySpectated'); + expect(spectate.data.spectators).toBe(0); // Spectate should fail as a player can't also be a spectator. + + const client2 = new WebSocket('ws://localhost:' + port); await new Promise((resolve) => { - socket.emit('searchLobby', (data: LobbyInfo[]) => { - expect(data.length).toEqual(1); - expect(data[0].code).toEqual(code); - expect(data[0].playerCount).toEqual(1); - expect(data[0].spectatorCount).toEqual(0); - resolve(undefined); - }); + client2.on('open', resolve); }); - await new Promise((resolve) => { - socket.emit( - 'spectateLobby', - { code: code, password: '' }, - (spectatorCount: number) => { - // Spectate should fail as a player can't also be a spectator. - expect(spectatorCount).toEqual(0); - resolve(undefined); + const spectate2 = await send( + client2, + { + event: 'spectateLobby', + data: { + spectator: { + profileName: 'Brat', + }, + code: search.data.lobbies[0].code, + password: '', }, - ); + }, + ); + expect(spectate2.event).toBe('lobbySpectated'); + expect(spectate2.data.spectators).toBe(1); // socket2 is a different connection, so we can spectate now. + + const search2 = await send( + client, + { + event: 'searchLobby', + data: {}, + }, + ); + expect(search2.event).toBe('lobbySearched'); + expect(search2.data.lobbies.length).toBe(1); + expect(search2.data.lobbies[0].code).toBe(code); + expect(search2.data.lobbies[0].playerCount).toBe(1); + expect(search2.data.lobbies[0].spectatorCount).toBe(1); + + const leave = await send(client, { + event: 'leaveLobby', + data: {}, }); + expect(leave.event).toBe('lobbyLeft'); + expect(leave.data.left).toBeTruthy(); - const socket2 = io('http://localhost:3001'); - socket2.connect(); + const search3 = await send( + client, + { + event: 'searchLobby', + data: {}, + }, + ); + expect(search3.event).toBe('lobbySearched'); + expect(search3.data.lobbies.length).toBe(0); - await new Promise((resolve) => { - socket2.emit( - 'spectateLobby', - { code: code, password: '' }, - (spectatorCount: number) => { - // socket2 is a different connection, so we can spectate now. - expect(spectatorCount).toEqual(1); - resolve(undefined); + client2.close(); + }); + + it('updateMachine', async () => { + await send(client, { + event: 'createLobby', + data: { + machine: { + player1: { + playerId: 'P1', + profileName: 'teejusb', + screenName: 'ScreenSelectMusic', + ready: false, + }, }, - ); + password: '', + }, }); - - await new Promise((resolve) => { - socket.emit('searchLobby', (data: LobbyInfo[]) => { - expect(data.length).toEqual(1); - expect(data[0].code).toEqual(code); - expect(data[0].playerCount).toEqual(1); - expect(data[0].spectatorCount).toEqual(1); - resolve(undefined); - }); + const update1: UpdateMachinePayload = { + machine: { + player1: { + playerId: 'P1', + profileName: 'teejusb', + screenName: 'ScreenGameplay', + ready: false, + score: 0.99, + songProgression: { + currentTime: 1, + totalTime: 2, + }, + }, + player2: { + playerId: 'P2', + profileName: 'Moistbruh', + screenName: 'ScreenGameplay', + ready: false, + score: 0.99, + }, + }, + }; + await send(client, { + event: 'updateMachine', + data: update1, }); + const lobby = Object.values(LOBBYMAN.lobbies)[0]; + const machine = Object.values(lobby.machines)[0]; + expect(omit(machine, 'socketId')).toEqual(update1.machine); - await new Promise((resolve) => { - socket.emit('leaveLobby', {}, (didLeave: boolean) => { - expect(didLeave).toEqual(true); - resolve(undefined); - }); + // If one player goes back to SongSelect (I know, technically not possible for a single machine) + // The songInfo/scores should persist + const update2: UpdateMachinePayload = { + machine: { + player1: { + playerId: 'P1', + profileName: 'teejusb', + screenName: 'ScreenSelectMusic', + ready: false, + }, + player2: { + playerId: 'P2', + profileName: 'Moistbruh', + screenName: 'ScreenGameplay', + ready: false, + score: 0.99, + }, + }, + }; + await send(client, { + event: 'updateMachine', + data: update2, }); - await new Promise((resolve) => { - socket.emit('searchLobby', (data: LobbyInfo[]) => { - expect(data.length).toEqual(0); - resolve(undefined); - }); + expect(machine.player1).toBeDefined(); + expect(machine.player1?.screenName).toEqual('ScreenSelectMusic'); + expect(machine.player1?.score).toBeDefined(); + expect(machine.player1?.songProgression).toBeDefined(); + + // Now we go back to select music, it should wipe songInfo/scores + const update3: UpdateMachinePayload = { + machine: { + player1: { + playerId: 'P1', + profileName: 'teejusb', + screenName: 'ScreenSelectMusic', + ready: false, + }, + player2: { + playerId: 'P2', + profileName: 'Moistbruh', + screenName: 'ScreenSelectMusic', + ready: false, + }, + }, + }; + await send(client, { + event: 'updateMachine', + data: update3, }); - socket2.disconnect(); + expect(machine.player1).toBeDefined(); + expect(machine.player1?.screenName).toEqual('ScreenSelectMusic'); + expect(machine.player1?.score).toBeUndefined(); + expect(machine.player1?.songProgression).toBeUndefined(); + }); + }); + + it('selectSong', async () => { + await send(client, { + event: 'createLobby', + data: { + machine: { + player1: { + playerId: 'P1', + profileName: 'teejusb', + screenName: 'ScreenSelectMusic', + ready: false, + }, + }, + password: '', + }, + }); + + // Initially no song + const [, lobby] = Object.entries(LOBBYMAN.lobbies)[0]; + expect(lobby.songInfo).toBeUndefined(); + + const payload: SelectSongPayload = { + songInfo: { + songPath: '11 guys/wowie', + songLength: 42069, + title: 'WOWIE', + artist: 'the guys', + }, + }; + + // First song sets song info + await send(client, { + event: 'selectSong', + data: payload, }); + expect(lobby.songInfo).toEqual(payload.songInfo); + + // Second one will fail + payload.songInfo.title = 'Updated'; + const second = await send( + client, + { + event: 'selectSong', + data: payload, + }, + ); + expect(second.data.success).toBe(false); + expect(lobby.songInfo?.title).toEqual('WOWIE'); }); afterEach(() => { - socket.disconnect(); + client.close(); }); afterAll(async () => { await app.close(); }); }); + +function send( + client: WebSocket, + message: EventMessage, +): Promise> { + return new Promise((resolve) => { + client.on('message', (response: EventMessage) => { + resolve(JSON.parse(response.toString())); + }); + client.send(JSON.stringify(message)); + }); +} diff --git a/src/events/events.gateway.ts b/src/events/events.gateway.ts index fac136c..c390cc9 100644 --- a/src/events/events.gateway.ts +++ b/src/events/events.gateway.ts @@ -1,86 +1,181 @@ import { - ConnectedSocket, - MessageBody, - SubscribeMessage, + OnGatewayConnection, + OnGatewayDisconnect, WebSocketGateway, - WebSocketServer, } from '@nestjs/websockets'; -import { Server, Socket } from 'socket.io'; -import { LOBBYMAN, LobbyInfo, Machine, Spectator } from '../types/models.types'; +import { WebSocket } from 'ws'; import { - DisconnectMachine, - DisconnectSpectator, - CanJoinLobby, - GenerateLobbyCode, - GetPlayerCountForLobby, + LOBBYMAN, + LobbyCode, + LobbyInfo, + Player, + ROOMMAN, + SocketId, +} from '../types/models.types'; +import { + disconnectSpectator, + canJoinLobby, + generateLobbyCode, + getPlayerCountForLobby, + RETAINED_PLAYER_KEYS, + inSongSelect, + responseStatusFailure, } from './utils'; +import { + CreateLobbyData, + JoinLobbyPayload, + LobbyLeftPayload, + LobbySearchedPayload, + ResponseStatusPayload, + EventMessage, + EventType, + SpectateLobbyPayload, + UpdateMachinePayload, + SelectSongPayload, + LobbyStatePayload, +} from './events.types'; +import { merge, pick } from 'lodash'; + +import { ClientService } from '../clients/client.service'; @WebSocketGateway({ cors: { origin: '*', }, }) -export class EventsGateway { - @WebSocketServer() - server: Server; +export class EventsGateway implements OnGatewayConnection, OnGatewayDisconnect { + /** Maps received message types to a callback function to handle those message. + * The callback function may return a message to send to the calling socket */ + private handlers: Partial< + Record< + EventType, + (socketId: SocketId, payload: any) => Promise + > + >; + + constructor(private readonly clients: ClientService) {} + + afterInit() { + this.handlers = { + createLobby: this.createLobby, + joinLobby: this.joinLobby, + leaveLobby: this.leaveLobby, + spectateLobby: this.spectateLobby, + searchLobby: this.searchLobby, + updateMachine: this.updateMachine, + lobbyState: this.lobbyState, + selectSong: this.selectSong, + }; + } + + /** + * Listener to handle new websocket connections. Responsible for notifying our CLIENTS manager + * and setting up callbacks to handle incoming messages */ + handleConnection(socket: WebSocket) { + const socketId = this.clients.connect(socket); + + socket.on('message', async (messageBuffer: Buffer) => { + try { + const messageString = messageBuffer.toString(); + let message: EventMessage; + try { + message = JSON.parse(messageString); + } catch (e) { + console.error('Error parsing message', messageString); + return; + } + + if (!message.event) { + return; + } + if (!this.handlers[message.event]) { + throw new Error(`No handler for message type "${message.event}"`); + } + const handler = this.handlers[message.event]; + if (!handler) { + throw new Error('Missing handler'); // Should not happen, but makes TS happy + } + // Retain "this" context within the handler callbacks (otherwise we lose this.clients) + const handlerBinded = handler.bind(this); + const response = await handlerBinded(socketId, message.data); + if (response) { + this.clients.sendSocket(response, socketId); + } + } catch (e) { + console.error('Error handling message', e); + } + }); + } /** * Cleans up the lobby manager when a client disconnects. - * @param client, The socket that disconnected. + * @param socket, The socket id that disconnected. + * @override OnGatewayDisconnect */ - handleDisconnect(client: Socket) { - if (client.id in LOBBYMAN.machineConnections) { - DisconnectMachine(client.id); + handleDisconnect(socket: WebSocket) { + let socketId: SocketId; + try { + socketId = this.clients.getSocketId(socket); + } catch (e) { + console.error('Disconnect not handled, socketId not found for socket'); + return; + } + console.info('Disconnecting socket ' + socketId); + + this.clients.disconnect(socketId); + + if (socketId in LOBBYMAN.machineConnections) { + this.disconnectMachine(socketId); } - if (client.id in LOBBYMAN.spectatorConnections) { - DisconnectSpectator(client.id); + if (socketId in LOBBYMAN.spectatorConnections) { + disconnectSpectator(socketId); } } /** * Creates a new lobby and connects a machine to it. - * @param client, The socket that connected. + * @param socketId, The socket that connected. * @param machine, The machine that connected. * @param password, The password for the lobby (empty implies public lobby). * @returns, The code for the newly created lobby. */ - @SubscribeMessage('createLobby') async createLobby( - @ConnectedSocket() client: Socket, - @MessageBody('machine') machine: Machine, - @MessageBody('password') password: string, - ): Promise { - if (client.id in LOBBYMAN.spectatorConnections) { - DisconnectSpectator(client.id); + socketId: string, + { machine, password }: CreateLobbyData, + ): Promise { + if (socketId in LOBBYMAN.spectatorConnections) { + disconnectSpectator(socketId); } - if (client.id in LOBBYMAN.machineConnections) { + if (socketId in LOBBYMAN.machineConnections) { // A machine can only join one lobby at a time. - DisconnectMachine(client.id); + this.disconnectMachine(socketId); } - let code = GenerateLobbyCode(); + let code = generateLobbyCode(); while (code in LOBBYMAN.lobbies) { - code = GenerateLobbyCode(); + code = generateLobbyCode(); } LOBBYMAN.lobbies[code] = { - code: code, - password: password ? password : '', + code, + password: password || '', machines: { - [client.id]: { + [socketId]: { ...machine, - socket: client, + socketId, }, }, spectators: {}, }; - client.join(code); - LOBBYMAN.machineConnections[client.id] = code; - console.log('Created lobby ' + code); + console.log('Created lobby', { code }); + + ROOMMAN.join(socketId, code); + LOBBYMAN.machineConnections[socketId] = code; - return code; + this.broadcastLobbyState(code); + return undefined; } /** @@ -91,33 +186,124 @@ export class EventsGateway { * @param password, The password for the lobby. * @returns True if the machine joined the lobby, false otherwise. */ - @SubscribeMessage('joinLobby') async joinLobby( - @ConnectedSocket() client: Socket, - @MessageBody('machine') machine: Machine, - @MessageBody('code') code: string, - @MessageBody('password') password: string, - ): Promise { - if (CanJoinLobby(code, password)) { - if (client.id in LOBBYMAN.spectatorConnections) { - DisconnectSpectator(client.id); - } + socketId: SocketId, + { machine, code, password }: JoinLobbyPayload, + ): Promise | undefined> { + if (!canJoinLobby(code, password)) { + return { + event: 'responseStatus', + data: { + event: 'joinLobby', + success: false, + message: + 'Cannot join lobby. Check the code + password and try again.', + }, + }; + } - if (client.id in LOBBYMAN.machineConnections) { - // A machine can only join one lobby at a time. - DisconnectMachine(client.id); - } + if (socketId in LOBBYMAN.spectatorConnections) { + disconnectSpectator(socketId); + } - const lobby = LOBBYMAN.lobbies[code]; - lobby.machines[client.id] = { - ...machine, - socket: client, - }; - LOBBYMAN.machineConnections[client.id] = code; - console.log('Machine ' + `${client.id}` + 'joined ' + `${code}`); - return true; + if (socketId in LOBBYMAN.machineConnections) { + // A machine can only join one lobby at a time. + this.disconnectMachine(socketId); + } + + const lobby = LOBBYMAN.lobbies[code]; + if (Object.keys(lobby.machines).length >= 4) { + responseStatusFailure('joinLobby', 'Too many machines in the lobby'); } - return false; + + if (lobby.songInfo) { + responseStatusFailure( + 'joinLobby', + 'A song is already selected, please try later.', + ); + } + + lobby.machines[socketId] = { + ...machine, + socketId, + }; + ROOMMAN.join(socketId, code); + LOBBYMAN.machineConnections[socketId] = code; + + this.broadcastLobbyState(code); + + return undefined; + } + + /** + * Updates a machine + */ + async updateMachine( + socketId: SocketId, + { machine }: UpdateMachinePayload, + ): Promise | undefined> { + const code = LOBBYMAN.machineConnections[socketId]; + if (!code) { + return responseStatusFailure('updateMachine', 'Machine not found'); + } + const lobby = LOBBYMAN.lobbies[code]; + + // Merge the incoming machine data with the respective lobby's machine + const playersInSongSelectBefore = inSongSelect(lobby); + merge(lobby.machines[socketId], machine); + const playersInSongSelectAfter = inSongSelect(lobby); + + // If all players have transitioned back to song select, + // Ensure the scores and currently-selected song get reset + if (!playersInSongSelectBefore && playersInSongSelectAfter) { + lobby.songInfo = undefined; + Object.values(lobby.machines).forEach((machine) => { + // Only retain relevant fields + if (machine.player1) { + machine.player1 = pick(machine.player1, RETAINED_PLAYER_KEYS); + } + if (machine.player2) { + machine.player2 = pick(machine.player2, RETAINED_PLAYER_KEYS); + } + }); + } + + this.broadcastLobbyState(lobby.code); + return undefined; + } + + /** + * Updates a machine + */ + async lobbyState( + socketId: SocketId, + ): Promise | undefined> { + const code = LOBBYMAN.machineConnections[socketId]; + if (!code) { + return responseStatusFailure('lobbyState', 'Machine not found'); + } + this.broadcastLobbyState(code); + + return undefined; + } + + async selectSong( + socketId: SocketId, + { songInfo }: SelectSongPayload, + ): Promise | undefined> { + const code = LOBBYMAN.machineConnections[socketId]; + if (!code) { + return responseStatusFailure('selectSong', 'Machine not found'); + } + const lobby = LOBBYMAN.lobbies[code]; + if (lobby.songInfo) { + return responseStatusFailure('selectSong', 'Song already selected'); + } + lobby.songInfo = songInfo; + + this.broadcastLobbyState(code); + + return undefined; } /** @@ -125,65 +311,160 @@ export class EventsGateway { * @param client, The socket connection of the machine to disconnect. * @returns, True if the machine was disconnected successfully. */ - @SubscribeMessage('leaveLobby') - async leaveLobby(@ConnectedSocket() client: Socket): Promise { - return DisconnectMachine(client.id); + async leaveLobby( + socketId: SocketId, + {}, + ): Promise> { + let left = false; + left = this.disconnectMachine(socketId); + return { event: 'lobbyLeft', data: { left } }; } /** * Connects a spectator to an existing lobby. - * @param client, The socket that connected. + * @param socketId, The socket that connected. * @param spectator, The spectator to connect to a lobby. * @param code, The code for the lobby to spectate. * @param password, The password for the lobby. * @returns, The number of spectators in the lobby. */ - @SubscribeMessage('spectateLobby') async spectateLobby( - @ConnectedSocket() client: Socket, - @MessageBody('spectator') spectator: Spectator, - @MessageBody('code') code: string, - @MessageBody('password') password: string, - ): Promise { - const lobby = LOBBYMAN.lobbies[code]; - if (lobby) { - if ( - !(client.id in LOBBYMAN.machineConnections) && - CanJoinLobby(code, password) - ) { - if (client.id in LOBBYMAN.spectatorConnections) { - // A spectator can only spectate one lobby at a time. - DisconnectSpectator(client.id); - } + socketId: SocketId, + { spectator, code, password }: SpectateLobbyPayload, + ): Promise | undefined> { + if (!canJoinLobby(code, password)) { + return responseStatusFailure( + 'spectateLobby', + 'No lobby found with code ' + code, + ); + } - lobby.spectators[client.id] = { - ...spectator, - socket: client, - }; - client.join(code); - LOBBYMAN.spectatorConnections[client.id] = code; - } - return Object.keys(lobby.spectators).length; + if (socketId in LOBBYMAN.spectatorConnections) { + // A spectator can only spectate one lobby at a time. + disconnectSpectator(socketId); } - return 0; + const lobby = LOBBYMAN.lobbies[code.toUpperCase()]; + lobby.spectators[socketId] = { + ...spectator, + socketId, + }; + ROOMMAN.join(socketId, code); + LOBBYMAN.spectatorConnections[socketId] = code; + + // Broadcasts an updated spectator count to all machines + // and the initial lobby state for the newly-added spectator + this.broadcastLobbyState(code); + + return undefined; } /** * Searches for all active lobbies. * @returns, The list of lobbies that are currently active. */ - @SubscribeMessage('searchLobby') - async searchLobby(): Promise { - const lobbyInfo: LobbyInfo[] = []; - for (const lobby of Object.values(LOBBYMAN.lobbies)) { - lobbyInfo.push({ - code: lobby.code, - isPasswordProtected: lobby.password.length !== 0, - playerCount: GetPlayerCountForLobby(lobby), - spectatorCount: Object.keys(lobby.spectators).length, - }); + async searchLobby(): Promise> { + const lobbies: LobbyInfo[] = Object.values(LOBBYMAN.lobbies).map((l) => ({ + code: l.code, + isPasswordProtected: l.password.length !== 0, + playerCount: getPlayerCountForLobby(l), + spectatorCount: Object.keys(l.spectators).length, + })); + return { event: 'lobbySearched', data: { lobbies } }; + } + + private broadcastLobbyState(code: LobbyCode) { + const lobby = this.getLobbyState(code); + if (lobby) { + this.clients.sendLobby(lobby, code); + } + } + + private getLobbyState( + code: LobbyCode, + ): EventMessage | null { + // Send back the machine state with the socket ids omitted + const players: Player[] = []; + const lobby = LOBBYMAN.lobbies[code]; + Object.values(lobby.machines).forEach((machine) => { + const { player1, player2 } = machine; + if (player1) { + players.push(player1); + } + if (player2) { + players.push(player2); + } + }); + const { songInfo } = lobby; + + return { + event: 'lobbyState', + data: { + players: players.sort((p1, p2) => { + if (p1.exScore && p2.exScore) { + return p2.exScore - p1.exScore; + } + return p1.profileName > p2.profileName ? 1 : -1; + }), + songInfo, + code, + }, + }; + } + + /** + * Makes a machine leave a lobby. If the machine was the last player in the + * lobby, the lobby will be deleted and all spectators will be disconnected. + * @param socketId, The socket ID of the machine to disconnect. + * @returns True if the machine left the lobby, false otherwise. + */ + private disconnectMachine(socketId: SocketId): boolean { + const code = LOBBYMAN.machineConnections[socketId]; + if (code === undefined) { + return false; + } + + const lobby = LOBBYMAN.lobbies[code]; + if (lobby === undefined) { + return false; + } + + const machine = lobby.machines[socketId]; + if (machine === undefined) { + return false; + } + + if (machine.socketId) { + if (machine.socketId in LOBBYMAN.machineConnections) { + delete LOBBYMAN.machineConnections[machine.socketId]; + } + + ROOMMAN.leave(machine.socketId, code); + + // Don't disconnect here, as we may be re-using the connection. + // In the case of `leaveLobby`, the client can manually disconnect. + } + delete lobby.machines[socketId]; + delete LOBBYMAN.machineConnections[socketId]; + + if (getPlayerCountForLobby(lobby) === 0) { + for (const spectator of Object.values(lobby.spectators)) { + if (spectator.socketId) { + ROOMMAN.leave(spectator.socketId, code); + // Force a disconnect. If there are no more players in the lobby, + // we should remove the spectators as well. + this.clients.disconnect(spectator.socketId); + delete LOBBYMAN.spectatorConnections[spectator.socketId]; + } + } + delete ROOMMAN.rooms[code]; + delete LOBBYMAN.lobbies[code]; + } else { + // When a client disconnects, notify other clients + const stateMessage = this.getLobbyState(code); + if (stateMessage) { + this.clients.sendLobby(stateMessage, code); + } } - console.log('Found ' + lobbyInfo.length + ' lobbies'); - return lobbyInfo; + return true; } } diff --git a/src/events/events.module.ts b/src/events/events.module.ts index 2b2d1cb..74bd8d1 100644 --- a/src/events/events.module.ts +++ b/src/events/events.module.ts @@ -1,7 +1,8 @@ import { Module } from '@nestjs/common'; import { EventsGateway } from './events.gateway'; +import { ClientService } from '../clients/client.service'; @Module({ - providers: [EventsGateway], + providers: [EventsGateway, ClientService], }) export class EventsModule {} diff --git a/src/events/events.types.ts b/src/events/events.types.ts new file mode 100644 index 0000000..a7c37ad --- /dev/null +++ b/src/events/events.types.ts @@ -0,0 +1,126 @@ +import { + LobbyCode, + LobbyInfo, + Machine, + Player, + PlayerId, + SongInfo, + Spectator, +} from '../types/models.types'; + +export type EventType = + | 'createLobby' + | 'joinLobby' + | 'lobbyJoined' + | 'updateMachine' + | 'machineUpdated' + | 'leaveLobby' + | 'lobbyLeft' + | 'spectateLobby' + | 'lobbySpectated' + | 'searchLobby' + | 'lobbySearched' + | 'clientDisconnected' + | 'readyUp' + | 'readyUpResult' + | 'lobbyState' + | 'selectSong' + | 'responseStatus' + | 'startSong'; + +export type EventData = + | CreateLobbyData + | JoinLobbyPayload + | LobbyJoinedPayload + | UpdateMachinePayload + | LeaveLobbyPayload + | LobbyLeftPayload + | SearchLobbyPayload + | LobbySearchedPayload + | ClientDisconnectedPayload + | ReadyUpPayload + | ReadyUpResultPayload + | LobbyStatePayload + | SelectSongPayload + | StartSongPayload; + +export interface EventMessage { + event: EventType; + data: T; +} + +export interface CreateLobbyData { + machine: Omit; + password: string; +} + +export interface JoinLobbyPayload { + machine: Omit; + code: LobbyCode; + password: string; +} + +export interface LobbyJoinedPayload { + joined: boolean; + message?: string; +} + +export interface UpdateMachinePayload { + machine: Omit; +} + +export interface ResponseStatusPayload { + event: EventType; + success: boolean; + message?: string; +} + +export interface SelectSongPayload { + songInfo: SongInfo; +} + +export interface SpectateLobbyPayload { + spectator: Spectator; + code: LobbyCode; + password: string; +} + +export interface LobbySpectatedPayload { + spectators: number; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface SearchLobbyPayload {} + +export interface LobbySearchedPayload { + lobbies: LobbyInfo[]; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface LeaveLobbyPayload {} + +export interface LobbyLeftPayload { + left: boolean; +} + +export interface ReadyUpPayload { + playerId: PlayerId; +} + +export interface ReadyUpResultPayload { + ready: boolean; +} + +export interface ClientDisconnectedPayload { + reason: string; +} + +export interface LobbyStatePayload { + players: Array; + code: LobbyCode; + songInfo?: SongInfo; +} + +export interface StartSongPayload { + start: boolean; +} diff --git a/src/events/utils.ts b/src/events/utils.ts index da233f3..8f5c5ff 100644 --- a/src/events/utils.ts +++ b/src/events/utils.ts @@ -1,5 +1,20 @@ -import { SocketId } from 'socket.io-adapter'; -import { LOBBYMAN, Lobby } from '../types/models.types'; +import { + LOBBYMAN, + Lobby, + Player, + ROOMMAN, + SocketId, +} from '../types/models.types'; +import { EventMessage, EventType, ResponseStatusPayload } from './events.types'; + +/** Keys retained when the game state is reset. + * @see updateMachine */ +export const RETAINED_PLAYER_KEYS: Array = [ + 'playerId', + 'profileName', + 'screenName', + 'ready', +]; /** * Determines if the correct credentials are provided to join a lobby. @@ -8,15 +23,17 @@ import { LOBBYMAN, Lobby } from '../types/models.types'; * @returns True if the lobby exists and the password is correct, false * otherwise. */ -export function CanJoinLobby(code: string, password: string) { +export function canJoinLobby(code: string, password: string) { // Does the lobby we're trying to join exist? - if (code in LOBBYMAN.lobbies) { - const lobby = LOBBYMAN.lobbies[code]; - // Join either if the lobby is public, or one has provided a valid - // password for a private lobby. - if (!lobby.password || lobby.password === password) { - return true; - } + const lobby = LOBBYMAN.lobbies[code.toUpperCase()]; + if (lobby === undefined) { + return false; + } + + // Join either if the lobby is public, or one has provided a valid + // password for a private lobby. + if (!lobby.password || lobby.password === password) { + return true; } return false; } @@ -25,7 +42,7 @@ export function CanJoinLobby(code: string, password: string) { * Generates a random lobby code. * @returns A random lobby code of 4 uppercase characters. */ -export function GenerateLobbyCode(): string { +export function generateLobbyCode(): string { const lobbyCodeLength = 4; const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; let result = ''; @@ -40,7 +57,7 @@ export function GenerateLobbyCode(): string { * @param lobby, The lobby to get the player count for. * @returns The number of players in the lobby. */ -export function GetPlayerCountForLobby(lobby: Lobby): number { +export function getPlayerCountForLobby(lobby: Lobby): number { let playerCount = 0; for (const machine of Object.values(lobby.machines)) { if (machine.player1 !== undefined) { @@ -54,70 +71,89 @@ export function GetPlayerCountForLobby(lobby: Lobby): number { } /** - * Makes a machine leave a lobby. If the machine was the last player in the - * lobby, the lobby will be deleted and all spectators will be disconnected. - * @param socketId, The socket ID of the machine to disconnect. - * @returns True if the machine left the lobby, false otherwise. + * Makes a spectator leave a lobby. + * @param socketId, The socket ID of the spectator to disconnect. + * @returns, True if the spectator left the lobby, false otherwise. */ -export function DisconnectMachine(socketId: SocketId): boolean { - const code = LOBBYMAN.machineConnections[socketId]; - if (code) { - const lobby = LOBBYMAN.lobbies[code]; - if (lobby) { - const machine = lobby.machines[socketId]; - if (machine) { - if (machine.socket) { - if (machine.socket.id in LOBBYMAN.machineConnections) { - delete LOBBYMAN.machineConnections[machine.socket.id]; - } +export function disconnectSpectator(socketId: SocketId): boolean { + const code = LOBBYMAN.spectatorConnections[socketId]; + if (code === undefined) { + return false; + } - machine.socket.leave(code); - // Don't disconnect here, as we may be re-using the connection. - // In the case of `leaveLobby`, the client can manually disconnect. - } - delete lobby.machines[socketId]; - delete LOBBYMAN.machineConnections[socketId]; + const lobby = LOBBYMAN.lobbies[code]; + if (lobby === undefined) { + return false; + } - if (GetPlayerCountForLobby(lobby) === 0) { - for (const spectator of Object.values(lobby.spectators)) { - if (spectator.socket) { - spectator.socket.leave(code); - // Force a disconnect. If there are no more players in the lobby, - // we should remove the spectators as well. - spectator.socket.disconnect(); - delete LOBBYMAN.spectatorConnections[spectator.socket.id]; - } - } - delete LOBBYMAN.lobbies[code]; - } - return true; - } - } + const spectator = lobby.spectators[socketId]; + if (spectator === undefined) { + return false; } - return false; + + if (spectator.socketId) { + ROOMMAN.leave(spectator.socketId, code); + // Don't disconnect here, as we may be re-using the connection. + } + delete lobby.spectators[socketId]; + delete LOBBYMAN.spectatorConnections[socketId]; + return true; +} + +/** Gets the lobby for specific connection. + * @returns, lobby if the machine is part of one, or undefined otherwise. + */ +export function getLobbyForMachine(socketId: SocketId): Lobby | undefined { + const code = LOBBYMAN.machineConnections[socketId]; + if (code === undefined) { + return undefined; + } + + return LOBBYMAN.lobbies[code]; } /** - * Makes a spectator leave a lobby. - * @param socketId, The socket ID of the spectator to disconnect. - * @returns, True if the spectator left the lobby, false otherwise. + * Constructs a ResponseStatus event with a success or fail. */ -export function DisconnectSpectator(socketId: SocketId): boolean { - const code = LOBBYMAN.spectatorConnections[socketId]; - if (code) { - const lobby = LOBBYMAN.lobbies[code]; - if (lobby) { - const spectator = lobby.spectators[socketId]; - if (spectator) { - if (spectator.socket) { - spectator.socket.leave(code); - // Don't disconnect here, as we may be re-using the connection. - } - delete lobby.spectators[socketId]; - delete LOBBYMAN.spectatorConnections[socketId]; - return true; - } +export function responseStatus( + event: EventType, + success: boolean, + message?: string, +): EventMessage { + return { + event: 'responseStatus', + data: { + event, + success, + message, + }, + }; +} + +/** + * Constructs a Response status event with a failure. + * @param event + * @param message + * @returns + */ +export function responseStatusFailure( + event: EventType, + message: string, +): EventMessage { + return responseStatus(event, false, message); +} + +export function inSongSelect(lobby: Lobby): boolean { + let selecting = true; + Object.values(lobby.machines).forEach(({ player1, player2 }) => { + if (player1 && player1.screenName !== 'ScreenSelectMusic') { + selecting = false; + return; } - } - return false; + if (player2 && player2.screenName !== 'ScreenSelectMusic') { + selecting = false; + return; + } + }); + return selecting; } diff --git a/src/main.ts b/src/main.ts index c07c8e5..25b1400 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,14 +1,18 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { LOBBYMAN } from './types/models.types'; +import { WsAdapter } from '@nestjs/platform-ws'; async function bootstrap() { const app = await NestFactory.create(AppModule); + app.useWebSocketAdapter(new WsAdapter(app)); LOBBYMAN.lobbies = {}; LOBBYMAN.machineConnections = {}; LOBBYMAN.spectatorConnections = {}; await app.listen(3000); + + console.log(`Application is running on: ${await app.getUrl()}`); } bootstrap(); diff --git a/src/types/models.types.ts b/src/types/models.types.ts index 1cddc4a..0b7a6be 100644 --- a/src/types/models.types.ts +++ b/src/types/models.types.ts @@ -1,7 +1,10 @@ -import { Socket } from 'socket.io'; -import { SocketId } from 'socket.io-adapter'; +export type SocketId = string; -export class Judgments { +export type LobbyCode = string; + +export type PlayerId = 'P1' | 'P2'; + +export interface Judgments { fantasticPlus: number; fantastics: number; excellents: number; @@ -18,12 +21,12 @@ export class Judgments { totalRolls: number; } -export class Spectator { +export interface Spectator { profileName: string; - socket?: Socket; + socketId?: SocketId; } -export class SongInfo { +export interface SongInfo { // The path for the song on a player's filesystem. // We'll use this as a key for other players to play. // e.g. 5guys1pack/Earthquake @@ -41,27 +44,37 @@ export class SongInfo { songPath: string; title: string; artist: string; - stepartist: string; songLength: number; } -export class Player { - playerId: string; +export interface Player { + playerId: PlayerId; profileName: string; + screenName: + | 'NoScreen' + | 'ScreenSelectMusic' + | 'ScreenGameplay' + | 'ScreenPlayerOptions' + | 'ScreenEvaluation'; + ready: boolean; judgments?: Judgments; score?: number; exScore?: number; + songProgression?: { + currentTime: number; + totalTime: number; + }; } -export class Machine { +export interface Machine { player1?: Player; player2?: Player; - socket?: Socket; + socketId?: SocketId; } -export class Lobby { - code: string; +export interface Lobby { + code: LobbyCode; // Empty string here is equivalent to "no password". We could use undefined // but we can consider them the same. password: string; @@ -71,8 +84,8 @@ export class Lobby { songInfo?: SongInfo; } -export class LobbyInfo { - code: string; +export interface LobbyInfo { + code: LobbyCode; isPasswordProtected: boolean; playerCount: number; spectatorCount: number; @@ -80,11 +93,51 @@ export class LobbyInfo { export class LOBBYMAN { // Mapping from lobby code to a Lobby - static lobbies: Record; + static lobbies: Record; // Mapping from socketId to the lobby code of the lobby it's connected to. - static machineConnections: Record; + static machineConnections: Record; // Mapping from socketId to the lobby code for the spectators. - static spectatorConnections: Record; + static spectatorConnections: Record; +} + +export class ROOMMAN { + // Mapping of lobby ids (rooms) to the socketIds in that room + public static rooms: Record> = {}; + + static join(socketId: SocketId, code: LobbyCode) { + if (!this.rooms[code]) { + this.rooms[code] = []; + } + const sockets = this.rooms[code]; + if (sockets.includes(socketId)) { + console.warn(`Socket ${socketId} is already in room ${code}`); + return; + } + console.info(`Socket ${socketId} is joining room ${code}`); + sockets.push(socketId); + } + + static leave(socketId: SocketId, code: LobbyCode) { + if (!this.rooms[code]) { + console.warn(`No room for code ${code}`); + return; + } + const sockets = this.rooms[code]; + if (!sockets) { + throw new Error('No socket with code ' + code); // Shouldn't happen, since we set the code right before this + } + if (!sockets.includes(socketId)) { + console.warn(`Socket ${socketId} is not in room ${code}`); + return; + } + console.info(`Socket ${socketId} is leaving room ${code}`); + this.rooms[code] = sockets.filter((s) => s !== socketId); + } + + static isJoined(socketId: SocketId, code: LobbyCode): boolean { + if (!this.rooms[code]) return false; + return Boolean(this.rooms[code].includes(socketId)); + } }