diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 3bf00b99..e33346d0 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -199,4 +199,4 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - msg: Environment deployed at https://play-${{ env.GITHUB_HEAD_REF_SLUG }}.test.workadventu.re + msg: "Environment deployed at https://play-${{ env.GITHUB_HEAD_REF_SLUG }}.test.workadventu.re \nTests available at https://maps-${{ env.GITHUB_HEAD_REF_SLUG }}.test.workadventu.re/tests" diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index 2036e4e6..faf50c7a 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -64,6 +64,11 @@ jobs: run: yarn test working-directory: "front" + # We will enable prettier checks on front in a few month, when most PRs without prettier have been merged + # - name: "Prettier" + # run: yarn run pretty-check + # working-directory: "front" + continuous-integration-pusher: name: "Continuous Integration Pusher" @@ -107,6 +112,10 @@ jobs: run: yarn test working-directory: "pusher" + - name: "Prettier" + run: yarn run pretty-check + working-directory: "pusher" + continuous-integration-back: name: "Continuous Integration Back" @@ -150,3 +159,7 @@ jobs: run: yarn test working-directory: "back" + - name: "Prettier" + run: yarn run pretty-check + working-directory: "back" + diff --git a/.github/workflows/push-to-npm.yml b/.github/workflows/push-to-npm.yml index 798e2530..fd247b11 100644 --- a/.github/workflows/push-to-npm.yml +++ b/.github/workflows/push-to-npm.yml @@ -2,6 +2,7 @@ name: Push @workadventure/iframe-api-typings to NPM on: release: types: [created] + push: jobs: build: runs-on: ubuntu-latest @@ -13,10 +14,6 @@ jobs: node-version: '14.x' registry-url: 'https://registry.npmjs.org' - - name: Edit tsconfig.json to add declarations - run: "sed -i 's/\"declaration\": false/\"declaration\": true/g' tsconfig.json" - working-directory: "front" - - name: Replace version number run: 'sed -i "s#VERSION_PLACEHOLDER#${GITHUB_REF/refs\/tags\//}#g" package.json' working-directory: "front/packages/iframe-api-typings" @@ -47,15 +44,18 @@ jobs: working-directory: "front" - name: "Build" - run: yarn run build + run: yarn run build-typings env: - API_URL: "localhost:8080" + PUSHER_URL: "//localhost:8080" working-directory: "front" # We build the front to generate the typings of iframe_api, then we copy those typings in a separate package. - name: Copy typings to package dir run: cp front/dist/src/iframe_api.d.ts front/packages/iframe-api-typings/iframe_api.d.ts + - name: Copy typings to package dir (2) + run: cp -R front/dist/src/Api front/packages/iframe-api-typings/Api + - name: Install dependencies in package run: yarn install working-directory: "front/packages/iframe-api-typings" @@ -65,3 +65,4 @@ jobs: working-directory: "front/packages/iframe-api-typings" env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + if: ${{ github.event_name == 'release' }} diff --git a/.gitignore b/.gitignore index 2cbf66b1..8fa69985 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ docker-compose.override.yaml maps/yarn.lock maps/dist/computer.js maps/dist/computer.js.map -/node_modules/ +node_modules +_ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 41f72510..fa3dd293 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,19 @@ - Use `WA.room.getCurrentUser(): Promise` to get the ID, name and tags of the current player - Use `WA.room.getCurrentRoom(): Promise` to get the ID, JSON map file, url of the map of the current room and the layer where the current player started - Use `WA.ui.registerMenuCommand(): void` to add a custom menu + - Use `WA.room.setTiles(): void` to change an array of tiles + +## Version 1.4.3 - 1.4.4 - 1.4.5 + +## Bugfixes + +- Fixing the generation of @workadventure/iframe-api-typings + +## Version 1.4.2 + +## Updates + +- A script in an iframe opened by another script can use the IFrame API. ## Version 1.4.1 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e5cd50dc..8bbbc93e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,10 +42,19 @@ Before committing, be sure to install the "Prettier" precommit hook that will re In order to enable the "Prettier" precommit hook, at the root of the project, run: ```console -$ yarn run install +$ yarn install $ yarn run prepare ``` +If you don't have the precommit hook installed (or if you committed code before installing the precommit hook), you will need +to run code linting manually: + +```console +$ docker-compose exec front yarn run pretty +$ docker-compose exec pusher yarn run pretty +$ docker-compose exec back yarn run pretty +``` + ### Providing tests WorkAdventure is based on a video game engine (Phaser), and video games are not the easiest programs to unit test. diff --git a/back/package-lock.json b/back/package-lock.json new file mode 100644 index 00000000..aa922074 --- /dev/null +++ b/back/package-lock.json @@ -0,0 +1,4479 @@ +{ + "name": "workadventureback", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.14.5" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", + "dev": true + }, + "@babel/highlight": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@mrmlnc/readdir-enhanced": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", + "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", + "dev": true, + "requires": { + "call-me-maybe": "^1.0.1", + "glob-to-regexp": "^0.3.0" + } + }, + "@nodelib/fs.stat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", + "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", + "dev": true + }, + "@types/busboy": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@types/busboy/-/busboy-0.2.3.tgz", + "integrity": "sha1-ZpetKYcyRsUw8Jo/9aQIYYJCMNU=", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/bytebuffer": { + "version": "5.0.42", + "resolved": "https://registry.npmjs.org/@types/bytebuffer/-/bytebuffer-5.0.42.tgz", + "integrity": "sha512-lEgKojWUAc/MG2t649oZS5AfYFP2xRNPoDuwDBlBMjHXd8MaGPgFgtCXUK7inZdBOygmVf10qxc1Us8GXC96aw==", + "requires": { + "@types/long": "*", + "@types/node": "*" + } + }, + "@types/circular-json": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@types/circular-json/-/circular-json-0.4.0.tgz", + "integrity": "sha512-7+kYB7x5a7nFWW1YPBh3KxhwKfiaI4PbZ1RvzBU91LZy7lWJO822CI+pqzSre/DZ7KsCuMKdHnLHHFu8AyXbQg==", + "dev": true + }, + "@types/debug": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz", + "integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==", + "dev": true + }, + "@types/eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==", + "dev": true + }, + "@types/google-protobuf": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/@types/google-protobuf/-/google-protobuf-3.7.3.tgz", + "integrity": "sha512-FRwj40euE2bYkG+0X5w2nEA8yAzgJRcEa7RBd0Gsdkb9/tPM2pctVVAvnOUTbcXo2VmIHPo0Ae94Gl9vRHfKzg==", + "dev": true + }, + "@types/http-status-codes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/http-status-codes/-/http-status-codes-1.2.0.tgz", + "integrity": "sha512-vjpjevMaxtrtdrrV/TQNIFT7mKL8nvIKG7G/LjMDZdVvqRxRg5SNfGkeuSaowVc0rbK8xDA2d/Etunyb5GyzzA==", + "dev": true, + "requires": { + "http-status-codes": "*" + } + }, + "@types/jasmine": { + "version": "3.5.14", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.5.14.tgz", + "integrity": "sha512-Fkgk536sHPqcOtd+Ow+WiUNuk0TSo/BntKkF8wSvcd6M2FvPjeXcUE6Oz/bwDZiUZEaXLslAgw00Q94Pnx6T4w==", + "dev": true + }, + "@types/json-schema": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", + "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", + "dev": true + }, + "@types/jsonwebtoken": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.0.tgz", + "integrity": "sha512-9bVao7LvyorRGZCw0VmH/dr7Og+NdjYSsKAxB43OQoComFbBgsEpoR9JW6+qSq/ogwVBg8GI2MfAlk4SYI4OLg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/long": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", + "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" + }, + "@types/mkdirp": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/mkdirp/-/mkdirp-1.0.1.tgz", + "integrity": "sha512-HkGSK7CGAXncr8Qn/0VqNtExEE+PHMWb+qlR1faHMao7ng6P3tAaoWWBMdva0gL5h4zprjIO89GJOLXsMcDm1Q==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/node": { + "version": "14.11.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.2.tgz", + "integrity": "sha512-jiE3QIxJ8JLNcb1Ps6rDbysDhN4xa8DJJvuC9prr6w+1tIh+QAbYyNF3tyiZNLDBIuBCf4KEcV2UvQm/V60xfA==" + }, + "@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, + "@types/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-FKjsOVbC6B7bdSB5CuzyHCkK69I=", + "dev": true + }, + "@types/strip-json-comments": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz", + "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", + "dev": true + }, + "@types/uuid": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.0.tgz", + "integrity": "sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ==" + }, + "@types/uuidv4": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/uuidv4/-/uuidv4-5.0.0.tgz", + "integrity": "sha512-xUrhYSJnkTq9CP79cU3svoKTLPCIbMMnu9Twf/tMpHATYSHCAAeDNeb2a/29YORhk5p4atHhCTMsIBU/tvdh6A==", + "dev": true, + "requires": { + "uuidv4": "*" + } + }, + "@typescript-eslint/eslint-plugin": { + "version": "2.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.34.0.tgz", + "integrity": "sha512-4zY3Z88rEE99+CNvTbXSyovv2z9PNOVffTWD2W8QF5s2prBQtwN2zadqERcrHpcR7O/+KMI3fcTAmUUhK/iQcQ==", + "dev": true, + "requires": { + "@typescript-eslint/experimental-utils": "2.34.0", + "functional-red-black-tree": "^1.0.1", + "regexpp": "^3.0.0", + "tsutils": "^3.17.1" + } + }, + "@typescript-eslint/experimental-utils": { + "version": "2.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.34.0.tgz", + "integrity": "sha512-eS6FTkq+wuMJ+sgtuNTtcqavWXqsflWcfBnlYhg/nS4aZ1leewkXGbvBhaapn1q6qf4M71bsR1tez5JTRMuqwA==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/typescript-estree": "2.34.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" + } + }, + "@typescript-eslint/parser": { + "version": "2.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.34.0.tgz", + "integrity": "sha512-03ilO0ucSD0EPTw2X4PntSIRFtDPWjrVq7C3/Z3VQHRC7+13YB55rcJI3Jt+YgeHbjUdJPcPa7b23rXCBokuyA==", + "dev": true, + "requires": { + "@types/eslint-visitor-keys": "^1.0.0", + "@typescript-eslint/experimental-utils": "2.34.0", + "@typescript-eslint/typescript-estree": "2.34.0", + "eslint-visitor-keys": "^1.1.0" + } + }, + "@typescript-eslint/typescript-estree": { + "version": "2.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.34.0.tgz", + "integrity": "sha512-OMAr+nJWKdlVM9LOqCqh3pQQPwxHAN7Du8DR6dmwCrAmxtiXQnhHJ6tBNtf+cggqfo51SG/FCwnKhXCIM7hnVg==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "eslint-visitor-keys": "^1.1.0", + "glob": "^7.1.6", + "is-glob": "^4.0.1", + "lodash": "^4.17.15", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + }, + "dependencies": { + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "acorn-jsx": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", + "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", + "dev": true + }, + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "requires": { + "type-fest": "^0.21.3" + }, + "dependencies": { + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true + } + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "ascli": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ascli/-/ascli-1.0.1.tgz", + "integrity": "sha1-vPpZdKYvGOgcq660lzKrSoj5Brw=", + "requires": { + "colour": "~0.7.1", + "optjs": "~3.2.2" + } + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "axios": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", + "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", + "requires": { + "follow-redirects": "^1.10.0" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "bintrees": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.1.tgz", + "integrity": "sha1-DmVcm5wkNeqraL9AJyJtK1WjRSQ=" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "busboy": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.3.1.tgz", + "integrity": "sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw==", + "requires": { + "dicer": "0.3.0" + } + }, + "bytebuffer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/bytebuffer/-/bytebuffer-5.0.1.tgz", + "integrity": "sha1-WC7qSxqHO20CCkjVjfhfC7ps/d0=", + "requires": { + "long": "~3" + } + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "call-me-maybe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", + "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=", + "dev": true + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=" + }, + "camelcase-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "dev": true, + "requires": { + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "chokidar": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", + "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "dependencies": { + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } + } + }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "circular-json": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.5.9.tgz", + "integrity": "sha512-4ivwqHpIFJZBuhN3g/pEcdbnGUywkBblloGbkglyloVjjR3uT6tieI89MVOfbP2tHX5sgb01FuLgAOzebNlJNQ==" + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "requires": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + } + } + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "colorette": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", + "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", + "dev": true + }, + "colour": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/colour/-/colour-0.7.1.tgz", + "integrity": "sha1-nLFpkX7F0SwHNtPoaFdG3xyt93g=" + }, + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cosmiconfig": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", + "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", + "dev": true, + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "dev": true, + "requires": { + "array-find-index": "^1.0.1" + } + }, + "dateformat": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz", + "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=", + "dev": true, + "requires": { + "get-stdin": "^4.0.1", + "meow": "^3.3.0" + } + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "requires": { + "ms": "2.1.2" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" + }, + "dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", + "dev": true + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" + }, + "dicer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz", + "integrity": "sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==", + "requires": { + "streamsearch": "0.1.2" + } + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "dynamic-dedupe": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz", + "integrity": "sha1-BuRMIj9eTpTXjvnbI6ZRXOL5YqE=", + "dev": true, + "requires": { + "xtend": "^4.0.0" + } + }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.1" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "eslint": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", + "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.10.0", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^1.4.3", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.1.2", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^7.0.0", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.14", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.3", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "eslint-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + } + } + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + }, + "espree": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", + "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "acorn-jsx": "^5.2.0", + "eslint-visitor-keys": "^1.1.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-glob": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.7.tgz", + "integrity": "sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw==", + "dev": true, + "requires": { + "@mrmlnc/readdir-enhanced": "^2.2.1", + "@nodelib/fs.stat": "^1.1.2", + "glob-parent": "^3.1.0", + "is-glob": "^4.0.0", + "merge2": "^1.2.3", + "micromatch": "^3.1.10" + }, + "dependencies": { + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + } + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "requires": { + "flat-cache": "^2.0.1" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + }, + "dependencies": { + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "dev": true + }, + "follow-redirects": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz", + "integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==" + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "fs-minipass": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", + "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "requires": { + "minipass": "^2.6.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, + "generic-type-guard": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/generic-type-guard/-/generic-type-guard-3.3.3.tgz", + "integrity": "sha512-SXraZvNW/uTfHVgB48iEwWaD1XFJ1nvZ8QP6qy9pSgaScEyQqFHYN5E6d6rCsJgrvlWKygPrNum7QeJHegzNuQ==" + }, + "get-own-enumerable-property-symbols": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", + "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", + "dev": true + }, + "get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "dev": true + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "glob-to-regexp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", + "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=", + "dev": true + }, + "globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, + "google-protobuf": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.13.0.tgz", + "integrity": "sha512-ZIf3qfLFayVrPvAjeKKxO5FRF1/NwRxt6Dko+fWEMuHwHbZx8/fcaAao9b0wCM6kr8qeg2te8XTpyuvKuD9aKw==" + }, + "graceful-fs": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", + "dev": true + }, + "grpc": { + "version": "1.24.4", + "resolved": "https://registry.npmjs.org/grpc/-/grpc-1.24.4.tgz", + "integrity": "sha512-mHRAwuitCMuSHo1tp1+Zc0sz3cYa7pkhVJ77pkIXD5gcVORtkRiyW6msXYqTDT+35jazg98lbO3XzuTo2+XrcA==", + "requires": { + "@types/bytebuffer": "^5.0.40", + "lodash.camelcase": "^4.3.0", + "lodash.clone": "^4.5.0", + "nan": "^2.13.2", + "node-pre-gyp": "^0.16.0", + "protobufjs": "^5.0.3" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "http-status-codes": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.1.4.tgz", + "integrity": "sha512-MZVIsLKGVOVE1KEnldppe6Ij+vmemMuApDfjhVSLzyYP+td0bREEYyAoIw9yFePoBXManCuBqmiNP5FqJS5Xkg==", + "dev": true + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "ignore-walk": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.4.tgz", + "integrity": "sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ==", + "requires": { + "minimatch": "^3.0.4" + } + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "inquirer": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", + "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.19", + "mute-stream": "0.0.8", + "run-async": "^2.4.0", + "rxjs": "^6.6.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-core-module": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz", + "integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-finite": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", + "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", + "dev": true + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=", + "dev": true + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true + }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "jasmine": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-3.6.1.tgz", + "integrity": "sha512-Jqp8P6ZWkTVFGmJwBK46p+kJNrZCdqkQ4GL+PGuBXZwK1fM4ST9BizkYgIwCFqYYqnTizAy6+XG2Ej5dFrej9Q==", + "dev": true, + "requires": { + "fast-glob": "^2.2.6", + "jasmine-core": "~3.6.0" + } + }, + "jasmine-core": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.6.0.tgz", + "integrity": "sha512-8uQYa7zJN8hq9z+g8z1bqCfdC8eoDAeVnM5sfqs7KHv9/ifoJ500m018fpFc7RDaO6SWCLCXwo/wPSNcdYTgcw==", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "requires": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + } + }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "requires": { + "invert-kv": "^1.0.0" + } + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "dev": true + }, + "lint-staged": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-11.0.0.tgz", + "integrity": "sha512-3rsRIoyaE8IphSUtO1RVTFl1e0SLBtxxUOPBtHxQgBHS5/i6nqvjcUfNioMa4BU9yGnPzbO+xkfLtXtxBpCzjw==", + "dev": true, + "requires": { + "chalk": "^4.1.1", + "cli-truncate": "^2.1.0", + "commander": "^7.2.0", + "cosmiconfig": "^7.0.0", + "debug": "^4.3.1", + "dedent": "^0.7.0", + "enquirer": "^2.3.6", + "execa": "^5.0.0", + "listr2": "^3.8.2", + "log-symbols": "^4.1.0", + "micromatch": "^4.0.4", + "normalize-path": "^3.0.0", + "please-upgrade-node": "^3.2.0", + "string-argv": "0.3.1", + "stringify-object": "^3.3.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } + } + }, + "listr2": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.10.0.tgz", + "integrity": "sha512-eP40ZHihu70sSmqFNbNy2NL1YwImmlMmPh9WO5sLmPDleurMHt3n+SwEWNu2kzKScexZnkyFtc1VI0z/TGlmpw==", + "dev": true, + "requires": { + "cli-truncate": "^2.1.0", + "colorette": "^1.2.2", + "log-update": "^4.0.0", + "p-map": "^4.0.0", + "rxjs": "^6.6.7", + "through": "^2.3.8", + "wrap-ansi": "^7.0.0" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + }, + "dependencies": { + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + } + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" + }, + "lodash.clone": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz", + "integrity": "sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y=" + }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "log-update": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", + "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "dev": true, + "requires": { + "ansi-escapes": "^4.3.0", + "cli-cursor": "^3.1.0", + "slice-ansi": "^4.0.0", + "wrap-ansi": "^6.2.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + } + } + }, + "long": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", + "integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=" + }, + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "dev": true, + "requires": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + }, + "dependencies": { + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "dev": true, + "requires": { + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" + } + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "minipass": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", + "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "requires": { + "minipass": "^2.9.0" + } + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "nan": { + "version": "2.14.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", + "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==" + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "needle": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.7.0.tgz", + "integrity": "sha512-b4f4JgOl7GZVM1p+xuWBAsHwflng1s2yOu9lOThKAzULRW7eqSFYfN4gbuUFOMuE0hVAPWJnSz/90LMOlEGErw==", + "requires": { + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node-pre-gyp": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.16.0.tgz", + "integrity": "sha512-4efGA+X/YXAHLi1hN8KaPrILULaUn2nWecFrn1k2I+99HpoyvcOGEbtcOxpDiUwPF2ZANMJDh32qwOUPenuR1g==", + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.3", + "needle": "^2.5.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4.4.2" + }, + "dependencies": { + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "requires": { + "minimist": "^1.2.5" + } + } + } + }, + "nopt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", + "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "npm-bundled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.2.tgz", + "integrity": "sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ==", + "requires": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-normalize-package-bin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==" + }, + "npm-packlist": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", + "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1", + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + }, + "dependencies": { + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + } + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "optjs": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/optjs/-/optjs-3.2.2.tgz", + "integrity": "sha1-aabOicRCpEQDFBrS+bNwvVu29O4=" + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "requires": { + "lcid": "^1.0.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "please-upgrade-node": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", + "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", + "dev": true, + "requires": { + "semver-compare": "^1.0.0" + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "prettier": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.1.tgz", + "integrity": "sha512-p+vNbgpLjif/+D+DwAZAbndtRrR0md0MwfmOVN9N+2RgyACMT+7tfaRnT+WDPkqnuVwleyuBIG2XBxKDme3hPA==", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "prom-client": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-12.0.0.tgz", + "integrity": "sha512-JbzzHnw0VDwCvoqf8y1WDtq4wSBAbthMB1pcVI/0lzdqHGJI3KBJDXle70XK+c7Iv93Gihqo0a5LlOn+g8+DrQ==", + "requires": { + "tdigest": "^0.1.1" + } + }, + "protobufjs": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-5.0.3.tgz", + "integrity": "sha512-55Kcx1MhPZX0zTbVosMQEO5R6/rikNXd9b6RQK4KSPcrSIIwoXTtebIczUrXlwaSrbz4x8XUVThGPob1n8I4QA==", + "requires": { + "ascli": "~1", + "bytebuffer": "~5", + "glob": "^7.0.5", + "yargs": "^3.10.0" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "query-string": { + "version": "6.13.4", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.13.4.tgz", + "integrity": "sha512-E2NPIeJoBEJGQNy3ib1k/Z/OkDBUKIo8IV2ZVwbKfoa65IS9unqWWUlLcbfU70Da0qNoxUZZA8CfKUjKLE641Q==", + "requires": { + "decode-uri-component": "^0.2.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + } + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + }, + "dependencies": { + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + } + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "dev": true, + "requires": { + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" + }, + "dependencies": { + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + } + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, + "repeat-element": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", + "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, + "requires": { + "is-finite": "^1.0.0" + } + }, + "resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "requires": { + "glob": "^7.1.3" + } + }, + "run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true + }, + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" + }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + } + } + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "dev": true, + "requires": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "source-map-url": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", + "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", + "dev": true + }, + "spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.9.tgz", + "integrity": "sha512-Ki212dKK4ogX+xDo4CtOZBVIwhsKBEfsEEcwmJfLQzirgc2jIWdzg40Unxz/HzEUqM1WFzVlQSMF9kZZ2HboLQ==", + "dev": true + }, + "split-on-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==" + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "streamsearch": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", + "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" + }, + "strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=" + }, + "string-argv": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", + "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", + "dev": true + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "dev": true, + "requires": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + } + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "dev": true, + "requires": { + "get-stdin": "^4.0.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "systeminformation": { + "version": "4.31.1", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-4.31.1.tgz", + "integrity": "sha512-dVCDWNMN8ncMZo5vbMCA5dpAdMgzafK2ucuJy5LFmGtp1cG6farnPg8QNvoOSky9SkFoEX1Aw0XhcOFV6TnLYA==" + }, + "table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + }, + "dependencies": { + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + } + } + }, + "tar": { + "version": "4.4.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", + "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + }, + "dependencies": { + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "requires": { + "minimist": "^1.2.5" + } + } + } + }, + "tdigest": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.1.tgz", + "integrity": "sha1-Ljyyw56kSeVdHmzZEReszKRYgCE=", + "requires": { + "bintrees": "1.0.1" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true + }, + "ts-node": { + "version": "8.10.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.10.2.tgz", + "integrity": "sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA==", + "dev": true, + "requires": { + "arg": "^4.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.17", + "yn": "3.1.1" + } + }, + "ts-node-dev": { + "version": "1.0.0-pre.63", + "resolved": "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-1.0.0-pre.63.tgz", + "integrity": "sha512-KURricXsXtiB4R+NCgiKgE01wyTe/GlXTdAPIhliDhF3kCn00kzyepAc1H8kbUJCmz0oYQq/GQ6CMtiWovs9qg==", + "dev": true, + "requires": { + "chokidar": "^3.4.0", + "dateformat": "~1.0.4-1.2.3", + "dynamic-dedupe": "^0.3.0", + "minimist": "^1.2.5", + "mkdirp": "^1.0.4", + "resolve": "^1.0.0", + "rimraf": "^2.6.1", + "source-map-support": "^0.5.12", + "tree-kill": "^1.2.2", + "ts-node": "^8.10.2", + "tsconfig": "^7.0.0" + } + }, + "tsconfig": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz", + "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==", + "dev": true, + "requires": { + "@types/strip-bom": "^3.0.0", + "@types/strip-json-comments": "0.0.30", + "strip-bom": "^3.0.0", + "strip-json-comments": "^2.0.0" + }, + "dependencies": { + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + } + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + }, + "typescript": { + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", + "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==", + "dev": true + }, + "uWebSockets.js": { + "version": "github:uNetworking/uWebSockets.js#9b1605d2db82981cafe69dbe356e10ce412f5805", + "from": "github:uNetworking/uWebSockets.js#v18.5.0" + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + } + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + } + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "uuid": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.0.tgz", + "integrity": "sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ==" + }, + "uuidv4": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/uuidv4/-/uuidv4-6.2.3.tgz", + "integrity": "sha512-4hxGisl76Y6A7nkadg5gMrPGVYVGLmJ3fZHVvmnXsy+8DMA7n7YV/4Y72Fw38CCwpZpyPgOaa/4YxhkCYwyNNQ==", + "requires": { + "@types/uuid": "8.3.0", + "uuid": "8.3.0" + } + }, + "v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "window-size": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.4.tgz", + "integrity": "sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY=" + }, + "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==", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + }, + "dependencies": { + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + } + } + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + }, + "y18n": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz", + "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==" + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + }, + "yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true + }, + "yargs": { + "version": "3.32.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz", + "integrity": "sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU=", + "requires": { + "camelcase": "^2.0.1", + "cliui": "^3.0.3", + "decamelize": "^1.1.1", + "os-locale": "^1.4.0", + "string-width": "^1.0.1", + "window-size": "^0.1.4", + "y18n": "^3.2.0" + } + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + } + } +} diff --git a/back/package.json b/back/package.json index 5bf5d031..7015b9b8 100644 --- a/back/package.json +++ b/back/package.json @@ -10,8 +10,8 @@ "runprod": "node --max-old-space-size=4096 ./dist/server.js", "profile": "tsc && node --prof ./dist/server.js", "test": "ts-node node_modules/jasmine/bin/jasmine --config=jasmine.json", - "lint": "node_modules/.bin/eslint src/ . --ext .ts", - "fix": "node_modules/.bin/eslint --fix src/ . --ext .ts", + "lint": "DEBUG= node_modules/.bin/eslint src/ . --ext .ts", + "fix": "DEBUG= node_modules/.bin/eslint --fix src/ . --ext .ts", "precommit": "lint-staged", "pretty": "yarn prettier --write 'src/**/*.{ts,tsx}'", "pretty-check": "yarn prettier --check 'src/**/*.{ts,tsx}'" diff --git a/back/src/App.ts b/back/src/App.ts index 4bcc56ba..a6c42abb 100644 --- a/back/src/App.ts +++ b/back/src/App.ts @@ -1,7 +1,7 @@ // lib/app.ts -import {PrometheusController} from "./Controller/PrometheusController"; -import {DebugController} from "./Controller/DebugController"; -import {App as uwsApp} from "./Server/sifrr.server"; +import { PrometheusController } from "./Controller/PrometheusController"; +import { DebugController } from "./Controller/DebugController"; +import { App as uwsApp } from "./Server/sifrr.server"; class App { public app: uwsApp; diff --git a/back/src/Controller/BaseController.ts b/back/src/Controller/BaseController.ts index 93c17ab4..dc510d6c 100644 --- a/back/src/Controller/BaseController.ts +++ b/back/src/Controller/BaseController.ts @@ -1,10 +1,9 @@ -import {HttpResponse} from "uWebSockets.js"; - +import { HttpResponse } from "uWebSockets.js"; export class BaseController { protected addCorsHeaders(res: HttpResponse): void { - res.writeHeader('access-control-allow-headers', 'Origin, X-Requested-With, Content-Type, Accept'); - res.writeHeader('access-control-allow-methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE'); - res.writeHeader('access-control-allow-origin', '*'); + res.writeHeader("access-control-allow-headers", "Origin, X-Requested-With, Content-Type, Accept"); + res.writeHeader("access-control-allow-methods", "GET, POST, OPTIONS, PUT, PATCH, DELETE"); + res.writeHeader("access-control-allow-origin", "*"); } } diff --git a/back/src/Controller/DebugController.ts b/back/src/Controller/DebugController.ts index 509d8b2f..b7f037fd 100644 --- a/back/src/Controller/DebugController.ts +++ b/back/src/Controller/DebugController.ts @@ -1,53 +1,54 @@ -import {ADMIN_API_TOKEN} from "../Enum/EnvironmentVariable"; -import {stringify} from "circular-json"; -import {HttpRequest, HttpResponse} from "uWebSockets.js"; -import { parse } from 'query-string'; -import {App} from "../Server/sifrr.server"; -import {socketManager} from "../Services/SocketManager"; +import { ADMIN_API_TOKEN } from "../Enum/EnvironmentVariable"; +import { stringify } from "circular-json"; +import { HttpRequest, HttpResponse } from "uWebSockets.js"; +import { parse } from "query-string"; +import { App } from "../Server/sifrr.server"; +import { socketManager } from "../Services/SocketManager"; export class DebugController { - constructor(private App : App) { + constructor(private App: App) { this.getDump(); } - - getDump(){ + getDump() { this.App.get("/dump", (res: HttpResponse, req: HttpRequest) => { const query = parse(req.getQuery()); if (query.token !== ADMIN_API_TOKEN) { - return res.status(401).send('Invalid token sent!'); + return res.status(401).send("Invalid token sent!"); } - return res.writeStatus('200 OK').writeHeader('Content-Type', 'application/json').end(stringify( - socketManager.getWorlds(), - (key: unknown, value: unknown) => { - if (key === 'listeners') { - return 'Listeners'; - } - if (key === 'socket') { - return 'Socket'; - } - if (key === 'batchedMessages') { - return 'BatchedMessages'; - } - if(value instanceof Map) { - const obj: any = {}; // eslint-disable-line @typescript-eslint/no-explicit-any - for (const [mapKey, mapValue] of value.entries()) { - obj[mapKey] = mapValue; + return res + .writeStatus("200 OK") + .writeHeader("Content-Type", "application/json") + .end( + stringify(socketManager.getWorlds(), (key: unknown, value: unknown) => { + if (key === "listeners") { + return "Listeners"; } - return obj; - } else if(value instanceof Set) { + if (key === "socket") { + return "Socket"; + } + if (key === "batchedMessages") { + return "BatchedMessages"; + } + if (value instanceof Map) { + const obj: any = {}; // eslint-disable-line @typescript-eslint/no-explicit-any + for (const [mapKey, mapValue] of value.entries()) { + obj[mapKey] = mapValue; + } + return obj; + } else if (value instanceof Set) { const obj: Array = []; for (const [setKey, setValue] of value.entries()) { obj.push(setValue); } return obj; - } else { - return value; - } - } - )); + } else { + return value; + } + }) + ); }); } } diff --git a/back/src/Controller/PrometheusController.ts b/back/src/Controller/PrometheusController.ts index e854cf43..3ab3d33f 100644 --- a/back/src/Controller/PrometheusController.ts +++ b/back/src/Controller/PrometheusController.ts @@ -1,7 +1,7 @@ -import {App} from "../Server/sifrr.server"; -import {HttpRequest, HttpResponse} from "uWebSockets.js"; -const register = require('prom-client').register; -const collectDefaultMetrics = require('prom-client').collectDefaultMetrics; +import { App } from "../Server/sifrr.server"; +import { HttpRequest, HttpResponse } from "uWebSockets.js"; +const register = require("prom-client").register; +const collectDefaultMetrics = require("prom-client").collectDefaultMetrics; export class PrometheusController { constructor(private App: App) { @@ -14,7 +14,7 @@ export class PrometheusController { } private metrics(res: HttpResponse, req: HttpRequest): void { - res.writeHeader('Content-Type', register.contentType); + res.writeHeader("Content-Type", register.contentType); res.end(register.metrics()); } } diff --git a/back/src/Enum/EnvironmentVariable.ts b/back/src/Enum/EnvironmentVariable.ts index 81693a98..19eddd3e 100644 --- a/back/src/Enum/EnvironmentVariable.ts +++ b/back/src/Enum/EnvironmentVariable.ts @@ -1,17 +1,17 @@ const MINIMUM_DISTANCE = process.env.MINIMUM_DISTANCE ? Number(process.env.MINIMUM_DISTANCE) : 64; const GROUP_RADIUS = process.env.GROUP_RADIUS ? Number(process.env.GROUP_RADIUS) : 48; -const ALLOW_ARTILLERY = process.env.ALLOW_ARTILLERY ? process.env.ALLOW_ARTILLERY == 'true' : false; -const ADMIN_API_URL = process.env.ADMIN_API_URL || ''; -const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN || 'myapitoken'; +const ALLOW_ARTILLERY = process.env.ALLOW_ARTILLERY ? process.env.ALLOW_ARTILLERY == "true" : false; +const ADMIN_API_URL = process.env.ADMIN_API_URL || ""; +const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN || "myapitoken"; const CPU_OVERHEAT_THRESHOLD = Number(process.env.CPU_OVERHEAT_THRESHOLD) || 80; -const JITSI_URL : string|undefined = (process.env.JITSI_URL === '') ? undefined : process.env.JITSI_URL; -const JITSI_ISS = process.env.JITSI_ISS || ''; -const SECRET_JITSI_KEY = process.env.SECRET_JITSI_KEY || ''; -const HTTP_PORT = parseInt(process.env.HTTP_PORT || '8080') || 8080; -const GRPC_PORT = parseInt(process.env.GRPC_PORT || '50051') || 50051; +const JITSI_URL: string | undefined = process.env.JITSI_URL === "" ? undefined : process.env.JITSI_URL; +const JITSI_ISS = process.env.JITSI_ISS || ""; +const SECRET_JITSI_KEY = process.env.SECRET_JITSI_KEY || ""; +const HTTP_PORT = parseInt(process.env.HTTP_PORT || "8080") || 8080; +const GRPC_PORT = parseInt(process.env.GRPC_PORT || "50051") || 50051; export const SOCKET_IDLE_TIMER = parseInt(process.env.SOCKET_IDLE_TIMER as string) || 30; // maximum time (in second) without activity before a socket is closed -export const TURN_STATIC_AUTH_SECRET = process.env.TURN_STATIC_AUTH_SECRET || ''; -export const MAX_PER_GROUP = parseInt(process.env.MAX_PER_GROUP || '4'); +export const TURN_STATIC_AUTH_SECRET = process.env.TURN_STATIC_AUTH_SECRET || ""; +export const MAX_PER_GROUP = parseInt(process.env.MAX_PER_GROUP || "4"); export { MINIMUM_DISTANCE, @@ -24,5 +24,5 @@ export { CPU_OVERHEAT_THRESHOLD, JITSI_URL, JITSI_ISS, - SECRET_JITSI_KEY -} + SECRET_JITSI_KEY, +}; diff --git a/back/src/Model/Admin.ts b/back/src/Model/Admin.ts index 29b53385..93396fa8 100644 --- a/back/src/Model/Admin.ts +++ b/back/src/Model/Admin.ts @@ -1,15 +1,12 @@ import { ServerToAdminClientMessage, - UserJoinedRoomMessage, UserLeftRoomMessage + UserJoinedRoomMessage, + UserLeftRoomMessage, } from "../Messages/generated/messages_pb"; -import {AdminSocket} from "../RoomManager"; - +import { AdminSocket } from "../RoomManager"; export class Admin { - public constructor( - private readonly socket: AdminSocket - ) { - } + public constructor(private readonly socket: AdminSocket) {} public sendUserJoin(uuid: string, name: string, ip: string): void { const serverToAdminClientMessage = new ServerToAdminClientMessage(); @@ -24,7 +21,7 @@ export class Admin { this.socket.write(serverToAdminClientMessage); } - public sendUserLeft(uuid: string/*, name: string, ip: string*/): void { + public sendUserLeft(uuid: string /*, name: string, ip: string*/): void { const serverToAdminClientMessage = new ServerToAdminClientMessage(); const userLeftRoomMessage = new UserLeftRoomMessage(); diff --git a/back/src/Model/GameRoom.ts b/back/src/Model/GameRoom.ts index 53d0a855..020f4c29 100644 --- a/back/src/Model/GameRoom.ts +++ b/back/src/Model/GameRoom.ts @@ -1,16 +1,16 @@ -import {PointInterface} from "./Websocket/PointInterface"; -import {Group} from "./Group"; -import {User, UserSocket} from "./User"; -import {PositionInterface} from "_Model/PositionInterface"; -import {EmoteCallback, EntersCallback, LeavesCallback, MovesCallback} from "_Model/Zone"; -import {PositionNotifier} from "./PositionNotifier"; -import {Movable} from "_Model/Movable"; -import {extractDataFromPrivateRoomId, extractRoomSlugPublicRoomId, isRoomAnonymous} from "./RoomIdentifier"; -import {arrayIntersect} from "../Services/ArrayHelper"; -import {EmoteEventMessage, JoinRoomMessage} from "../Messages/generated/messages_pb"; -import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils"; -import {ZoneSocket} from "src/RoomManager"; -import {Admin} from "../Model/Admin"; +import { PointInterface } from "./Websocket/PointInterface"; +import { Group } from "./Group"; +import { User, UserSocket } from "./User"; +import { PositionInterface } from "_Model/PositionInterface"; +import { EmoteCallback, EntersCallback, LeavesCallback, MovesCallback } from "_Model/Zone"; +import { PositionNotifier } from "./PositionNotifier"; +import { Movable } from "_Model/Movable"; +import { extractDataFromPrivateRoomId, extractRoomSlugPublicRoomId, isRoomAnonymous } from "./RoomIdentifier"; +import { arrayIntersect } from "../Services/ArrayHelper"; +import { EmoteEventMessage, JoinRoomMessage } from "../Messages/generated/messages_pb"; +import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils"; +import { ZoneSocket } from "src/RoomManager"; +import { Admin } from "../Model/Admin"; export type ConnectCallback = (user: User, group: Group) => void; export type DisconnectCallback = (user: User, group: Group) => void; @@ -39,33 +39,33 @@ export class GameRoom { private readonly positionNotifier: PositionNotifier; public readonly roomId: string; public readonly roomSlug: string; - public readonly worldSlug: string = ''; - public readonly organizationSlug: string = ''; - private versionNumber:number = 1; + public readonly worldSlug: string = ""; + public readonly organizationSlug: string = ""; + private versionNumber: number = 1; private nextUserId: number = 1; - constructor(roomId: string, - connectCallback: ConnectCallback, - disconnectCallback: DisconnectCallback, - minDistance: number, - groupRadius: number, - onEnters: EntersCallback, - onMoves: MovesCallback, - onLeaves: LeavesCallback, - onEmote: EmoteCallback, + constructor( + roomId: string, + connectCallback: ConnectCallback, + disconnectCallback: DisconnectCallback, + minDistance: number, + groupRadius: number, + onEnters: EntersCallback, + onMoves: MovesCallback, + onLeaves: LeavesCallback, + onEmote: EmoteCallback ) { this.roomId = roomId; if (isRoomAnonymous(roomId)) { this.roomSlug = extractRoomSlugPublicRoomId(this.roomId); } else { - const {organizationSlug, worldSlug, roomSlug} = extractDataFromPrivateRoomId(this.roomId); + const { organizationSlug, worldSlug, roomSlug } = extractDataFromPrivateRoomId(this.roomId); this.roomSlug = roomSlug; this.organizationSlug = organizationSlug; this.worldSlug = worldSlug; } - this.users = new Map(); this.usersByUuid = new Map(); this.admins = new Set(); @@ -86,21 +86,22 @@ export class GameRoom { return this.users; } - public getUserByUuid(uuid: string): User|undefined { + public getUserByUuid(uuid: string): User | undefined { return this.usersByUuid.get(uuid); } - public getUserById(id: number): User|undefined { + public getUserById(id: number): User | undefined { return this.users.get(id); } - - public join(socket : UserSocket, joinRoomMessage: JoinRoomMessage): User { + + public join(socket: UserSocket, joinRoomMessage: JoinRoomMessage): User { const positionMessage = joinRoomMessage.getPositionmessage(); if (positionMessage === undefined) { - throw new Error('Missing position message'); + throw new Error("Missing position message"); } const position = ProtobufUtils.toPointInterface(positionMessage); - const user = new User(this.nextUserId, + const user = new User( + this.nextUserId, joinRoomMessage.getUseruuid(), joinRoomMessage.getIpaddress(), position, @@ -126,12 +127,12 @@ export class GameRoom { return user; } - public leave(user : User){ + public leave(user: User) { const userObj = this.users.get(user.id); if (userObj === undefined) { - console.warn('User ', user.id, 'does not belong to this game room! It should!'); + console.warn("User ", user.id, "does not belong to this game room! It should!"); } - if (userObj !== undefined && typeof userObj.group !== 'undefined') { + if (userObj !== undefined && typeof userObj.group !== "undefined") { this.leaveGroup(userObj); } this.users.delete(user.id); @@ -143,7 +144,7 @@ export class GameRoom { // Notify admins for (const admin of this.admins) { - admin.sendUserLeft(user.uuid/*, user.name, user.IPAddress*/); + admin.sendUserLeft(user.uuid /*, user.name, user.IPAddress*/); } } @@ -151,7 +152,7 @@ export class GameRoom { return this.users.size === 0 && this.admins.size === 0; } - public updatePosition(user : User, userPosition: PointInterface): void { + public updatePosition(user: User, userPosition: PointInterface): void { user.setPosition(userPosition); this.updateUserGroup(user); @@ -173,22 +174,24 @@ export class GameRoom { return; } - const closestItem: User|Group|null = this.searchClosestAvailableUserOrGroup(user); + const closestItem: User | Group | null = this.searchClosestAvailableUserOrGroup(user); if (closestItem !== null) { if (closestItem instanceof Group) { // Let's join the group! closestItem.join(user); } else { - const closestUser : User = closestItem; - const group: Group = new Group(this.roomId,[ - user, - closestUser - ], this.connectCallback, this.disconnectCallback, this.positionNotifier); + const closestUser: User = closestItem; + const group: Group = new Group( + this.roomId, + [user, closestUser], + this.connectCallback, + this.disconnectCallback, + this.positionNotifier + ); this.groups.add(group); } } - } else { // If the user is part of a group: // should he leave the group? @@ -229,7 +232,9 @@ export class GameRoom { this.positionNotifier.leave(group); group.destroy(); if (!this.groups.has(group)) { - throw new Error("Could not find group "+group.getId()+" referenced by user "+user.id+" in World."); + throw new Error( + "Could not find group " + group.getId() + " referenced by user " + user.id + " in World." + ); } this.groups.delete(group); //todo: is the group garbage collected? @@ -247,16 +252,15 @@ export class GameRoom { * OR * - close enough to a group (distance <= groupRadius) */ - private searchClosestAvailableUserOrGroup(user: User): User|Group|null - { + private searchClosestAvailableUserOrGroup(user: User): User | Group | null { let minimumDistanceFound: number = Math.max(this.minDistance, this.groupRadius); let matchingItem: User | Group | null = null; this.users.forEach((currentUser, userId) => { // Let's only check users that are not part of a group - if (typeof currentUser.group !== 'undefined') { + if (typeof currentUser.group !== "undefined") { return; } - if(currentUser === user) { + if (currentUser === user) { return; } if (currentUser.silent) { @@ -265,7 +269,7 @@ export class GameRoom { const distance = GameRoom.computeDistance(user, currentUser); // compute distance between peers. - if(distance <= minimumDistanceFound && distance <= this.minDistance) { + if (distance <= minimumDistanceFound && distance <= this.minDistance) { minimumDistanceFound = distance; matchingItem = currentUser; } @@ -276,7 +280,7 @@ export class GameRoom { return; } const distance = GameRoom.computeDistanceBetweenPositions(user.getPosition(), group.getPosition()); - if(distance <= minimumDistanceFound && distance <= this.groupRadius) { + if (distance <= minimumDistanceFound && distance <= this.groupRadius) { minimumDistanceFound = distance; matchingItem = group; } @@ -285,15 +289,15 @@ export class GameRoom { return matchingItem; } - public static computeDistance(user1: User, user2: User): number - { + public static computeDistance(user1: User, user2: User): number { const user1Position = user1.getPosition(); const user2Position = user2.getPosition(); - return Math.sqrt(Math.pow(user2Position.x - user1Position.x, 2) + Math.pow(user2Position.y - user1Position.y, 2)); + return Math.sqrt( + Math.pow(user2Position.x - user1Position.x, 2) + Math.pow(user2Position.y - user1Position.y, 2) + ); } - public static computeDistanceBetweenPositions(position1: PositionInterface, position2: PositionInterface): number - { + public static computeDistanceBetweenPositions(position1: PositionInterface, position2: PositionInterface): number { return Math.sqrt(Math.pow(position2.x - position1.x, 2) + Math.pow(position2.y - position1.y, 2)); } @@ -325,9 +329,9 @@ export class GameRoom { public adminLeave(admin: Admin): void { this.admins.delete(admin); } - + public incrementVersion(): number { - this.versionNumber++ + this.versionNumber++; return this.versionNumber; } diff --git a/back/src/Model/Group.ts b/back/src/Model/Group.ts index ffe7a78a..5a0f3be6 100644 --- a/back/src/Model/Group.ts +++ b/back/src/Model/Group.ts @@ -1,13 +1,12 @@ import { ConnectCallback, DisconnectCallback } from "./GameRoom"; import { User } from "./User"; -import {PositionInterface} from "_Model/PositionInterface"; -import {Movable} from "_Model/Movable"; -import {PositionNotifier} from "_Model/PositionNotifier"; -import {gaugeManager} from "../Services/GaugeManager"; -import {MAX_PER_GROUP} from "../Enum/EnvironmentVariable"; +import { PositionInterface } from "_Model/PositionInterface"; +import { Movable } from "_Model/Movable"; +import { PositionNotifier } from "_Model/PositionNotifier"; +import { gaugeManager } from "../Services/GaugeManager"; +import { MAX_PER_GROUP } from "../Enum/EnvironmentVariable"; export class Group implements Movable { - private static nextId: number = 1; private id: number; @@ -18,8 +17,13 @@ export class Group implements Movable { private wasDestroyed: boolean = false; private roomId: string; - - constructor(roomId: string, users: User[], private connectCallback: ConnectCallback, private disconnectCallback: DisconnectCallback, private positionNotifier: PositionNotifier) { + constructor( + roomId: string, + users: User[], + private connectCallback: ConnectCallback, + private disconnectCallback: DisconnectCallback, + private positionNotifier: PositionNotifier + ) { this.roomId = roomId; this.users = new Set(); this.id = Group.nextId; @@ -43,7 +47,7 @@ export class Group implements Movable { return Array.from(this.users.values()); } - getId() : number { + getId(): number { return this.id; } @@ -53,7 +57,7 @@ export class Group implements Movable { getPosition(): PositionInterface { return { x: this.x, - y: this.y + y: this.y, }; } @@ -83,7 +87,7 @@ export class Group implements Movable { if (oldX === undefined) { this.positionNotifier.enter(this); } else { - this.positionNotifier.updatePosition(this, {x, y}, {x: oldX, y: oldY}); + this.positionNotifier.updatePosition(this, { x, y }, { x: oldX, y: oldY }); } } @@ -95,19 +99,17 @@ export class Group implements Movable { return this.users.size <= 1; } - join(user: User): void - { + join(user: User): void { // Broadcast on the right event this.connectCallback(user, this); this.users.add(user); user.group = this; } - leave(user: User): void - { + leave(user: User): void { const success = this.users.delete(user); if (success === false) { - throw new Error("Could not find user "+user.id+" in the group "+this.id); + throw new Error("Could not find user " + user.id + " in the group " + this.id); } user.group = undefined; @@ -123,8 +125,7 @@ export class Group implements Movable { * Let's kick everybody out. * Usually used when there is only one user left. */ - destroy(): void - { + destroy(): void { if (this.hasEditedGauge) gaugeManager.decNbGroupsPerRoomGauge(this.roomId); for (const user of this.users) { this.leave(user); @@ -132,7 +133,7 @@ export class Group implements Movable { this.wasDestroyed = true; } - get getSize(){ + get getSize() { return this.users.size; } } diff --git a/back/src/Model/Movable.ts b/back/src/Model/Movable.ts index 173db0ae..ca586b7c 100644 --- a/back/src/Model/Movable.ts +++ b/back/src/Model/Movable.ts @@ -1,8 +1,8 @@ -import {PositionInterface} from "_Model/PositionInterface"; +import { PositionInterface } from "_Model/PositionInterface"; /** * A physical object that can be placed into a Zone */ export interface Movable { - getPosition(): PositionInterface + getPosition(): PositionInterface; } diff --git a/back/src/Model/PositionInterface.ts b/back/src/Model/PositionInterface.ts index d3b0dd47..65636759 100644 --- a/back/src/Model/PositionInterface.ts +++ b/back/src/Model/PositionInterface.ts @@ -1,4 +1,4 @@ export interface PositionInterface { - x: number, - y: number + x: number; + y: number; } diff --git a/back/src/Model/PositionNotifier.ts b/back/src/Model/PositionNotifier.ts index 275bf9d0..c34c1ef1 100644 --- a/back/src/Model/PositionNotifier.ts +++ b/back/src/Model/PositionNotifier.ts @@ -8,12 +8,12 @@ * The PositionNotifier is important for performance. It allows us to send the position of players only to a restricted * number of players around the current player. */ -import {EmoteCallback, EntersCallback, LeavesCallback, MovesCallback, Zone} from "./Zone"; -import {Movable} from "_Model/Movable"; -import {PositionInterface} from "_Model/PositionInterface"; -import {ZoneSocket} from "../RoomManager"; -import {User} from "_Model/User"; -import {EmoteEventMessage} from "../Messages/generated/messages_pb"; +import { EmoteCallback, EntersCallback, LeavesCallback, MovesCallback, Zone } from "./Zone"; +import { Movable } from "_Model/Movable"; +import { PositionInterface } from "_Model/PositionInterface"; +import { ZoneSocket } from "../RoomManager"; +import { User } from "_Model/User"; +import { EmoteEventMessage } from "../Messages/generated/messages_pb"; interface ZoneDescriptor { i: number; @@ -21,19 +21,24 @@ interface ZoneDescriptor { } export class PositionNotifier { - // TODO: we need a way to clean the zones if noone is in the zone and noone listening (to free memory!) private zones: Zone[][] = []; - constructor(private zoneWidth: number, private zoneHeight: number, private onUserEnters: EntersCallback, private onUserMoves: MovesCallback, private onUserLeaves: LeavesCallback, private onEmote: EmoteCallback) { - } + constructor( + private zoneWidth: number, + private zoneHeight: number, + private onUserEnters: EntersCallback, + private onUserMoves: MovesCallback, + private onUserLeaves: LeavesCallback, + private onEmote: EmoteCallback + ) {} private getZoneDescriptorFromCoordinates(x: number, y: number): ZoneDescriptor { return { i: Math.floor(x / this.zoneWidth), j: Math.floor(y / this.zoneHeight), - } + }; } public enter(thing: Movable): void { @@ -100,6 +105,5 @@ export class PositionNotifier { const zoneDesc = this.getZoneDescriptorFromCoordinates(user.getPosition().x, user.getPosition().y); const zone = this.getZone(zoneDesc.i, zoneDesc.j); zone.emitEmoteEvent(emoteEventMessage); - } } diff --git a/back/src/Model/RoomIdentifier.ts b/back/src/Model/RoomIdentifier.ts index 3ac62bca..d1de8800 100644 --- a/back/src/Model/RoomIdentifier.ts +++ b/back/src/Model/RoomIdentifier.ts @@ -1,30 +1,30 @@ //helper functions to parse room IDs export const isRoomAnonymous = (roomID: string): boolean => { - if (roomID.startsWith('_/')) { + if (roomID.startsWith("_/")) { return true; - } else if(roomID.startsWith('@/')) { + } else if (roomID.startsWith("@/")) { return false; } else { - throw new Error('Incorrect room ID: '+roomID); + throw new Error("Incorrect room ID: " + roomID); } -} +}; export const extractRoomSlugPublicRoomId = (roomId: string): string => { - const idParts = roomId.split('/'); - if (idParts.length < 3) throw new Error('Incorrect roomId: '+roomId); - return idParts.slice(2).join('/'); -} + const idParts = roomId.split("/"); + if (idParts.length < 3) throw new Error("Incorrect roomId: " + roomId); + return idParts.slice(2).join("/"); +}; export interface extractDataFromPrivateRoomIdResponse { organizationSlug: string; worldSlug: string; roomSlug: string; } export const extractDataFromPrivateRoomId = (roomId: string): extractDataFromPrivateRoomIdResponse => { - const idParts = roomId.split('/'); - if (idParts.length < 4) throw new Error('Incorrect roomId: '+roomId); + const idParts = roomId.split("/"); + if (idParts.length < 4) throw new Error("Incorrect roomId: " + roomId); const organizationSlug = idParts[1]; const worldSlug = idParts[2]; const roomSlug = idParts[3]; - return {organizationSlug, worldSlug, roomSlug} -} \ No newline at end of file + return { organizationSlug, worldSlug, roomSlug }; +}; diff --git a/back/src/Model/User.ts b/back/src/Model/User.ts index 4a3e75ec..186fb32a 100644 --- a/back/src/Model/User.ts +++ b/back/src/Model/User.ts @@ -1,11 +1,17 @@ import { Group } from "./Group"; import { PointInterface } from "./Websocket/PointInterface"; -import {Zone} from "_Model/Zone"; -import {Movable} from "_Model/Movable"; -import {PositionNotifier} from "_Model/PositionNotifier"; -import {ServerDuplexStream} from "grpc"; -import {BatchMessage, CompanionMessage, PusherToBackMessage, ServerToClientMessage, SubMessage} from "../Messages/generated/messages_pb"; -import {CharacterLayer} from "_Model/Websocket/CharacterLayer"; +import { Zone } from "_Model/Zone"; +import { Movable } from "_Model/Movable"; +import { PositionNotifier } from "_Model/PositionNotifier"; +import { ServerDuplexStream } from "grpc"; +import { + BatchMessage, + CompanionMessage, + PusherToBackMessage, + ServerToClientMessage, + SubMessage, +} from "../Messages/generated/messages_pb"; +import { CharacterLayer } from "_Model/Websocket/CharacterLayer"; export type UserSocket = ServerDuplexStream; @@ -22,7 +28,7 @@ export class User implements Movable { private positionNotifier: PositionNotifier, public readonly socket: UserSocket, public readonly tags: string[], - public readonly visitCardUrl: string|null, + public readonly visitCardUrl: string | null, public readonly name: string, public readonly characterLayers: CharacterLayer[], public readonly companion?: CompanionMessage @@ -42,9 +48,8 @@ export class User implements Movable { this.positionNotifier.updatePosition(this, position, oldPosition); } - private batchedMessages: BatchMessage = new BatchMessage(); - private batchTimeout: NodeJS.Timeout|null = null; + private batchTimeout: NodeJS.Timeout | null = null; public emitInBatch(payload: SubMessage): void { this.batchedMessages.addPayload(payload); diff --git a/back/src/Model/Websocket/CharacterLayer.ts b/back/src/Model/Websocket/CharacterLayer.ts index 13d838ee..3e428790 100644 --- a/back/src/Model/Websocket/CharacterLayer.ts +++ b/back/src/Model/Websocket/CharacterLayer.ts @@ -1,4 +1,4 @@ export interface CharacterLayer { - name: string, - url: string|undefined + name: string; + url: string | undefined; } diff --git a/back/src/Model/Websocket/ItemEventMessage.ts b/back/src/Model/Websocket/ItemEventMessage.ts index b1f9203e..1bb7f615 100644 --- a/back/src/Model/Websocket/ItemEventMessage.ts +++ b/back/src/Model/Websocket/ItemEventMessage.ts @@ -1,10 +1,11 @@ import * as tg from "generic-type-guard"; -export const isItemEventMessageInterface = - new tg.IsInterface().withProperties({ +export const isItemEventMessageInterface = new tg.IsInterface() + .withProperties({ itemId: tg.isNumber, event: tg.isString, state: tg.isUnknown, parameters: tg.isUnknown, - }).get(); + }) + .get(); export type ItemEventMessageInterface = tg.GuardedType; diff --git a/back/src/Model/Websocket/MessageUserPosition.ts b/back/src/Model/Websocket/MessageUserPosition.ts index ee43d58c..19b57d2e 100644 --- a/back/src/Model/Websocket/MessageUserPosition.ts +++ b/back/src/Model/Websocket/MessageUserPosition.ts @@ -1,7 +1,10 @@ -import {PointInterface} from "./PointInterface"; +import { PointInterface } from "./PointInterface"; -export class Point implements PointInterface{ - constructor(public x : number, public y : number, public direction : string = "none", public moving : boolean = false) { - } +export class Point implements PointInterface { + constructor( + public x: number, + public y: number, + public direction: string = "none", + public moving: boolean = false + ) {} } - diff --git a/back/src/Model/Websocket/PointInterface.ts b/back/src/Model/Websocket/PointInterface.ts index afb07a23..d7c7826e 100644 --- a/back/src/Model/Websocket/PointInterface.ts +++ b/back/src/Model/Websocket/PointInterface.ts @@ -7,11 +7,12 @@ import * as tg from "generic-type-guard"; readonly moving: boolean; }*/ -export const isPointInterface = - new tg.IsInterface().withProperties({ +export const isPointInterface = new tg.IsInterface() + .withProperties({ x: tg.isNumber, y: tg.isNumber, direction: tg.isString, - moving: tg.isBoolean - }).get(); + moving: tg.isBoolean, + }) + .get(); export type PointInterface = tg.GuardedType; diff --git a/back/src/Model/Websocket/ProtobufUtils.ts b/back/src/Model/Websocket/ProtobufUtils.ts index b85a4257..68817a4f 100644 --- a/back/src/Model/Websocket/ProtobufUtils.ts +++ b/back/src/Model/Websocket/ProtobufUtils.ts @@ -1,34 +1,33 @@ -import {PointInterface} from "./PointInterface"; +import { PointInterface } from "./PointInterface"; import { CharacterLayerMessage, ItemEventMessage, PointMessage, - PositionMessage + PositionMessage, } from "../../Messages/generated/messages_pb"; -import {CharacterLayer} from "_Model/Websocket/CharacterLayer"; +import { CharacterLayer } from "_Model/Websocket/CharacterLayer"; import Direction = PositionMessage.Direction; -import {ItemEventMessageInterface} from "_Model/Websocket/ItemEventMessage"; -import {PositionInterface} from "_Model/PositionInterface"; +import { ItemEventMessageInterface } from "_Model/Websocket/ItemEventMessage"; +import { PositionInterface } from "_Model/PositionInterface"; export class ProtobufUtils { - public static toPositionMessage(point: PointInterface): PositionMessage { let direction: Direction; switch (point.direction) { - case 'up': + case "up": direction = Direction.UP; break; - case 'down': + case "down": direction = Direction.DOWN; break; - case 'left': + case "left": direction = Direction.LEFT; break; - case 'right': + case "right": direction = Direction.RIGHT; break; default: - throw new Error('unexpected direction'); + throw new Error("unexpected direction"); } const position = new PositionMessage(); @@ -44,16 +43,16 @@ export class ProtobufUtils { let direction: string; switch (position.getDirection()) { case Direction.UP: - direction = 'up'; + direction = "up"; break; case Direction.DOWN: - direction = 'down'; + direction = "down"; break; case Direction.LEFT: - direction = 'left'; + direction = "left"; break; case Direction.RIGHT: - direction = 'right'; + direction = "right"; break; default: throw new Error("Unexpected direction"); @@ -82,7 +81,7 @@ export class ProtobufUtils { event: itemEventMessage.getEvent(), parameters: JSON.parse(itemEventMessage.getParametersjson()), state: JSON.parse(itemEventMessage.getStatejson()), - } + }; } public static toItemEventProtobuf(itemEvent: ItemEventMessageInterface): ItemEventMessage { @@ -96,7 +95,7 @@ export class ProtobufUtils { } public static toCharacterLayerMessages(characterLayers: CharacterLayer[]): CharacterLayerMessage[] { - return characterLayers.map(function(characterLayer): CharacterLayerMessage { + return characterLayers.map(function (characterLayer): CharacterLayerMessage { const message = new CharacterLayerMessage(); message.setName(characterLayer.name); if (characterLayer.url) { @@ -107,7 +106,7 @@ export class ProtobufUtils { } public static toCharacterLayerObjects(characterLayers: CharacterLayerMessage[]): CharacterLayer[] { - return characterLayers.map(function(characterLayer): CharacterLayer { + return characterLayers.map(function (characterLayer): CharacterLayer { const url = characterLayer.getUrl(); return { name: characterLayer.getName(), diff --git a/back/src/Model/Zone.ts b/back/src/Model/Zone.ts index ffb172bb..d236e489 100644 --- a/back/src/Model/Zone.ts +++ b/back/src/Model/Zone.ts @@ -1,35 +1,52 @@ -import {User} from "./User"; -import {PositionInterface} from "_Model/PositionInterface"; -import {Movable} from "./Movable"; -import {Group} from "./Group"; -import {ZoneSocket} from "../RoomManager"; -import {EmoteEventMessage} from "../Messages/generated/messages_pb"; +import { User } from "./User"; +import { PositionInterface } from "_Model/PositionInterface"; +import { Movable } from "./Movable"; +import { Group } from "./Group"; +import { ZoneSocket } from "../RoomManager"; +import { EmoteEventMessage } from "../Messages/generated/messages_pb"; -export type EntersCallback = (thing: Movable, fromZone: Zone|null, listener: ZoneSocket) => void; +export type EntersCallback = (thing: Movable, fromZone: Zone | null, listener: ZoneSocket) => void; export type MovesCallback = (thing: Movable, position: PositionInterface, listener: ZoneSocket) => void; -export type LeavesCallback = (thing: Movable, newZone: Zone|null, listener: ZoneSocket) => void; +export type LeavesCallback = (thing: Movable, newZone: Zone | null, listener: ZoneSocket) => void; export type EmoteCallback = (emoteEventMessage: EmoteEventMessage, listener: ZoneSocket) => void; export class Zone { private things: Set = new Set(); private listeners: Set = new Set(); - - - constructor(private onEnters: EntersCallback, private onMoves: MovesCallback, private onLeaves: LeavesCallback, private onEmote: EmoteCallback, public readonly x: number, public readonly y: number) { } + + constructor( + private onEnters: EntersCallback, + private onMoves: MovesCallback, + private onLeaves: LeavesCallback, + private onEmote: EmoteCallback, + public readonly x: number, + public readonly y: number + ) {} /** * A user/thing leaves the zone */ - public leave(thing: Movable, newZone: Zone|null) { + public leave(thing: Movable, newZone: Zone | null) { const result = this.things.delete(thing); if (!result) { if (thing instanceof User) { - throw new Error('Could not find user in zone '+thing.id); + throw new Error("Could not find user in zone " + thing.id); } if (thing instanceof Group) { - throw new Error('Could not find group '+thing.getId()+' in zone ('+this.x+','+this.y+'). Position of group: ('+thing.getPosition().x+','+thing.getPosition().y+')'); + throw new Error( + "Could not find group " + + thing.getId() + + " in zone (" + + this.x + + "," + + this.y + + "). Position of group: (" + + thing.getPosition().x + + "," + + thing.getPosition().y + + ")" + ); } - } this.notifyLeft(thing, newZone); } @@ -37,13 +54,13 @@ export class Zone { /** * Notify listeners of this zone that this user/thing left */ - private notifyLeft(thing: Movable, newZone: Zone|null) { + private notifyLeft(thing: Movable, newZone: Zone | null) { for (const listener of this.listeners) { this.onLeaves(thing, newZone, listener); } } - public enter(thing: Movable, oldZone: Zone|null, position: PositionInterface) { + public enter(thing: Movable, oldZone: Zone | null, position: PositionInterface) { this.things.add(thing); this.notifyEnter(thing, oldZone, position); } @@ -51,13 +68,12 @@ export class Zone { /** * Notify listeners of this zone that this user entered */ - private notifyEnter(thing: Movable, oldZone: Zone|null, position: PositionInterface) { + private notifyEnter(thing: Movable, oldZone: Zone | null, position: PositionInterface) { for (const listener of this.listeners) { this.onEnters(thing, oldZone, listener); } } - public move(thing: Movable, position: PositionInterface) { if (!this.things.has(thing)) { this.things.add(thing); @@ -67,7 +83,7 @@ export class Zone { for (const listener of this.listeners) { //if (listener !== thing) { - this.onMoves(thing,position, listener); + this.onMoves(thing, position, listener); //} } } @@ -89,6 +105,5 @@ export class Zone { for (const listener of this.listeners) { this.onEmote(emoteEventMessage, listener); } - } } diff --git a/back/src/RoomManager.ts b/back/src/RoomManager.ts index 19266687..9aaf1edb 100644 --- a/back/src/RoomManager.ts +++ b/back/src/RoomManager.ts @@ -1,4 +1,4 @@ -import {IRoomManagerServer} from "./Messages/generated/messages_grpc_pb"; +import { IRoomManagerServer } from "./Messages/generated/messages_grpc_pb"; import { AdminGlobalMessage, AdminMessage, @@ -11,92 +11,114 @@ import { JoinRoomMessage, PlayGlobalMessage, PusherToBackMessage, - QueryJitsiJwtMessage, RefreshRoomPromptMessage, + QueryJitsiJwtMessage, + RefreshRoomPromptMessage, ServerToAdminClientMessage, ServerToClientMessage, SilentMessage, UserMovesMessage, - WebRtcSignalToServerMessage, WorldFullWarningToRoomMessage, - ZoneMessage + WebRtcSignalToServerMessage, + WorldFullWarningToRoomMessage, + ZoneMessage, } from "./Messages/generated/messages_pb"; -import {sendUnaryData, ServerDuplexStream, ServerUnaryCall, ServerWritableStream} from "grpc"; -import {socketManager} from "./Services/SocketManager"; -import {emitError} from "./Services/MessageHelpers"; -import {User, UserSocket} from "./Model/User"; -import {GameRoom} from "./Model/GameRoom"; +import { sendUnaryData, ServerDuplexStream, ServerUnaryCall, ServerWritableStream } from "grpc"; +import { socketManager } from "./Services/SocketManager"; +import { emitError } from "./Services/MessageHelpers"; +import { User, UserSocket } from "./Model/User"; +import { GameRoom } from "./Model/GameRoom"; import Debug from "debug"; -import {Admin} from "./Model/Admin"; +import { Admin } from "./Model/Admin"; -const debug = Debug('roommanager'); +const debug = Debug("roommanager"); export type AdminSocket = ServerDuplexStream; export type ZoneSocket = ServerWritableStream; const roomManager: IRoomManagerServer = { joinRoom: (call: UserSocket): void => { - console.log('joinRoom called'); + console.log("joinRoom called"); - let room: GameRoom|null = null; - let user: User|null = null; + let room: GameRoom | null = null; + let user: User | null = null; - call.on('data', (message: PusherToBackMessage) => { + call.on("data", (message: PusherToBackMessage) => { try { if (room === null || user === null) { if (message.hasJoinroommessage()) { - socketManager.handleJoinRoom(call, message.getJoinroommessage() as JoinRoomMessage).then(({room: gameRoom, user: myUser}) => { - if (call.writable) { - room = gameRoom; - user = myUser; - } else { - //Connexion may have been closed before the init was finished, so we have to manually disconnect the user. - socketManager.leaveRoom(gameRoom, myUser); - } - }); + socketManager + .handleJoinRoom(call, message.getJoinroommessage() as JoinRoomMessage) + .then(({ room: gameRoom, user: myUser }) => { + if (call.writable) { + room = gameRoom; + user = myUser; + } else { + //Connexion may have been closed before the init was finished, so we have to manually disconnect the user. + socketManager.leaveRoom(gameRoom, myUser); + } + }); } else { - throw new Error('The first message sent MUST be of type JoinRoomMessage'); + throw new Error("The first message sent MUST be of type JoinRoomMessage"); } } else { if (message.hasJoinroommessage()) { - throw new Error('Cannot call JoinRoomMessage twice!'); + throw new Error("Cannot call JoinRoomMessage twice!"); } else if (message.hasUsermovesmessage()) { - socketManager.handleUserMovesMessage(room, user, message.getUsermovesmessage() as UserMovesMessage); + socketManager.handleUserMovesMessage( + room, + user, + message.getUsermovesmessage() as UserMovesMessage + ); } else if (message.hasSilentmessage()) { socketManager.handleSilentMessage(room, user, message.getSilentmessage() as SilentMessage); } else if (message.hasItemeventmessage()) { socketManager.handleItemEvent(room, user, message.getItemeventmessage() as ItemEventMessage); } else if (message.hasWebrtcsignaltoservermessage()) { - socketManager.emitVideo(room, user, message.getWebrtcsignaltoservermessage() as WebRtcSignalToServerMessage); + socketManager.emitVideo( + room, + user, + message.getWebrtcsignaltoservermessage() as WebRtcSignalToServerMessage + ); } else if (message.hasWebrtcscreensharingsignaltoservermessage()) { - socketManager.emitScreenSharing(room, user, message.getWebrtcscreensharingsignaltoservermessage() as WebRtcSignalToServerMessage); + socketManager.emitScreenSharing( + room, + user, + message.getWebrtcscreensharingsignaltoservermessage() as WebRtcSignalToServerMessage + ); } else if (message.hasPlayglobalmessage()) { socketManager.emitPlayGlobalMessage(room, message.getPlayglobalmessage() as PlayGlobalMessage); - } else if (message.hasQueryjitsijwtmessage()){ - socketManager.handleQueryJitsiJwtMessage(user, message.getQueryjitsijwtmessage() as QueryJitsiJwtMessage); - } else if (message.hasEmotepromptmessage()){ - socketManager.handleEmoteEventMessage(room, user, message.getEmotepromptmessage() as EmotePromptMessage); - }else if (message.hasSendusermessage()) { + } else if (message.hasQueryjitsijwtmessage()) { + socketManager.handleQueryJitsiJwtMessage( + user, + message.getQueryjitsijwtmessage() as QueryJitsiJwtMessage + ); + } else if (message.hasEmotepromptmessage()) { + socketManager.handleEmoteEventMessage( + room, + user, + message.getEmotepromptmessage() as EmotePromptMessage + ); + } else if (message.hasSendusermessage()) { const sendUserMessage = message.getSendusermessage(); - if(sendUserMessage !== undefined) { + if (sendUserMessage !== undefined) { socketManager.handlerSendUserMessage(user, sendUserMessage); } - }else if (message.hasBanusermessage()) { + } else if (message.hasBanusermessage()) { const banUserMessage = message.getBanusermessage(); - if(banUserMessage !== undefined) { + if (banUserMessage !== undefined) { socketManager.handlerBanUserMessage(room, user, banUserMessage); } } else { - throw new Error('Unhandled message type'); + throw new Error("Unhandled message type"); } } } catch (e) { emitError(call, e); call.end(); } - }); - call.on('end', () => { - debug('joinRoom ended'); + call.on("end", () => { + debug("joinRoom ended"); if (user !== null && room !== null) { socketManager.leaveRoom(room, user); } @@ -105,41 +127,40 @@ const roomManager: IRoomManagerServer = { user = null; }); - call.on('error', (err: Error) => { - console.error('An error occurred in joinRoom stream:', err); + call.on("error", (err: Error) => { + console.error("An error occurred in joinRoom stream:", err); }); - }, listenZone(call: ZoneSocket): void { - debug('listenZone called'); + debug("listenZone called"); const zoneMessage = call.request; socketManager.addZoneListener(call, zoneMessage.getRoomid(), zoneMessage.getX(), zoneMessage.getY()); - call.on('cancelled', () => { - debug('listenZone cancelled'); + call.on("cancelled", () => { + debug("listenZone cancelled"); socketManager.removeZoneListener(call, zoneMessage.getRoomid(), zoneMessage.getX(), zoneMessage.getY()); call.end(); - }) - - call.on('close', () => { - debug('listenZone connection closed'); + }); + + call.on("close", () => { + debug("listenZone connection closed"); socketManager.removeZoneListener(call, zoneMessage.getRoomid(), zoneMessage.getX(), zoneMessage.getY()); - }).on('error', (e) => { - console.error('An error occurred in listenZone stream:', e); + }).on("error", (e) => { + console.error("An error occurred in listenZone stream:", e); socketManager.removeZoneListener(call, zoneMessage.getRoomid(), zoneMessage.getX(), zoneMessage.getY()); call.end(); }); }, adminRoom(call: AdminSocket): void { - console.log('adminRoom called'); + console.log("adminRoom called"); const admin = new Admin(call); - let room: GameRoom|null = null; + let room: GameRoom | null = null; - call.on('data', (message: AdminPusherToBackMessage) => { + call.on("data", (message: AdminPusherToBackMessage) => { try { if (room === null) { if (message.hasSubscribetoroom()) { @@ -148,18 +169,17 @@ const roomManager: IRoomManagerServer = { room = gameRoom; }); } else { - throw new Error('The first message sent MUST be of type JoinRoomMessage'); + throw new Error("The first message sent MUST be of type JoinRoomMessage"); } } } catch (e) { emitError(call, e); call.end(); } - }); - call.on('end', () => { - debug('joinRoom ended'); + call.on("end", () => { + debug("joinRoom ended"); if (room !== null) { socketManager.leaveAdminRoom(room, admin); } @@ -167,18 +187,21 @@ const roomManager: IRoomManagerServer = { room = null; }); - call.on('error', (err: Error) => { - console.error('An error occurred in joinAdminRoom stream:', err); + call.on("error", (err: Error) => { + console.error("An error occurred in joinAdminRoom stream:", err); }); }, sendAdminMessage(call: ServerUnaryCall, callback: sendUnaryData): void { - - socketManager.sendAdminMessage(call.request.getRoomid(), call.request.getRecipientuuid(), call.request.getMessage()); + socketManager.sendAdminMessage( + call.request.getRoomid(), + call.request.getRecipientuuid(), + call.request.getMessage() + ); callback(null, new EmptyMessage()); }, sendGlobalAdminMessage(call: ServerUnaryCall, callback: sendUnaryData): void { - throw new Error('Not implemented yet'); + throw new Error("Not implemented yet"); // TODO callback(null, new EmptyMessage()); }, @@ -192,14 +215,20 @@ const roomManager: IRoomManagerServer = { socketManager.sendAdminRoomMessage(call.request.getRoomid(), call.request.getMessage()); callback(null, new EmptyMessage()); }, - sendWorldFullWarningToRoom(call: ServerUnaryCall, callback: sendUnaryData): void { + sendWorldFullWarningToRoom( + call: ServerUnaryCall, + callback: sendUnaryData + ): void { socketManager.dispatchWorlFullWarning(call.request.getRoomid()); callback(null, new EmptyMessage()); }, - sendRefreshRoomPrompt(call: ServerUnaryCall, callback: sendUnaryData): void { + sendRefreshRoomPrompt( + call: ServerUnaryCall, + callback: sendUnaryData + ): void { socketManager.dispatchRoomRefresh(call.request.getRoomid()); callback(null, new EmptyMessage()); }, }; -export {roomManager}; +export { roomManager }; diff --git a/back/src/Server/server/app.ts b/back/src/Server/server/app.ts index 3b98a9b3..4c422d5c 100644 --- a/back/src/Server/server/app.ts +++ b/back/src/Server/server/app.ts @@ -1,13 +1,13 @@ -import { App as _App, AppOptions } from 'uWebSockets.js'; -import BaseApp from './baseapp'; -import { extend } from './utils'; -import { UwsApp } from './types'; +import { App as _App, AppOptions } from "uWebSockets.js"; +import BaseApp from "./baseapp"; +import { extend } from "./utils"; +import { UwsApp } from "./types"; class App extends (_App) { - constructor(options: AppOptions = {}) { - super(options); // eslint-disable-line constructor-super - extend(this, new BaseApp()); - } + constructor(options: AppOptions = {}) { + super(options); // eslint-disable-line constructor-super + extend(this, new BaseApp()); + } } export default App; diff --git a/back/src/Server/server/baseapp.ts b/back/src/Server/server/baseapp.ts index accd8a99..6d973ac7 100644 --- a/back/src/Server/server/baseapp.ts +++ b/back/src/Server/server/baseapp.ts @@ -1,116 +1,109 @@ -import { Readable } from 'stream'; -import { us_listen_socket_close, TemplatedApp, HttpResponse, HttpRequest } from 'uWebSockets.js'; +import { Readable } from "stream"; +import { us_listen_socket_close, TemplatedApp, HttpResponse, HttpRequest } from "uWebSockets.js"; -import formData from './formdata'; -import { stob } from './utils'; -import { Handler } from './types'; -import {join} from "path"; +import formData from "./formdata"; +import { stob } from "./utils"; +import { Handler } from "./types"; +import { join } from "path"; -const contTypes = ['application/x-www-form-urlencoded', 'multipart/form-data']; +const contTypes = ["application/x-www-form-urlencoded", "multipart/form-data"]; const noOp = () => true; const handleBody = (res: HttpResponse, req: HttpRequest) => { - const contType = req.getHeader('content-type'); + const contType = req.getHeader("content-type"); - res.bodyStream = function() { - const stream = new Readable(); - stream._read = noOp; // eslint-disable-line @typescript-eslint/unbound-method + res.bodyStream = function () { + const stream = new Readable(); + stream._read = noOp; // eslint-disable-line @typescript-eslint/unbound-method - this.onData((ab: ArrayBuffer, isLast: boolean) => { - // uint and then slicing is bit faster than slice and then uint - stream.push(new Uint8Array(ab.slice((ab as any).byteOffset, ab.byteLength))); // eslint-disable-line @typescript-eslint/no-explicit-any - if (isLast) { - stream.push(null); - } - }); + this.onData((ab: ArrayBuffer, isLast: boolean) => { + // uint and then slicing is bit faster than slice and then uint + stream.push(new Uint8Array(ab.slice((ab as any).byteOffset, ab.byteLength))); // eslint-disable-line @typescript-eslint/no-explicit-any + if (isLast) { + stream.push(null); + } + }); - return stream; - }; + return stream; + }; - res.body = () => stob(res.bodyStream()); + res.body = () => stob(res.bodyStream()); - if (contType.includes('application/json')) - res.json = async () => JSON.parse(await res.body()); - if (contTypes.map(t => contType.includes(t)).includes(true)) - res.formData = formData.bind(res, contType); + if (contType.includes("application/json")) res.json = async () => JSON.parse(await res.body()); + if (contTypes.map((t) => contType.includes(t)).includes(true)) res.formData = formData.bind(res, contType); }; class BaseApp { - _sockets = new Map(); - ws!: TemplatedApp['ws']; - get!: TemplatedApp['get']; - _post!: TemplatedApp['post']; - _put!: TemplatedApp['put']; - _patch!: TemplatedApp['patch']; - _listen!: TemplatedApp['listen']; + _sockets = new Map(); + ws!: TemplatedApp["ws"]; + get!: TemplatedApp["get"]; + _post!: TemplatedApp["post"]; + _put!: TemplatedApp["put"]; + _patch!: TemplatedApp["patch"]; + _listen!: TemplatedApp["listen"]; - post(pattern: string, handler: Handler) { - if (typeof handler !== 'function') - throw Error(`handler should be a function, given ${typeof handler}.`); - this._post(pattern, (res, req) => { - handleBody(res, req); - handler(res, req); - }); - return this; - } + post(pattern: string, handler: Handler) { + if (typeof handler !== "function") throw Error(`handler should be a function, given ${typeof handler}.`); + this._post(pattern, (res, req) => { + handleBody(res, req); + handler(res, req); + }); + return this; + } - put(pattern: string, handler: Handler) { - if (typeof handler !== 'function') - throw Error(`handler should be a function, given ${typeof handler}.`); - this._put(pattern, (res, req) => { - handleBody(res, req); + put(pattern: string, handler: Handler) { + if (typeof handler !== "function") throw Error(`handler should be a function, given ${typeof handler}.`); + this._put(pattern, (res, req) => { + handleBody(res, req); - handler(res, req); - }); - return this; - } + handler(res, req); + }); + return this; + } - patch(pattern: string, handler: Handler) { - if (typeof handler !== 'function') - throw Error(`handler should be a function, given ${typeof handler}.`); - this._patch(pattern, (res, req) => { - handleBody(res, req); + patch(pattern: string, handler: Handler) { + if (typeof handler !== "function") throw Error(`handler should be a function, given ${typeof handler}.`); + this._patch(pattern, (res, req) => { + handleBody(res, req); - handler(res, req); - }); - return this; - } + handler(res, req); + }); + return this; + } - listen(h: string | number, p: Function | number = noOp, cb?: Function) { - if (typeof p === 'number' && typeof h === 'string') { - this._listen(h, p, socket => { - this._sockets.set(p, socket); - if (cb === undefined) { - throw new Error('cb undefined'); + listen(h: string | number, p: Function | number = noOp, cb?: Function) { + if (typeof p === "number" && typeof h === "string") { + this._listen(h, p, (socket) => { + this._sockets.set(p, socket); + if (cb === undefined) { + throw new Error("cb undefined"); + } + cb(socket); + }); + } else if (typeof h === "number" && typeof p === "function") { + this._listen(h, (socket) => { + this._sockets.set(h, socket); + p(socket); + }); + } else { + throw Error("Argument types: (host: string, port: number, cb?: Function) | (port: number, cb?: Function)"); } - cb(socket); - }); - } else if (typeof h === 'number' && typeof p === 'function') { - this._listen(h, socket => { - this._sockets.set(h, socket); - p(socket); - }); - } else { - throw Error( - 'Argument types: (host: string, port: number, cb?: Function) | (port: number, cb?: Function)' - ); + + return this; } - return this; - } - - close(port: null | number = null) { - if (port) { - this._sockets.has(port) && us_listen_socket_close(this._sockets.get(port)); - this._sockets.delete(port); - } else { - this._sockets.forEach(app => { - us_listen_socket_close(app); - }); - this._sockets.clear(); + close(port: null | number = null) { + if (port) { + this._sockets.has(port) && us_listen_socket_close(this._sockets.get(port)); + this._sockets.delete(port); + } else { + this._sockets.forEach((app) => { + us_listen_socket_close(app); + }); + this._sockets.clear(); + } + return this; } - return this; - } } export default BaseApp; diff --git a/back/src/Server/server/formdata.ts b/back/src/Server/server/formdata.ts index 9dd08440..66e51db4 100644 --- a/back/src/Server/server/formdata.ts +++ b/back/src/Server/server/formdata.ts @@ -1,100 +1,99 @@ -import { createWriteStream } from 'fs'; -import { join, dirname } from 'path'; -import Busboy from 'busboy'; -import mkdirp from 'mkdirp'; +import { createWriteStream } from "fs"; +import { join, dirname } from "path"; +import Busboy from "busboy"; +import mkdirp from "mkdirp"; function formData( - contType: string, - options: busboy.BusboyConfig & { - abortOnLimit?: boolean; - tmpDir?: string; - onFile?: ( - fieldname: string, - file: NodeJS.ReadableStream, - filename: string, - encoding: string, - mimetype: string - ) => string; - onField?: (fieldname: string, value: any) => void; // eslint-disable-line @typescript-eslint/no-explicit-any - filename?: (oldName: string) => string; - } = {} + contType: string, + options: busboy.BusboyConfig & { + abortOnLimit?: boolean; + tmpDir?: string; + onFile?: ( + fieldname: string, + file: NodeJS.ReadableStream, + filename: string, + encoding: string, + mimetype: string + ) => string; + onField?: (fieldname: string, value: any) => void; // eslint-disable-line @typescript-eslint/no-explicit-any + filename?: (oldName: string) => string; + } = {} ) { - console.log('Enter form data'); - options.headers = { - 'content-type': contType - }; + console.log("Enter form data"); + options.headers = { + "content-type": contType, + }; - return new Promise((resolve, reject) => { - const busb = new Busboy(options); - const ret = {}; + return new Promise((resolve, reject) => { + const busb = new Busboy(options); + const ret = {}; - this.bodyStream().pipe(busb); + this.bodyStream().pipe(busb); - busb.on('limit', () => { - if (options.abortOnLimit) { - reject(Error('limit')); - } + busb.on("limit", () => { + if (options.abortOnLimit) { + reject(Error("limit")); + } + }); + + busb.on("file", function (fieldname, file, filename, encoding, mimetype) { + const value: { filePath: string | undefined; filename: string; encoding: string; mimetype: string } = { + filename, + encoding, + mimetype, + filePath: undefined, + }; + + if (typeof options.tmpDir === "string") { + if (typeof options.filename === "function") filename = options.filename(filename); + const fileToSave = join(options.tmpDir, filename); + mkdirp(dirname(fileToSave)); + + file.pipe(createWriteStream(fileToSave)); + value.filePath = fileToSave; + } + if (typeof options.onFile === "function") { + value.filePath = options.onFile(fieldname, file, filename, encoding, mimetype) || value.filePath; + } + + setRetValue(ret, fieldname, value); + }); + + busb.on("field", function (fieldname, value) { + if (typeof options.onField === "function") options.onField(fieldname, value); + + setRetValue(ret, fieldname, value); + }); + + busb.on("finish", function () { + resolve(ret); + }); + + busb.on("error", reject); }); - - busb.on('file', function(fieldname, file, filename, encoding, mimetype) { - const value: { filePath: string|undefined, filename: string, encoding:string, mimetype: string } = { - filename, - encoding, - mimetype, - filePath: undefined - }; - - if (typeof options.tmpDir === 'string') { - if (typeof options.filename === 'function') filename = options.filename(filename); - const fileToSave = join(options.tmpDir, filename); - mkdirp(dirname(fileToSave)); - - file.pipe(createWriteStream(fileToSave)); - value.filePath = fileToSave; - } - if (typeof options.onFile === 'function') { - value.filePath = - options.onFile(fieldname, file, filename, encoding, mimetype) || value.filePath; - } - - setRetValue(ret, fieldname, value); - }); - - busb.on('field', function(fieldname, value) { - if (typeof options.onField === 'function') options.onField(fieldname, value); - - setRetValue(ret, fieldname, value); - }); - - busb.on('finish', function() { - resolve(ret); - }); - - busb.on('error', reject); - }); } function setRetValue( - ret: { [x: string]: any }, // eslint-disable-line @typescript-eslint/no-explicit-any - fieldname: string, - value: { filename: string; encoding: string; mimetype: string; filePath?: string } | any // eslint-disable-line @typescript-eslint/no-explicit-any + ret: { [x: string]: any }, // eslint-disable-line @typescript-eslint/no-explicit-any + fieldname: string, + value: { filename: string; encoding: string; mimetype: string; filePath?: string } | any // eslint-disable-line @typescript-eslint/no-explicit-any ) { - if (fieldname.endsWith('[]')) { - fieldname = fieldname.slice(0, fieldname.length - 2); - if (Array.isArray(ret[fieldname])) { - ret[fieldname].push(value); + if (fieldname.endsWith("[]")) { + fieldname = fieldname.slice(0, fieldname.length - 2); + if (Array.isArray(ret[fieldname])) { + ret[fieldname].push(value); + } else { + ret[fieldname] = [value]; + } } else { - ret[fieldname] = [value]; + if (Array.isArray(ret[fieldname])) { + ret[fieldname].push(value); + } else if (ret[fieldname]) { + ret[fieldname] = [ret[fieldname], value]; + } else { + ret[fieldname] = value; + } } - } else { - if (Array.isArray(ret[fieldname])) { - ret[fieldname].push(value); - } else if (ret[fieldname]) { - ret[fieldname] = [ret[fieldname], value]; - } else { - ret[fieldname] = value; - } - } } export default formData; diff --git a/back/src/Server/server/sslapp.ts b/back/src/Server/server/sslapp.ts index 46ae89a5..80df0e4a 100644 --- a/back/src/Server/server/sslapp.ts +++ b/back/src/Server/server/sslapp.ts @@ -1,13 +1,13 @@ -import { SSLApp as _SSLApp, AppOptions } from 'uWebSockets.js'; -import BaseApp from './baseapp'; -import { extend } from './utils'; -import { UwsApp } from './types'; +import { SSLApp as _SSLApp, AppOptions } from "uWebSockets.js"; +import BaseApp from "./baseapp"; +import { extend } from "./utils"; +import { UwsApp } from "./types"; class SSLApp extends (_SSLApp) { - constructor(options: AppOptions) { - super(options); // eslint-disable-line constructor-super - extend(this, new BaseApp()); - } + constructor(options: AppOptions) { + super(options); // eslint-disable-line constructor-super + extend(this, new BaseApp()); + } } export default SSLApp; diff --git a/back/src/Server/server/types.ts b/back/src/Server/server/types.ts index 3d0f48c7..afc21d17 100644 --- a/back/src/Server/server/types.ts +++ b/back/src/Server/server/types.ts @@ -1,9 +1,9 @@ -import { AppOptions, TemplatedApp, HttpResponse, HttpRequest } from 'uWebSockets.js'; +import { AppOptions, TemplatedApp, HttpResponse, HttpRequest } from "uWebSockets.js"; export type UwsApp = { - (options: AppOptions): TemplatedApp; - new (options: AppOptions): TemplatedApp; - prototype: TemplatedApp; + (options: AppOptions): TemplatedApp; + new (options: AppOptions): TemplatedApp; + prototype: TemplatedApp; }; export type Handler = (res: HttpResponse, req: HttpRequest) => void; diff --git a/back/src/Server/server/utils.ts b/back/src/Server/server/utils.ts index 80ea3938..dc813064 100644 --- a/back/src/Server/server/utils.ts +++ b/back/src/Server/server/utils.ts @@ -1,37 +1,36 @@ -import { ReadStream } from 'fs'; +import { ReadStream } from "fs"; -function extend(who: any, from: any, overwrite = true) { // eslint-disable-line @typescript-eslint/no-explicit-any - const ownProps = Object.getOwnPropertyNames(Object.getPrototypeOf(from)).concat( - Object.keys(from) - ); - ownProps.forEach(prop => { - if (prop === 'constructor' || from[prop] === undefined) return; - if (who[prop] && overwrite) { - who[`_${prop}`] = who[prop]; - } - if (typeof from[prop] === 'function') who[prop] = from[prop].bind(who); - else who[prop] = from[prop]; - }); +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function extend(who: any, from: any, overwrite = true) { + const ownProps = Object.getOwnPropertyNames(Object.getPrototypeOf(from)).concat(Object.keys(from)); + ownProps.forEach((prop) => { + if (prop === "constructor" || from[prop] === undefined) return; + if (who[prop] && overwrite) { + who[`_${prop}`] = who[prop]; + } + if (typeof from[prop] === "function") who[prop] = from[prop].bind(who); + else who[prop] = from[prop]; + }); } function stob(stream: ReadStream): Promise { - return new Promise(resolve => { - const buffers: Buffer[] = []; - stream.on('data', buffers.push.bind(buffers)); + return new Promise((resolve) => { + const buffers: Buffer[] = []; + stream.on("data", buffers.push.bind(buffers)); - stream.on('end', () => { - switch (buffers.length) { - case 0: - resolve(Buffer.allocUnsafe(0)); - break; - case 1: - resolve(buffers[0]); - break; - default: - resolve(Buffer.concat(buffers)); - } + stream.on("end", () => { + switch (buffers.length) { + case 0: + resolve(Buffer.allocUnsafe(0)); + break; + case 1: + resolve(buffers[0]); + break; + default: + resolve(Buffer.concat(buffers)); + } + }); }); - }); } export { extend, stob }; diff --git a/back/src/Server/sifrr.server.ts b/back/src/Server/sifrr.server.ts index 47fba02c..4ef03721 100644 --- a/back/src/Server/sifrr.server.ts +++ b/back/src/Server/sifrr.server.ts @@ -1,19 +1,19 @@ -import { parse } from 'query-string'; -import { HttpRequest } from 'uWebSockets.js'; -import App from './server/app'; -import SSLApp from './server/sslapp'; -import * as types from './server/types'; +import { parse } from "query-string"; +import { HttpRequest } from "uWebSockets.js"; +import App from "./server/app"; +import SSLApp from "./server/sslapp"; +import * as types from "./server/types"; const getQuery = (req: HttpRequest) => { - return parse(req.getQuery()); + return parse(req.getQuery()); }; export { App, SSLApp, getQuery }; -export * from './server/types'; +export * from "./server/types"; export default { - App, - SSLApp, - getQuery, - ...types + App, + SSLApp, + getQuery, + ...types, }; diff --git a/back/src/Services/ArrayHelper.ts b/back/src/Services/ArrayHelper.ts index 67321d1b..8af1da9f 100644 --- a/back/src/Services/ArrayHelper.ts +++ b/back/src/Services/ArrayHelper.ts @@ -1,3 +1,3 @@ -export const arrayIntersect = (array1: string[], array2: string[]) : boolean => { - return array1.filter(value => array2.includes(value)).length > 0; -} \ No newline at end of file +export const arrayIntersect = (array1: string[], array2: string[]): boolean => { + return array1.filter((value) => array2.includes(value)).length > 0; +}; diff --git a/back/src/Services/ClientEventsEmitter.ts b/back/src/Services/ClientEventsEmitter.ts index 381137a1..0f56d55c 100644 --- a/back/src/Services/ClientEventsEmitter.ts +++ b/back/src/Services/ClientEventsEmitter.ts @@ -1,7 +1,7 @@ -const EventEmitter = require('events'); +const EventEmitter = require("events"); -const clientJoinEvent = 'clientJoin'; -const clientLeaveEvent = 'clientLeave'; +const clientJoinEvent = "clientJoin"; +const clientLeaveEvent = "clientLeave"; class ClientEventsEmitter extends EventEmitter { emitClientJoin(clientUUid: string, roomId: string): void { diff --git a/back/src/Services/CpuTracker.ts b/back/src/Services/CpuTracker.ts index c7d57f3d..3d06ca70 100644 --- a/back/src/Services/CpuTracker.ts +++ b/back/src/Services/CpuTracker.ts @@ -1,6 +1,6 @@ -import {CPU_OVERHEAT_THRESHOLD} from "../Enum/EnvironmentVariable"; +import { CPU_OVERHEAT_THRESHOLD } from "../Enum/EnvironmentVariable"; -function secNSec2ms(secNSec: Array|number) { +function secNSec2ms(secNSec: Array | number) { if (Array.isArray(secNSec)) { return secNSec[0] * 1000 + secNSec[1] / 1000000; } @@ -12,17 +12,17 @@ class CpuTracker { private overHeating: boolean = false; constructor() { - let time = process.hrtime.bigint() - let usage = process.cpuUsage() + let time = process.hrtime.bigint(); + let usage = process.cpuUsage(); setInterval(() => { const elapTime = process.hrtime.bigint(); - const elapUsage = process.cpuUsage(usage) - usage = process.cpuUsage() + const elapUsage = process.cpuUsage(usage); + usage = process.cpuUsage(); const elapTimeMS = elapTime - time; - const elapUserMS = secNSec2ms(elapUsage.user) - const elapSystMS = secNSec2ms(elapUsage.system) - this.cpuPercent = Math.round(100 * (elapUserMS + elapSystMS) / Number(elapTimeMS) * 1000000) + const elapUserMS = secNSec2ms(elapUsage.user); + const elapSystMS = secNSec2ms(elapUsage.system); + this.cpuPercent = Math.round(((100 * (elapUserMS + elapSystMS)) / Number(elapTimeMS)) * 1000000); time = elapTime; diff --git a/back/src/Services/GaugeManager.ts b/back/src/Services/GaugeManager.ts index 80712856..6d2183d8 100644 --- a/back/src/Services/GaugeManager.ts +++ b/back/src/Services/GaugeManager.ts @@ -1,4 +1,4 @@ -import {Counter, Gauge} from "prom-client"; +import { Counter, Gauge } from "prom-client"; //this class should manage all the custom metrics used by prometheus class GaugeManager { @@ -10,29 +10,29 @@ class GaugeManager { constructor() { this.nbRoomsGauge = new Gauge({ - name: 'workadventure_nb_rooms', - help: 'Number of active rooms' + name: "workadventure_nb_rooms", + help: "Number of active rooms", }); this.nbClientsGauge = new Gauge({ - name: 'workadventure_nb_sockets', - help: 'Number of connected sockets', - labelNames: [ ] + name: "workadventure_nb_sockets", + help: "Number of connected sockets", + labelNames: [], }); this.nbClientsPerRoomGauge = new Gauge({ - name: 'workadventure_nb_clients_per_room', - help: 'Number of clients per room', - labelNames: [ 'room' ] + name: "workadventure_nb_clients_per_room", + help: "Number of clients per room", + labelNames: ["room"], }); this.nbGroupsPerRoomCounter = new Counter({ - name: 'workadventure_counter_groups_per_room', - help: 'Counter of groups per room', - labelNames: [ 'room' ] + name: "workadventure_counter_groups_per_room", + help: "Counter of groups per room", + labelNames: ["room"], }); this.nbGroupsPerRoomGauge = new Gauge({ - name: 'workadventure_nb_groups_per_room', - help: 'Number of groups per room', - labelNames: [ 'room' ] + name: "workadventure_nb_groups_per_room", + help: "Number of groups per room", + labelNames: ["room"], }); } @@ -54,13 +54,13 @@ class GaugeManager { } incNbGroupsPerRoomGauge(roomId: string): void { - this.nbGroupsPerRoomCounter.inc({ room: roomId }) - this.nbGroupsPerRoomGauge.inc({ room: roomId }) + this.nbGroupsPerRoomCounter.inc({ room: roomId }); + this.nbGroupsPerRoomGauge.inc({ room: roomId }); } - + decNbGroupsPerRoomGauge(roomId: string): void { - this.nbGroupsPerRoomGauge.dec({ room: roomId }) + this.nbGroupsPerRoomGauge.dec({ room: roomId }); } } -export const gaugeManager = new GaugeManager(); \ No newline at end of file +export const gaugeManager = new GaugeManager(); diff --git a/back/src/Services/MessageHelpers.ts b/back/src/Services/MessageHelpers.ts index b2600a4a..493f7173 100644 --- a/back/src/Services/MessageHelpers.ts +++ b/back/src/Services/MessageHelpers.ts @@ -1,5 +1,5 @@ -import {ErrorMessage, ServerToClientMessage} from "../Messages/generated/messages_pb"; -import {UserSocket} from "_Model/User"; +import { ErrorMessage, ServerToClientMessage } from "../Messages/generated/messages_pb"; +import { UserSocket } from "_Model/User"; export function emitError(Client: UserSocket, message: string): void { const errorMessage = new ErrorMessage(); @@ -9,7 +9,7 @@ export function emitError(Client: UserSocket, message: string): void { serverToClientMessage.setErrormessage(errorMessage); //if (!Client.disconnecting) { - Client.write(serverToClientMessage); + Client.write(serverToClientMessage); //} console.warn(message); } diff --git a/back/src/Services/SocketManager.ts b/back/src/Services/SocketManager.ts index a56a1ac4..e61763cd 100644 --- a/back/src/Services/SocketManager.ts +++ b/back/src/Services/SocketManager.ts @@ -1,4 +1,4 @@ -import {GameRoom} from "../Model/GameRoom"; +import { GameRoom } from "../Model/GameRoom"; import { ItemEventMessage, ItemStateMessage, @@ -27,39 +27,39 @@ import { WorldFullWarningMessage, UserLeftZoneMessage, EmoteEventMessage, - BanUserMessage, RefreshRoomMessage, EmotePromptMessage, + BanUserMessage, + RefreshRoomMessage, + EmotePromptMessage, } from "../Messages/generated/messages_pb"; -import {User, UserSocket} from "../Model/User"; -import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils"; -import {Group} from "../Model/Group"; -import {cpuTracker} from "./CpuTracker"; +import { User, UserSocket } from "../Model/User"; +import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils"; +import { Group } from "../Model/Group"; +import { cpuTracker } from "./CpuTracker"; import { GROUP_RADIUS, JITSI_ISS, MINIMUM_DISTANCE, SECRET_JITSI_KEY, - TURN_STATIC_AUTH_SECRET + TURN_STATIC_AUTH_SECRET, } from "../Enum/EnvironmentVariable"; -import {Movable} from "../Model/Movable"; -import {PositionInterface} from "../Model/PositionInterface"; +import { Movable } from "../Model/Movable"; +import { PositionInterface } from "../Model/PositionInterface"; import Jwt from "jsonwebtoken"; -import {JITSI_URL} from "../Enum/EnvironmentVariable"; -import {clientEventsEmitter} from "./ClientEventsEmitter"; -import {gaugeManager} from "./GaugeManager"; -import {ZoneSocket} from "../RoomManager"; -import {Zone} from "_Model/Zone"; +import { JITSI_URL } from "../Enum/EnvironmentVariable"; +import { clientEventsEmitter } from "./ClientEventsEmitter"; +import { gaugeManager } from "./GaugeManager"; +import { ZoneSocket } from "../RoomManager"; +import { Zone } from "_Model/Zone"; import Debug from "debug"; -import {Admin} from "_Model/Admin"; +import { Admin } from "_Model/Admin"; import crypto from "crypto"; - -const debug = Debug('sockermanager'); +const debug = Debug("sockermanager"); function emitZoneMessage(subMessage: SubToPusherMessage, socket: ZoneSocket): void { // TODO: should we batch those every 100ms? const batchMessage = new BatchToPusherMessage(); batchMessage.addPayload(subMessage); - socket.write(batchMessage); } @@ -68,7 +68,6 @@ export class SocketManager { private rooms: Map = new Map(); constructor() { - clientEventsEmitter.registerToClientJoin((clientUUid: string, roomId: string) => { gaugeManager.incNbClientPerRoomGauge(roomId); }); @@ -77,16 +76,18 @@ export class SocketManager { }); } - public async handleJoinRoom(socket: UserSocket, joinRoomMessage: JoinRoomMessage): Promise<{ room: GameRoom; user: User }> { - + public async handleJoinRoom( + socket: UserSocket, + joinRoomMessage: JoinRoomMessage + ): Promise<{ room: GameRoom; user: User }> { //join new previous room - const {room, user} = await this.joinRoom(socket, joinRoomMessage); - + const { room, user } = await this.joinRoom(socket, joinRoomMessage); + if (!socket.writable) { - console.warn('Socket was aborted'); + console.warn("Socket was aborted"); return { room, - user + user, }; } const roomJoinedMessage = new RoomJoinedMessage(); @@ -108,9 +109,8 @@ export class SocketManager { return { room, - user + user, }; - } handleUserMovesMessage(room: GameRoom, user: User, userMovesMessage: UserMovesMessage) { @@ -124,13 +124,12 @@ export class SocketManager { } if (position === undefined) { - throw new Error('Position not found in message'); + throw new Error("Position not found in message"); } const viewport = userMoves.viewport; if (viewport === undefined) { - throw new Error('Viewport not found in message'); + throw new Error("Viewport not found in message"); } - // update position in the world room.updatePosition(user, ProtobufUtils.toPointInterface(position)); @@ -189,7 +188,11 @@ export class SocketManager { //send only at user const remoteUser = room.getUsers().get(data.getReceiverid()); if (remoteUser === undefined) { - console.warn("While exchanging a WebRTC signal: client with id ", data.getReceiverid(), " does not exist. This might be a race condition."); + console.warn( + "While exchanging a WebRTC signal: client with id ", + data.getReceiverid(), + " does not exist. This might be a race condition." + ); return; } @@ -197,8 +200,8 @@ export class SocketManager { webrtcSignalToClient.setUserid(user.id); webrtcSignalToClient.setSignal(data.getSignal()); // TODO: only compute credentials if data.signal.type === "offer" - if (TURN_STATIC_AUTH_SECRET !== '') { - const {username, password} = this.getTURNCredentials(''+user.id, TURN_STATIC_AUTH_SECRET); + if (TURN_STATIC_AUTH_SECRET !== "") { + const { username, password } = this.getTURNCredentials("" + user.id, TURN_STATIC_AUTH_SECRET); webrtcSignalToClient.setWebrtcusername(username); webrtcSignalToClient.setWebrtcpassword(password); } @@ -207,7 +210,7 @@ export class SocketManager { serverToClientMessage.setWebrtcsignaltoclientmessage(webrtcSignalToClient); //if (!client.disconnecting) { - remoteUser.socket.write(serverToClientMessage); + remoteUser.socket.write(serverToClientMessage); //} } @@ -215,7 +218,11 @@ export class SocketManager { //send only at user const remoteUser = room.getUsers().get(data.getReceiverid()); if (remoteUser === undefined) { - console.warn("While exchanging a WEBRTC_SCREEN_SHARING signal: client with id ", data.getReceiverid(), " does not exist. This might be a race condition."); + console.warn( + "While exchanging a WEBRTC_SCREEN_SHARING signal: client with id ", + data.getReceiverid(), + " does not exist. This might be a race condition." + ); return; } @@ -223,8 +230,8 @@ export class SocketManager { webrtcSignalToClient.setUserid(user.id); webrtcSignalToClient.setSignal(data.getSignal()); // TODO: only compute credentials if data.signal.type === "offer" - if (TURN_STATIC_AUTH_SECRET !== '') { - const {username, password} = this.getTURNCredentials(''+user.id, TURN_STATIC_AUTH_SECRET); + if (TURN_STATIC_AUTH_SECRET !== "") { + const { username, password } = this.getTURNCredentials("" + user.id, TURN_STATIC_AUTH_SECRET); webrtcSignalToClient.setWebrtcusername(username); webrtcSignalToClient.setWebrtcpassword(password); } @@ -233,11 +240,11 @@ export class SocketManager { serverToClientMessage.setWebrtcscreensharingsignaltoclientmessage(webrtcSignalToClient); //if (!client.disconnecting) { - remoteUser.socket.write(serverToClientMessage); + remoteUser.socket.write(serverToClientMessage); //} } - leaveRoom(room: GameRoom, user: User){ + leaveRoom(room: GameRoom, user: User) { // leave previous room and world try { //user leave previous world @@ -249,33 +256,39 @@ export class SocketManager { } } finally { clientEventsEmitter.emitClientLeave(user.uuid, room.roomId); - console.log('A user left'); + console.log("A user left"); } } async getOrCreateRoom(roomId: string): Promise { //check and create new world for a room - let world = this.rooms.get(roomId) - if(world === undefined){ + let world = this.rooms.get(roomId); + if (world === undefined) { world = new GameRoom( roomId, (user: User, group: Group) => this.joinWebRtcRoom(user, group), (user: User, group: Group) => this.disConnectedUser(user, group), MINIMUM_DISTANCE, GROUP_RADIUS, - (thing: Movable, fromZone: Zone|null, listener: ZoneSocket) => this.onZoneEnter(thing, fromZone, listener), - (thing: Movable, position:PositionInterface, listener: ZoneSocket) => this.onClientMove(thing, position, listener), - (thing: Movable, newZone: Zone|null, listener: ZoneSocket) => this.onClientLeave(thing, newZone, listener), - (emoteEventMessage:EmoteEventMessage, listener: ZoneSocket) => this.onEmote(emoteEventMessage, listener), + (thing: Movable, fromZone: Zone | null, listener: ZoneSocket) => + this.onZoneEnter(thing, fromZone, listener), + (thing: Movable, position: PositionInterface, listener: ZoneSocket) => + this.onClientMove(thing, position, listener), + (thing: Movable, newZone: Zone | null, listener: ZoneSocket) => + this.onClientLeave(thing, newZone, listener), + (emoteEventMessage: EmoteEventMessage, listener: ZoneSocket) => + this.onEmote(emoteEventMessage, listener) ); gaugeManager.incNbRoomGauge(); this.rooms.set(roomId, world); } - return Promise.resolve(world) + return Promise.resolve(world); } - private async joinRoom(socket: UserSocket, joinRoomMessage: JoinRoomMessage): Promise<{ room: GameRoom; user: User }> { - + private async joinRoom( + socket: UserSocket, + joinRoomMessage: JoinRoomMessage + ): Promise<{ room: GameRoom; user: User }> { const roomId = joinRoomMessage.getRoomid(); const room = await socketManager.getOrCreateRoom(roomId); @@ -284,15 +297,15 @@ export class SocketManager { const user = room.join(socket, joinRoomMessage); clientEventsEmitter.emitClientJoin(user.uuid, roomId); - console.log(new Date().toISOString() + ' A user joined'); - return {room, user}; + console.log(new Date().toISOString() + " A user joined"); + return { room, user }; } - private onZoneEnter(thing: Movable, fromZone: Zone|null, listener: ZoneSocket) { + private onZoneEnter(thing: Movable, fromZone: Zone | null, listener: ZoneSocket) { if (thing instanceof User) { const userJoinedZoneMessage = new UserJoinedZoneMessage(); if (!Number.isInteger(thing.id)) { - throw new Error('clientUser.userId is not an integer '+thing.id); + throw new Error("clientUser.userId is not an integer " + thing.id); } userJoinedZoneMessage.setUserid(thing.id); userJoinedZoneMessage.setName(thing.name); @@ -312,11 +325,11 @@ export class SocketManager { } else if (thing instanceof Group) { this.emitCreateUpdateGroupEvent(listener, fromZone, thing); } else { - console.error('Unexpected type for Movable.'); + console.error("Unexpected type for Movable."); } } - private onClientMove(thing: Movable, position:PositionInterface, listener: ZoneSocket): void { + private onClientMove(thing: Movable, position: PositionInterface, listener: ZoneSocket): void { if (thing instanceof User) { const userMovedMessage = new UserMovedMessage(); userMovedMessage.setUserid(thing.id); @@ -331,21 +344,20 @@ export class SocketManager { } else if (thing instanceof Group) { this.emitCreateUpdateGroupEvent(listener, null, thing); } else { - console.error('Unexpected type for Movable.'); + console.error("Unexpected type for Movable."); } } - private onClientLeave(thing: Movable, newZone: Zone|null, listener: ZoneSocket) { + private onClientLeave(thing: Movable, newZone: Zone | null, listener: ZoneSocket) { if (thing instanceof User) { this.emitUserLeftEvent(listener, thing.id, newZone); } else if (thing instanceof Group) { this.emitDeleteGroupEvent(listener, thing.getId(), newZone); } else { - console.error('Unexpected type for Movable.'); + console.error("Unexpected type for Movable."); } } - private onEmote(emoteEventMessage: EmoteEventMessage, client: ZoneSocket) { const subMessage = new SubToPusherMessage(); subMessage.setEmoteeventmessage(emoteEventMessage); @@ -353,7 +365,7 @@ export class SocketManager { emitZoneMessage(subMessage, client); } - private emitCreateUpdateGroupEvent(client: ZoneSocket, fromZone: Zone|null, group: Group): void { + private emitCreateUpdateGroupEvent(client: ZoneSocket, fromZone: Zone | null, group: Group): void { const position = group.getPosition(); const pointMessage = new PointMessage(); pointMessage.setX(Math.floor(position.x)); @@ -371,7 +383,7 @@ export class SocketManager { //client.emitInBatch(subMessage); } - private emitDeleteGroupEvent(client: ZoneSocket, groupId: number, newZone: Zone|null): void { + private emitDeleteGroupEvent(client: ZoneSocket, groupId: number, newZone: Zone | null): void { const groupDeleteMessage = new GroupLeftZoneMessage(); groupDeleteMessage.setGroupid(groupId); groupDeleteMessage.setTozone(this.toProtoZone(newZone)); @@ -383,7 +395,7 @@ export class SocketManager { //user.emitInBatch(subMessage); } - private emitUserLeftEvent(client: ZoneSocket, userId: number, newZone: Zone|null): void { + private emitUserLeftEvent(client: ZoneSocket, userId: number, newZone: Zone | null): void { const userLeftMessage = new UserLeftZoneMessage(); userLeftMessage.setUserid(userId); userLeftMessage.setTozone(this.toProtoZone(newZone)); @@ -394,7 +406,7 @@ export class SocketManager { emitZoneMessage(subMessage, client); } - private toProtoZone(zone: Zone|null): ProtoZone|undefined { + private toProtoZone(zone: Zone | null): ProtoZone | undefined { if (zone !== null) { const zoneMessage = new ProtoZone(); zoneMessage.setX(zone.x); @@ -405,7 +417,6 @@ export class SocketManager { } private joinWebRtcRoom(user: User, group: Group) { - for (const otherUser of group.getUsers()) { if (user === otherUser) { continue; @@ -416,8 +427,8 @@ export class SocketManager { webrtcStartMessage1.setUserid(otherUser.id); webrtcStartMessage1.setName(otherUser.name); webrtcStartMessage1.setInitiator(true); - if (TURN_STATIC_AUTH_SECRET !== '') { - const {username, password} = this.getTURNCredentials(''+otherUser.id, TURN_STATIC_AUTH_SECRET); + if (TURN_STATIC_AUTH_SECRET !== "") { + const { username, password } = this.getTURNCredentials("" + otherUser.id, TURN_STATIC_AUTH_SECRET); webrtcStartMessage1.setWebrtcusername(username); webrtcStartMessage1.setWebrtcpassword(password); } @@ -426,16 +437,16 @@ export class SocketManager { serverToClientMessage1.setWebrtcstartmessage(webrtcStartMessage1); //if (!user.socket.disconnecting) { - user.socket.write(serverToClientMessage1); - //console.log('Sending webrtcstart initiator to '+user.socket.userId) + user.socket.write(serverToClientMessage1); + //console.log('Sending webrtcstart initiator to '+user.socket.userId) //} const webrtcStartMessage2 = new WebRtcStartMessage(); webrtcStartMessage2.setUserid(user.id); webrtcStartMessage2.setName(user.name); webrtcStartMessage2.setInitiator(false); - if (TURN_STATIC_AUTH_SECRET !== '') { - const {username, password} = this.getTURNCredentials(''+user.id, TURN_STATIC_AUTH_SECRET); + if (TURN_STATIC_AUTH_SECRET !== "") { + const { username, password } = this.getTURNCredentials("" + user.id, TURN_STATIC_AUTH_SECRET); webrtcStartMessage2.setWebrtcusername(username); webrtcStartMessage2.setWebrtcpassword(password); } @@ -444,10 +455,9 @@ export class SocketManager { serverToClientMessage2.setWebrtcstartmessage(webrtcStartMessage2); //if (!otherUser.socket.disconnecting) { - otherUser.socket.write(serverToClientMessage2); - //console.log('Sending webrtcstart to '+otherUser.socket.userId) + otherUser.socket.write(serverToClientMessage2); + //console.log('Sending webrtcstart to '+otherUser.socket.userId) //} - } } @@ -456,17 +466,17 @@ export class SocketManager { * and the Coturn server. * The Coturn server should be initialized with parameters: `--use-auth-secret --static-auth-secret=MySecretKey` */ - private getTURNCredentials(name: string, secret: string): {username: string, password: string} { - const unixTimeStamp = Math.floor(Date.now()/1000) + 4*3600; // this credential would be valid for the next 4 hours - const username = [unixTimeStamp, name].join(':'); - const hmac = crypto.createHmac('sha1', secret); - hmac.setEncoding('base64'); + private getTURNCredentials(name: string, secret: string): { username: string; password: string } { + const unixTimeStamp = Math.floor(Date.now() / 1000) + 4 * 3600; // this credential would be valid for the next 4 hours + const username = [unixTimeStamp, name].join(":"); + const hmac = crypto.createHmac("sha1", secret); + hmac.setEncoding("base64"); hmac.write(username); hmac.end(); const password = hmac.read(); return { username: username, - password: password + password: password, }; } @@ -489,10 +499,9 @@ export class SocketManager { serverToClientMessage1.setWebrtcdisconnectmessage(webrtcDisconnectMessage1); //if (!otherUser.socket.disconnecting) { - otherUser.socket.write(serverToClientMessage1); + otherUser.socket.write(serverToClientMessage1); //} - const webrtcDisconnectMessage2 = new WebRtcDisconnectMessage(); webrtcDisconnectMessage2.setUserid(otherUser.id); @@ -500,7 +509,7 @@ export class SocketManager { serverToClientMessage2.setWebrtcdisconnectmessage(webrtcDisconnectMessage2); //if (!user.socket.disconnecting) { - user.socket.write(serverToClientMessage2); + user.socket.write(serverToClientMessage2); //} } } @@ -517,40 +526,41 @@ export class SocketManager { console.error('An error occurred on "emitPlayGlobalMessage" event'); console.error(e); } - } public getWorlds(): Map { return this.rooms; } - public handleQueryJitsiJwtMessage(user: User, queryJitsiJwtMessage: QueryJitsiJwtMessage) { const room = queryJitsiJwtMessage.getJitsiroom(); const tag = queryJitsiJwtMessage.getTag(); // FIXME: this is not secure. We should load the JSON for the current room and check rights associated to room instead. - if (SECRET_JITSI_KEY === '') { - throw new Error('You must set the SECRET_JITSI_KEY key to the secret to generate JWT tokens for Jitsi.'); + if (SECRET_JITSI_KEY === "") { + throw new Error("You must set the SECRET_JITSI_KEY key to the secret to generate JWT tokens for Jitsi."); } // Let's see if the current client has const isAdmin = user.tags.includes(tag); - const jwt = Jwt.sign({ - "aud": "jitsi", - "iss": JITSI_ISS, - "sub": JITSI_URL, - "room": room, - "moderator": isAdmin - }, SECRET_JITSI_KEY, { - expiresIn: '1d', - algorithm: "HS256", - header: - { - "alg": "HS256", - "typ": "JWT" - } - }); + const jwt = Jwt.sign( + { + aud: "jitsi", + iss: JITSI_ISS, + sub: JITSI_URL, + room: room, + moderator: isAdmin, + }, + SECRET_JITSI_KEY, + { + expiresIn: "1d", + algorithm: "HS256", + header: { + alg: "HS256", + typ: "JWT", + }, + } + ); const sendJitsiJwtMessage = new SendJitsiJwtMessage(); sendJitsiJwtMessage.setJitsiroom(room); @@ -562,7 +572,7 @@ export class SocketManager { user.socket.write(serverToClientMessage); } - public handlerSendUserMessage(user: User, sendUserMessageToSend: SendUserMessage){ + public handlerSendUserMessage(user: User, sendUserMessageToSend: SendUserMessage) { const sendUserMessage = new SendUserMessage(); sendUserMessage.setMessage(sendUserMessageToSend.getMessage()); sendUserMessage.setType(sendUserMessageToSend.getType()); @@ -572,7 +582,7 @@ export class SocketManager { user.socket.write(serverToClientMessage); } - public handlerBanUserMessage(room: GameRoom, user: User, banUserMessageToSend: BanUserMessage){ + public handlerBanUserMessage(room: GameRoom, user: User, banUserMessageToSend: BanUserMessage) { const banUserMessage = new BanUserMessage(); banUserMessage.setMessage(banUserMessageToSend.getMessage()); banUserMessage.setType(banUserMessageToSend.getType()); @@ -592,7 +602,7 @@ export class SocketManager { public addZoneListener(call: ZoneSocket, roomId: string, x: number, y: number): void { const room = this.rooms.get(roomId); if (!room) { - console.error("In addZoneListener, could not find room with id '" + roomId + "'"); + console.error("In addZoneListener, could not find room with id '" + roomId + "'"); return; } @@ -636,7 +646,7 @@ export class SocketManager { removeZoneListener(call: ZoneSocket, roomId: string, x: number, y: number) { const room = this.rooms.get(roomId); if (!room) { - console.error("In removeZoneListener, could not find room with id '" + roomId + "'"); + console.error("In removeZoneListener, could not find room with id '" + roomId + "'"); return; } @@ -651,7 +661,7 @@ export class SocketManager { return room; } - public leaveAdminRoom(room: GameRoom, admin: Admin){ + public leaveAdminRoom(room: GameRoom, admin: Admin) { room.adminLeave(admin); if (room.isEmpty()) { this.rooms.delete(room.roomId); @@ -663,19 +673,27 @@ export class SocketManager { public sendAdminMessage(roomId: string, recipientUuid: string, message: string): void { const room = this.rooms.get(roomId); if (!room) { - console.error("In sendAdminMessage, could not find room with id '" + roomId + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?"); + console.error( + "In sendAdminMessage, could not find room with id '" + + roomId + + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?" + ); return; } const recipient = room.getUserByUuid(recipientUuid); if (recipient === undefined) { - console.error("In sendAdminMessage, could not find user with id '" + recipientUuid + "'. Maybe the user left the room a few milliseconds ago and there was a race condition?"); + console.error( + "In sendAdminMessage, could not find user with id '" + + recipientUuid + + "'. Maybe the user left the room a few milliseconds ago and there was a race condition?" + ); return; } const sendUserMessage = new SendUserMessage(); sendUserMessage.setMessage(message); - sendUserMessage.setType('ban'); //todo: is the type correct? + sendUserMessage.setType("ban"); //todo: is the type correct? const serverToClientMessage = new ServerToClientMessage(); serverToClientMessage.setSendusermessage(sendUserMessage); @@ -686,13 +704,21 @@ export class SocketManager { public banUser(roomId: string, recipientUuid: string, message: string): void { const room = this.rooms.get(roomId); if (!room) { - console.error("In banUser, could not find room with id '" + roomId + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?"); + console.error( + "In banUser, could not find room with id '" + + roomId + + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?" + ); return; } const recipient = room.getUserByUuid(recipientUuid); if (recipient === undefined) { - console.error("In banUser, could not find user with id '" + recipientUuid + "'. Maybe the user left the room a few milliseconds ago and there was a race condition?"); + console.error( + "In banUser, could not find user with id '" + + recipientUuid + + "'. Maybe the user left the room a few milliseconds ago and there was a race condition?" + ); return; } @@ -701,7 +727,7 @@ export class SocketManager { const banUserMessage = new BanUserMessage(); banUserMessage.setMessage(message); - banUserMessage.setType('banned'); + banUserMessage.setType("banned"); const serverToClientMessage = new ServerToClientMessage(); serverToClientMessage.setBanusermessage(banUserMessage); @@ -711,19 +737,22 @@ export class SocketManager { recipient.socket.end(); } - sendAdminRoomMessage(roomId: string, message: string) { const room = this.rooms.get(roomId); if (!room) { //todo: this should cause the http call to return a 500 - console.error("In sendAdminRoomMessage, could not find room with id '" + roomId + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?"); + console.error( + "In sendAdminRoomMessage, could not find room with id '" + + roomId + + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?" + ); return; } room.getUsers().forEach((recipient) => { const sendUserMessage = new SendUserMessage(); sendUserMessage.setMessage(message); - sendUserMessage.setType('message'); + sendUserMessage.setType("message"); const clientMessage = new ServerToClientMessage(); clientMessage.setSendusermessage(sendUserMessage); @@ -732,14 +761,18 @@ export class SocketManager { }); } - dispatchWorlFullWarning(roomId: string,): void { + dispatchWorlFullWarning(roomId: string): void { const room = this.rooms.get(roomId); if (!room) { //todo: this should cause the http call to return a 500 - console.error("In sendAdminRoomMessage, could not find room with id '" + roomId + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?"); + console.error( + "In sendAdminRoomMessage, could not find room with id '" + + roomId + + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?" + ); return; } - + room.getUsers().forEach((recipient) => { const worldFullMessage = new WorldFullWarningMessage(); @@ -750,17 +783,17 @@ export class SocketManager { }); } - dispatchRoomRefresh(roomId: string,): void { + dispatchRoomRefresh(roomId: string): void { const room = this.rooms.get(roomId); if (!room) { return; } - + const versionNumber = room.incrementVersion(); room.getUsers().forEach((recipient) => { const worldFullMessage = new RefreshRoomMessage(); - worldFullMessage.setRoomid(roomId) - worldFullMessage.setVersionnumber(versionNumber) + worldFullMessage.setRoomid(roomId); + worldFullMessage.setVersionnumber(versionNumber); const clientMessage = new ServerToClientMessage(); clientMessage.setRefreshroommessage(worldFullMessage); diff --git a/docs/maps/api-nav.md b/docs/maps/api-nav.md index 29323632..f5721063 100644 --- a/docs/maps/api-nav.md +++ b/docs/maps/api-nav.md @@ -52,11 +52,11 @@ WA.nav.goToRoom("/_/global/.json#start-layer-2") ### Opening/closing a web page in an iFrame ``` -WA.nav.openCoWebSite(url: string): void +WA.nav.openCoWebSite(url: string, allowApi: boolean = false, allowPolicy: string = ""): void WA.nav.closeCoWebSite(): void ``` -Opens the webpage at "url" in an iFrame (on the right side of the screen) or close that iFrame. +Opens the webpage at "url" in an iFrame (on the right side of the screen) or close that iFrame. `allowApi` allows the webpage to use the "IFrame API" and execute script (it is equivalent to putting the `openWebsiteAllowApi` property in the map). `allowPolicy` grants additional access rights to the iFrame. The `allowPolicy` parameter is turned into an [`allow` feature policy in the iFrame](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-allow). Example: @@ -65,4 +65,3 @@ WA.nav.openCoWebSite('https://www.wikipedia.org/'); // ... WA.nav.closeCoWebSite(); ``` - diff --git a/docs/maps/api-reference.md b/docs/maps/api-reference.md index 30a11b2a..8c8205d8 100644 --- a/docs/maps/api-reference.md +++ b/docs/maps/api-reference.md @@ -9,4 +9,4 @@ - [Sound functions](api-sound.md) - [Controls functions](api-controls.md) -- [List of deprecated functions](api-deprecated.md) \ No newline at end of file +- [List of deprecated functions](api-deprecated.md) diff --git a/docs/maps/api-room.md b/docs/maps/api-room.md index d8381cc6..9d08ce1b 100644 --- a/docs/maps/api-room.md +++ b/docs/maps/api-room.md @@ -81,7 +81,7 @@ WA.room.getCurrentRoom(): Promise ``` Return a promise that resolves to a `Room` object with the following attributes : * **id (string) :** ID of the current room -* **map (ITiledMap) :** contains the JSON map file with the properties that were setted by the script if `setProperty` was called. +* **map (ITiledMap) :** contains the JSON map file with the properties that were set by the script if `setProperty` was called. * **mapUrl (string) :** Url of the JSON map file * **startLayer (string | null) :** Name of the layer where the current user started, only if different from `start` layer @@ -112,3 +112,35 @@ WA.room.getCurrentUser().then((user) => { } }) ``` + +### Changing tiles +``` +WA.room.setTiles(tiles: TileDescriptor[]): void +``` +Replace the tile at the `x` and `y` coordinates in the layer named `layer` by the tile with the id `tile`. + +If `tile` is a string, it's not the id of the tile but the value of the property `name`. +
+
+ +
+
+ +`TileDescriptor` has the following attributes : +* **x (number) :** The coordinate x of the tile that you want to replace. +* **y (number) :** The coordinate y of the tile that you want to replace. +* **tile (number | string) :** The id of the tile that will be placed in the map. +* **layer (string) :** The name of the layer where the tile will be placed. + +**Important !** : If you use `tile` as a number, be sure to add the `firstgid` of the tileset of the tile that you want to the id of the tile in Tiled Editor. + + +Example : +```javascript +WA.room.setTiles([ + {x: 6, y: 4, tile: 'blue', layer: 'setTiles'}, + {x: 7, y: 4, tile: 109, layer: 'setTiles'}, + {x: 8, y: 4, tile: 109, layer: 'setTiles'}, + {x: 9, y: 4, tile: 'blue', layer: 'setTiles'} + ]); +``` diff --git a/front/dist/resources/objects/layout_modes.png b/front/dist/resources/objects/layout_modes.png deleted file mode 100644 index abd9adaf..00000000 Binary files a/front/dist/resources/objects/layout_modes.png and /dev/null differ diff --git a/front/dist/static/images/favicons/android-icon-144x144.png b/front/dist/static/images/favicons/android-icon-144x144.png index b0463804..59a7c4ce 100644 Binary files a/front/dist/static/images/favicons/android-icon-144x144.png and b/front/dist/static/images/favicons/android-icon-144x144.png differ diff --git a/front/dist/static/images/favicons/android-icon-192x192.png b/front/dist/static/images/favicons/android-icon-192x192.png index 6f764aae..e1a4b3ed 100644 Binary files a/front/dist/static/images/favicons/android-icon-192x192.png and b/front/dist/static/images/favicons/android-icon-192x192.png differ diff --git a/front/dist/static/images/favicons/android-icon-36x36.png b/front/dist/static/images/favicons/android-icon-36x36.png index cc93c290..f9d822e9 100644 Binary files a/front/dist/static/images/favicons/android-icon-36x36.png and b/front/dist/static/images/favicons/android-icon-36x36.png differ diff --git a/front/dist/static/images/favicons/android-icon-48x48.png b/front/dist/static/images/favicons/android-icon-48x48.png index a2a7f7e9..35b1f177 100644 Binary files a/front/dist/static/images/favicons/android-icon-48x48.png and b/front/dist/static/images/favicons/android-icon-48x48.png differ diff --git a/front/dist/static/images/favicons/android-icon-72x72.png b/front/dist/static/images/favicons/android-icon-72x72.png index 9ae8c47a..b04f1b25 100644 Binary files a/front/dist/static/images/favicons/android-icon-72x72.png and b/front/dist/static/images/favicons/android-icon-72x72.png differ diff --git a/front/dist/static/images/favicons/android-icon-96x96.png b/front/dist/static/images/favicons/android-icon-96x96.png index 43324e2c..380de08a 100644 Binary files a/front/dist/static/images/favicons/android-icon-96x96.png and b/front/dist/static/images/favicons/android-icon-96x96.png differ diff --git a/front/dist/static/images/favicons/apple-icon-114x114.png b/front/dist/static/images/favicons/apple-icon-114x114.png index f205a3ad..6c994217 100644 Binary files a/front/dist/static/images/favicons/apple-icon-114x114.png and b/front/dist/static/images/favicons/apple-icon-114x114.png differ diff --git a/front/dist/static/images/favicons/apple-icon-120x120.png b/front/dist/static/images/favicons/apple-icon-120x120.png index 09f4e85d..b20bf1e2 100644 Binary files a/front/dist/static/images/favicons/apple-icon-120x120.png and b/front/dist/static/images/favicons/apple-icon-120x120.png differ diff --git a/front/dist/static/images/favicons/apple-icon-144x144.png b/front/dist/static/images/favicons/apple-icon-144x144.png index b0463804..59a7c4ce 100644 Binary files a/front/dist/static/images/favicons/apple-icon-144x144.png and b/front/dist/static/images/favicons/apple-icon-144x144.png differ diff --git a/front/dist/static/images/favicons/apple-icon-152x152.png b/front/dist/static/images/favicons/apple-icon-152x152.png index fa06794f..fc905367 100644 Binary files a/front/dist/static/images/favicons/apple-icon-152x152.png and b/front/dist/static/images/favicons/apple-icon-152x152.png differ diff --git a/front/dist/static/images/favicons/apple-icon-180x180.png b/front/dist/static/images/favicons/apple-icon-180x180.png index 4b9af8b6..8f18bd51 100644 Binary files a/front/dist/static/images/favicons/apple-icon-180x180.png and b/front/dist/static/images/favicons/apple-icon-180x180.png differ diff --git a/front/dist/static/images/favicons/apple-icon-57x57.png b/front/dist/static/images/favicons/apple-icon-57x57.png index bd72841f..62c8ab07 100644 Binary files a/front/dist/static/images/favicons/apple-icon-57x57.png and b/front/dist/static/images/favicons/apple-icon-57x57.png differ diff --git a/front/dist/static/images/favicons/apple-icon-60x60.png b/front/dist/static/images/favicons/apple-icon-60x60.png index 89238b40..68c5b74b 100644 Binary files a/front/dist/static/images/favicons/apple-icon-60x60.png and b/front/dist/static/images/favicons/apple-icon-60x60.png differ diff --git a/front/dist/static/images/favicons/apple-icon-72x72.png b/front/dist/static/images/favicons/apple-icon-72x72.png index 9ae8c47a..b04f1b25 100644 Binary files a/front/dist/static/images/favicons/apple-icon-72x72.png and b/front/dist/static/images/favicons/apple-icon-72x72.png differ diff --git a/front/dist/static/images/favicons/apple-icon-76x76.png b/front/dist/static/images/favicons/apple-icon-76x76.png index fbd0c29c..a58cf3ed 100644 Binary files a/front/dist/static/images/favicons/apple-icon-76x76.png and b/front/dist/static/images/favicons/apple-icon-76x76.png differ diff --git a/front/dist/static/images/favicons/apple-icon-precomposed.png b/front/dist/static/images/favicons/apple-icon-precomposed.png index 3132ca5a..e1a4b3ed 100644 Binary files a/front/dist/static/images/favicons/apple-icon-precomposed.png and b/front/dist/static/images/favicons/apple-icon-precomposed.png differ diff --git a/front/dist/static/images/favicons/apple-icon.png b/front/dist/static/images/favicons/apple-icon.png index 3132ca5a..e1a4b3ed 100644 Binary files a/front/dist/static/images/favicons/apple-icon.png and b/front/dist/static/images/favicons/apple-icon.png differ diff --git a/front/dist/static/images/favicons/favicon-16x16.png b/front/dist/static/images/favicons/favicon-16x16.png index a49eb71a..cd608133 100644 Binary files a/front/dist/static/images/favicons/favicon-16x16.png and b/front/dist/static/images/favicons/favicon-16x16.png differ diff --git a/front/dist/static/images/favicons/favicon-32x32.png b/front/dist/static/images/favicons/favicon-32x32.png index 957f5006..a15ffef5 100644 Binary files a/front/dist/static/images/favicons/favicon-32x32.png and b/front/dist/static/images/favicons/favicon-32x32.png differ diff --git a/front/dist/static/images/favicons/favicon-96x96.png b/front/dist/static/images/favicons/favicon-96x96.png index 43324e2c..380de08a 100644 Binary files a/front/dist/static/images/favicons/favicon-96x96.png and b/front/dist/static/images/favicons/favicon-96x96.png differ diff --git a/front/dist/static/images/favicons/favicon.ico b/front/dist/static/images/favicons/favicon.ico index ec628ea2..203ce590 100644 Binary files a/front/dist/static/images/favicons/favicon.ico and b/front/dist/static/images/favicons/favicon.ico differ diff --git a/front/dist/static/images/favicons/ms-icon-144x144.png b/front/dist/static/images/favicons/ms-icon-144x144.png index b0463804..59a7c4ce 100644 Binary files a/front/dist/static/images/favicons/ms-icon-144x144.png and b/front/dist/static/images/favicons/ms-icon-144x144.png differ diff --git a/front/dist/static/images/favicons/ms-icon-150x150.png b/front/dist/static/images/favicons/ms-icon-150x150.png index 4ab38ea3..3515b43a 100644 Binary files a/front/dist/static/images/favicons/ms-icon-150x150.png and b/front/dist/static/images/favicons/ms-icon-150x150.png differ diff --git a/front/dist/static/images/favicons/ms-icon-310x310.png b/front/dist/static/images/favicons/ms-icon-310x310.png index 56ceeb95..115ce84d 100644 Binary files a/front/dist/static/images/favicons/ms-icon-310x310.png and b/front/dist/static/images/favicons/ms-icon-310x310.png differ diff --git a/front/dist/static/images/favicons/ms-icon-70x70.png b/front/dist/static/images/favicons/ms-icon-70x70.png index 3fa12cae..342deff9 100644 Binary files a/front/dist/static/images/favicons/ms-icon-70x70.png and b/front/dist/static/images/favicons/ms-icon-70x70.png differ diff --git a/front/package.json b/front/package.json index 61205855..9c592578 100644 --- a/front/package.json +++ b/front/package.json @@ -60,12 +60,13 @@ "templater": "cross-env ./templater.sh", "serve": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" webpack serve --open", "build": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" NODE_ENV=production webpack", - "test": "TS_NODE_PROJECT=\"tsconfig-for-jasmine.json\" ts-node node_modules/jasmine/bin/jasmine --config=jasmine.json", + "build-typings": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" NODE_ENV=production BUILD_TYPINGS=1 webpack", + "test": "cross-env TS_NODE_PROJECT=\"tsconfig-for-jasmine.json\" ts-node node_modules/jasmine/bin/jasmine --config=jasmine.json", "lint": "node_modules/.bin/eslint src/ . --ext .ts", "fix": "node_modules/.bin/eslint --fix src/ . --ext .ts", "precommit": "lint-staged", - "svelte-check-watch": "svelte-check --fail-on-warnings --fail-on-hints --compiler-warnings \"a11y-no-onchange:ignore,a11y-autofocus:ignore\" --watch", - "svelte-check": "svelte-check --fail-on-warnings --fail-on-hints --compiler-warnings \"a11y-no-onchange:ignore,a11y-autofocus:ignore\"", + "svelte-check-watch": "svelte-check --fail-on-warnings --fail-on-hints --compiler-warnings \"a11y-no-onchange:ignore,a11y-autofocus:ignore,a11y-media-has-caption:ignore\" --watch", + "svelte-check": "svelte-check --fail-on-warnings --fail-on-hints --compiler-warnings \"a11y-no-onchange:ignore,a11y-autofocus:ignore,a11y-media-has-caption:ignore\"", "pretty": "yarn prettier --write 'src/**/*.{ts,tsx}'", "pretty-check": "yarn prettier --check 'src/**/*.{ts,tsx}'" }, diff --git a/front/src/Api/Events/DataLayerEvent.ts b/front/src/Api/Events/DataLayerEvent.ts index 096d6ef5..3062c1bc 100644 --- a/front/src/Api/Events/DataLayerEvent.ts +++ b/front/src/Api/Events/DataLayerEvent.ts @@ -1,13 +1,12 @@ import * as tg from "generic-type-guard"; - - -export const isDataLayerEvent = - new tg.IsInterface().withProperties({ - data: tg.isObject - }).get(); +export const isDataLayerEvent = new tg.IsInterface() + .withProperties({ + data: tg.isObject, + }) + .get(); /** * A message sent from the game to the iFrame when the data of the layers change after the iFrame send a message to the game that it want to listen to the data of the layers */ -export type DataLayerEvent = tg.GuardedType; \ No newline at end of file +export type DataLayerEvent = tg.GuardedType; diff --git a/front/src/Api/Events/GameStateEvent.ts b/front/src/Api/Events/GameStateEvent.ts index 85fb37e9..edeeef80 100644 --- a/front/src/Api/Events/GameStateEvent.ts +++ b/front/src/Api/Events/GameStateEvent.ts @@ -1,14 +1,15 @@ import * as tg from "generic-type-guard"; -export const isGameStateEvent = - new tg.IsInterface().withProperties({ - roomId: tg.isString, - mapUrl: tg.isString, - nickname: tg.isUnion(tg.isString, tg.isNull), - uuid: tg.isUnion(tg.isString, tg.isUndefined), - startLayerName: tg.isUnion(tg.isString, tg.isNull), - tags : tg.isArray(tg.isString), - }).get(); +export const isGameStateEvent = new tg.IsInterface() + .withProperties({ + roomId: tg.isString, + mapUrl: tg.isString, + nickname: tg.isUnion(tg.isString, tg.isNull), + uuid: tg.isUnion(tg.isString, tg.isUndefined), + startLayerName: tg.isUnion(tg.isString, tg.isNull), + tags: tg.isArray(tg.isString), + }) + .get(); /** * A message sent from the game to the iFrame when the gameState is received by the script */ diff --git a/front/src/Api/Events/HasPlayerMovedEvent.ts b/front/src/Api/Events/HasPlayerMovedEvent.ts index 50f017df..87b45482 100644 --- a/front/src/Api/Events/HasPlayerMovedEvent.ts +++ b/front/src/Api/Events/HasPlayerMovedEvent.ts @@ -1,19 +1,17 @@ import * as tg from "generic-type-guard"; - - -export const isHasPlayerMovedEvent = - new tg.IsInterface().withProperties({ - direction: tg.isElementOf('right', 'left', 'up', 'down'), +export const isHasPlayerMovedEvent = new tg.IsInterface() + .withProperties({ + direction: tg.isElementOf("right", "left", "up", "down"), moving: tg.isBoolean, x: tg.isNumber, - y: tg.isNumber - }).get(); + y: tg.isNumber, + }) + .get(); /** * A message sent from the game to the iFrame to notify a movement from the current player. */ export type HasPlayerMovedEvent = tg.GuardedType; - -export type HasPlayerMovedEventCallback = (event: HasPlayerMovedEvent) => void +export type HasPlayerMovedEventCallback = (event: HasPlayerMovedEvent) => void; diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index d2df4ded..ed2db1db 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -1,78 +1,83 @@ - -import type { GameStateEvent } from './GameStateEvent'; -import type { ButtonClickedEvent } from './ButtonClickedEvent'; -import type { ChatEvent } from './ChatEvent'; -import type { ClosePopupEvent } from './ClosePopupEvent'; -import type { EnterLeaveEvent } from './EnterLeaveEvent'; -import type { GoToPageEvent } from './GoToPageEvent'; -import type { LoadPageEvent } from './LoadPageEvent'; -import type { OpenCoWebSiteEvent } from './OpenCoWebSiteEvent'; -import type { OpenPopupEvent } from './OpenPopupEvent'; -import type { OpenTabEvent } from './OpenTabEvent'; -import type { UserInputChatEvent } from './UserInputChatEvent'; +import type { GameStateEvent } from "./GameStateEvent"; +import type { ButtonClickedEvent } from "./ButtonClickedEvent"; +import type { ChatEvent } from "./ChatEvent"; +import type { ClosePopupEvent } from "./ClosePopupEvent"; +import type { EnterLeaveEvent } from "./EnterLeaveEvent"; +import type { GoToPageEvent } from "./GoToPageEvent"; +import type { LoadPageEvent } from "./LoadPageEvent"; +import type { OpenCoWebSiteEvent } from "./OpenCoWebSiteEvent"; +import type { OpenPopupEvent } from "./OpenPopupEvent"; +import type { OpenTabEvent } from "./OpenTabEvent"; +import type { UserInputChatEvent } from "./UserInputChatEvent"; import type { DataLayerEvent } from "./DataLayerEvent"; -import type { LayerEvent } from './LayerEvent'; +import type { LayerEvent } from "./LayerEvent"; import type { SetPropertyEvent } from "./setPropertyEvent"; import type { LoadSoundEvent } from "./LoadSoundEvent"; import type { PlaySoundEvent } from "./PlaySoundEvent"; import type { MenuItemClickedEvent } from "./ui/MenuItemClickedEvent"; -import type { MenuItemRegisterEvent } from './ui/MenuItemRegisterEvent'; +import type { MenuItemRegisterEvent } from "./ui/MenuItemRegisterEvent"; import type { HasPlayerMovedEvent } from "./HasPlayerMovedEvent"; -import type { MessageReferenceEvent, TriggerMessageEvent } from '../iframe/TriggerMessageEvent'; - +import type { SetTilesEvent } from "./SetTilesEvent"; +import type { + MessageReferenceEvent, + removeTriggerMessage, + triggerMessage, + TriggerMessageEvent, +} from "./ui/TriggerMessageEvent"; export interface TypedMessageEvent extends MessageEvent { - data: T + data: T; } +/** + * List event types sent from an iFrame to WorkAdventure + */ export type IframeEventMap = { - //getState: GameStateEvent, - // updateTile: UpdateTileEvent - loadPage: LoadPageEvent - chat: ChatEvent, - openPopup: OpenPopupEvent - closePopup: ClosePopupEvent - openTab: OpenTabEvent - goToPage: GoToPageEvent - openCoWebSite: OpenCoWebSiteEvent - closeCoWebSite: null - disablePlayerControls: null - restorePlayerControls: null - displayBubble: null - removeBubble: null - onPlayerMove: undefined - showLayer: LayerEvent - hideLayer: LayerEvent - setProperty: SetPropertyEvent - getDataLayer: undefined - loadSound: LoadSoundEvent - playSound: PlaySoundEvent - stopSound: null, - getState: undefined, - registerMenuCommand: MenuItemRegisterEvent + loadPage: LoadPageEvent; + chat: ChatEvent; + openPopup: OpenPopupEvent; + closePopup: ClosePopupEvent; + openTab: OpenTabEvent; + goToPage: GoToPageEvent; + openCoWebSite: OpenCoWebSiteEvent; + closeCoWebSite: null; + disablePlayerControls: null; + restorePlayerControls: null; + displayBubble: null; + removeBubble: null; + onPlayerMove: undefined; + showLayer: LayerEvent; + hideLayer: LayerEvent; + setProperty: SetPropertyEvent; + getDataLayer: undefined; + loadSound: LoadSoundEvent; + playSound: PlaySoundEvent; + stopSound: null; + getState: undefined; + registerMenuCommand: MenuItemRegisterEvent; + setTiles: SetTilesEvent; - triggerMessage: TriggerMessageEvent - removeTriggerMessage: MessageReferenceEvent -} + triggerMessage: TriggerMessageEvent; + removeTriggerMessage: MessageReferenceEvent; +}; export interface IframeEvent { type: T; data: IframeEventMap[T]; } - // eslint-disable-next-line @typescript-eslint/no-explicit-any -export const isIframeEventWrapper = (event: any): event is IframeEvent => typeof event.type === 'string'; +export const isIframeEventWrapper = (event: any): event is IframeEvent => + typeof event.type === "string"; export interface IframeResponseEventMap { - userInputChat: UserInputChatEvent - enterEvent: EnterLeaveEvent - leaveEvent: EnterLeaveEvent - buttonClickedEvent: ButtonClickedEvent - gameState: GameStateEvent - hasPlayerMoved: HasPlayerMovedEvent - dataLayer: DataLayerEvent - menuItemClicked: MenuItemClickedEvent - messageTriggered: MessageReferenceEvent + userInputChat: UserInputChatEvent; + enterEvent: EnterLeaveEvent; + leaveEvent: EnterLeaveEvent; + buttonClickedEvent: ButtonClickedEvent; + hasPlayerMoved: HasPlayerMovedEvent; + dataLayer: DataLayerEvent; + menuItemClicked: MenuItemClickedEvent; + messageTriggered: MessageReferenceEvent; } export interface IframeResponseEvent { type: T; @@ -80,4 +85,67 @@ export interface IframeResponseEvent { } // eslint-disable-next-line @typescript-eslint/no-explicit-any -export const isIframeResponseEventWrapper = (event: { type?: string }): event is IframeResponseEvent => typeof event.type === 'string'; +export const isIframeResponseEventWrapper = (event: { + type?: string; +}): event is IframeResponseEvent => typeof event.type === "string"; + +/** + * List event types sent from an iFrame to WorkAdventure that expect a unique answer from WorkAdventure along the type for the answer from WorkAdventure to the iFrame + */ +export type IframeQueryMap = { + getState: { + query: undefined; + answer: GameStateEvent; + }; + + [triggerMessage]: { + query: TriggerMessageEvent; + answer: void; + }; + + [removeTriggerMessage]: { + query: MessageReferenceEvent; + answer: void; + }; +}; + +export interface IframeQuery { + type: T; + data: IframeQueryMap[T]["query"]; +} + +export interface IframeQueryWrapper { + id: number; + query: IframeQuery; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const isIframeQuery = (event: any): event is IframeQuery => typeof event.type === "string"; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const isIframeQueryWrapper = (event: any): event is IframeQueryWrapper => + typeof event.id === "number" && isIframeQuery(event.query); + +export interface IframeAnswerEvent { + id: number; + type: T; + data: IframeQueryMap[T]["answer"]; +} + +export const isIframeAnswerEvent = (event: { + type?: string; + id?: number; +}): event is IframeAnswerEvent => typeof event.type === "string" && typeof event.id === "number"; + +export interface IframeErrorAnswerEvent { + id: number; + type: keyof IframeQueryMap; + error: string; +} + +export const isIframeErrorAnswerEvent = (event: { + type?: string; + id?: number; + error?: string; +}): event is IframeErrorAnswerEvent => + typeof event.type === "string" && typeof event.id === "number" && typeof event.error === "string"; diff --git a/front/src/Api/Events/LayerEvent.ts b/front/src/Api/Events/LayerEvent.ts index f854248b..b56c3163 100644 --- a/front/src/Api/Events/LayerEvent.ts +++ b/front/src/Api/Events/LayerEvent.ts @@ -1,9 +1,10 @@ import * as tg from "generic-type-guard"; -export const isLayerEvent = - new tg.IsInterface().withProperties({ +export const isLayerEvent = new tg.IsInterface() + .withProperties({ name: tg.isString, - }).get(); + }) + .get(); /** * A message sent from the iFrame to the game to show/hide a layer. */ diff --git a/front/src/Api/Events/LoadPageEvent.ts b/front/src/Api/Events/LoadPageEvent.ts index 9bc7f32a..63600a28 100644 --- a/front/src/Api/Events/LoadPageEvent.ts +++ b/front/src/Api/Events/LoadPageEvent.ts @@ -1,13 +1,12 @@ import * as tg from "generic-type-guard"; - - -export const isLoadPageEvent = - new tg.IsInterface().withProperties({ +export const isLoadPageEvent = new tg.IsInterface() + .withProperties({ url: tg.isString, - }).get(); + }) + .get(); /** * A message sent from the iFrame to the game to add a message in the chat. */ -export type LoadPageEvent = tg.GuardedType; \ No newline at end of file +export type LoadPageEvent = tg.GuardedType; diff --git a/front/src/Api/Events/OpenCoWebSiteEvent.ts b/front/src/Api/Events/OpenCoWebSiteEvent.ts index 0fbc0ce2..7b5e6070 100644 --- a/front/src/Api/Events/OpenCoWebSiteEvent.ts +++ b/front/src/Api/Events/OpenCoWebSiteEvent.ts @@ -1,11 +1,12 @@ import * as tg from "generic-type-guard"; - - -export const isOpenCoWebsite = - new tg.IsInterface().withProperties({ +export const isOpenCoWebsite = new tg.IsInterface() + .withProperties({ url: tg.isString, - }).get(); + allowApi: tg.isBoolean, + allowPolicy: tg.isString, + }) + .get(); /** * A message sent from the iFrame to the game to add a message in the chat. diff --git a/front/src/Api/Events/SetTilesEvent.ts b/front/src/Api/Events/SetTilesEvent.ts new file mode 100644 index 00000000..c7f8f16d --- /dev/null +++ b/front/src/Api/Events/SetTilesEvent.ts @@ -0,0 +1,16 @@ +import * as tg from "generic-type-guard"; + +export const isSetTilesEvent = tg.isArray( + new tg.IsInterface() + .withProperties({ + x: tg.isNumber, + y: tg.isNumber, + tile: tg.isUnion(tg.isNumber, tg.isString), + layer: tg.isString, + }) + .get() +); +/** + * A message sent from the iFrame to the game to set one or many tiles. + */ +export type SetTilesEvent = tg.GuardedType; diff --git a/front/src/Api/Events/setPropertyEvent.ts b/front/src/Api/Events/setPropertyEvent.ts index 39785bc6..7335f781 100644 --- a/front/src/Api/Events/setPropertyEvent.ts +++ b/front/src/Api/Events/setPropertyEvent.ts @@ -1,12 +1,13 @@ import * as tg from "generic-type-guard"; -export const isSetPropertyEvent = - new tg.IsInterface().withProperties({ +export const isSetPropertyEvent = new tg.IsInterface() + .withProperties({ layerName: tg.isString, propertyName: tg.isString, - propertyValue: tg.isUnion(tg.isString, tg.isUnion(tg.isNumber, tg.isUnion(tg.isBoolean, tg.isUndefined))) - }).get(); + propertyValue: tg.isUnion(tg.isString, tg.isUnion(tg.isNumber, tg.isUnion(tg.isBoolean, tg.isUndefined))), + }) + .get(); /** * A message sent from the iFrame to the game to change the value of the property of the layer */ -export type SetPropertyEvent = tg.GuardedType; \ No newline at end of file +export type SetPropertyEvent = tg.GuardedType; diff --git a/front/src/Api/Events/ui/MenuItemClickedEvent.ts b/front/src/Api/Events/ui/MenuItemClickedEvent.ts index fad2944f..a8c8d0ed 100644 --- a/front/src/Api/Events/ui/MenuItemClickedEvent.ts +++ b/front/src/Api/Events/ui/MenuItemClickedEvent.ts @@ -1,12 +1,11 @@ import * as tg from "generic-type-guard"; -export const isMenuItemClickedEvent = - new tg.IsInterface().withProperties({ - menuItem: tg.isString - }).get(); +export const isMenuItemClickedEvent = new tg.IsInterface() + .withProperties({ + menuItem: tg.isString, + }) + .get(); /** * A message sent from the game to the iFrame when a menu item is clicked. */ export type MenuItemClickedEvent = tg.GuardedType; - - diff --git a/front/src/Api/Events/ui/MenuItemRegisterEvent.ts b/front/src/Api/Events/ui/MenuItemRegisterEvent.ts index 4a56d8a0..404bdb13 100644 --- a/front/src/Api/Events/ui/MenuItemRegisterEvent.ts +++ b/front/src/Api/Events/ui/MenuItemRegisterEvent.ts @@ -1,25 +1,26 @@ import * as tg from "generic-type-guard"; -import { Subject } from 'rxjs'; +import { Subject } from "rxjs"; -export const isMenuItemRegisterEvent = - new tg.IsInterface().withProperties({ - menutItem: tg.isString - }).get(); +export const isMenuItemRegisterEvent = new tg.IsInterface() + .withProperties({ + menutItem: tg.isString, + }) + .get(); /** * A message sent from the iFrame to the game to add a new menu item. */ export type MenuItemRegisterEvent = tg.GuardedType; -export const isMenuItemRegisterIframeEvent = - new tg.IsInterface().withProperties({ +export const isMenuItemRegisterIframeEvent = new tg.IsInterface() + .withProperties({ type: tg.isSingletonString("registerMenuCommand"), - data: isMenuItemRegisterEvent - }).get(); - + data: isMenuItemRegisterEvent, + }) + .get(); const _registerMenuCommandStream: Subject = new Subject(); export const registerMenuCommandStream = _registerMenuCommandStream.asObservable(); export function handleMenuItemRegistrationEvent(event: MenuItemRegisterEvent) { - _registerMenuCommandStream.next(event.menutItem) -} \ No newline at end of file + _registerMenuCommandStream.next(event.menutItem); +} diff --git a/front/src/Api/Events/ui/TriggerMessageEventHandler.ts b/front/src/Api/Events/ui/TriggerMessageEventHandler.ts index d690dbc0..d7326e00 100644 --- a/front/src/Api/Events/ui/TriggerMessageEventHandler.ts +++ b/front/src/Api/Events/ui/TriggerMessageEventHandler.ts @@ -1,42 +1,35 @@ -import { Subject } from 'rxjs'; -import { iframeListener } from '../../IframeListener'; -import { isMessageReferenceEvent, isTriggerMessageEvent, MessageReferenceEvent, removeTriggerMessage, triggerMessage, TriggerMessageEvent } from './TriggerMessageEvent'; +import { Subject } from "rxjs"; +import { iframeListener } from "../../IframeListener"; +import { + isMessageReferenceEvent, + isTriggerMessageEvent, + MessageReferenceEvent, + removeTriggerMessage, + triggerMessage, + TriggerMessageEvent, +} from "./TriggerMessageEvent"; import * as tg from "generic-type-guard"; export function sendMessageTriggeredEvent(uuid: string) { iframeListener.postMessage({ - 'type': 'messageTriggered', - 'data': { + type: "messageTriggered", + data: { uuid, - } as MessageReferenceEvent + } as MessageReferenceEvent, }); } -const _triggerMessageEvent: Subject = new Subject(); -const _removeTriggerMessageEvent: Subject = new Subject(); +const isTriggerMessageEventObject = new tg.IsInterface() + .withProperties({ + type: tg.isSingletonString(triggerMessage), + data: isTriggerMessageEvent, + }) + .get(); -export const triggerMessageEvent = _triggerMessageEvent.asObservable(); +const isTriggerMessageRemoveEventObject = new tg.IsInterface() + .withProperties({ + type: tg.isSingletonString(removeTriggerMessage), + data: isMessageReferenceEvent, + }) + .get(); -export const removeTriggerMessageEvent = _removeTriggerMessageEvent.asObservable(); - -const isTriggerMessageEventObject = new tg.IsInterface().withProperties({ - type: tg.isSingletonString(triggerMessage), - data: isTriggerMessageEvent -}).get() -const isTriggerMessageRemoveEventObject = new tg.IsInterface().withProperties({ - type: tg.isSingletonString(removeTriggerMessage), - data: isMessageReferenceEvent -}).get() - - -export const isTriggerMessageHandlerEvent = tg.isUnion(isTriggerMessageEventObject, isTriggerMessageRemoveEventObject) - - - - -export function triggerMessageEventHandler(event: tg.GuardedType) { - if (isTriggerMessageEventObject(event)) { - _triggerMessageEvent.next(event.data) - } else if (isTriggerMessageRemoveEventObject(event)) { - _removeTriggerMessageEvent.next(event.data) - } -} \ No newline at end of file +export const isTriggerMessageHandlerEvent = tg.isUnion(isTriggerMessageEventObject, isTriggerMessageRemoveEventObject); diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index 3320519a..224f86db 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -1,4 +1,5 @@ import { Subject } from "rxjs"; +import type * as tg from "generic-type-guard"; import { ChatEvent, isChatEvent } from "./Events/ChatEvent"; import { HtmlUtils } from "../WebRtc/HtmlUtils"; import type { EnterLeaveEvent } from "./Events/EnterLeaveEvent"; @@ -10,34 +11,40 @@ import { scriptUtils } from "./ScriptUtils"; import { GoToPageEvent, isGoToPageEvent } from "./Events/GoToPageEvent"; import { isOpenCoWebsite, OpenCoWebSiteEvent } from "./Events/OpenCoWebSiteEvent"; import { + IframeErrorAnswerEvent, IframeEvent, IframeEventMap, + IframeQuery, + IframeQueryMap, IframeResponseEvent, IframeResponseEventMap, isIframeEventWrapper, - TypedMessageEvent + isIframeQueryWrapper, + TypedMessageEvent, } from "./Events/IframeEvent"; import type { UserInputChatEvent } from "./Events/UserInputChatEvent"; -//import { isLoadPageEvent } from './Events/LoadPageEvent'; import { isPlaySoundEvent, PlaySoundEvent } from "./Events/PlaySoundEvent"; import { isStopSoundEvent, StopSoundEvent } from "./Events/StopSoundEvent"; import { isLoadSoundEvent, LoadSoundEvent } from "./Events/LoadSoundEvent"; import { isSetPropertyEvent, SetPropertyEvent } from "./Events/setPropertyEvent"; import { isLayerEvent, LayerEvent } from "./Events/LayerEvent"; -import { isMenuItemRegisterEvent, } from "./Events/ui/MenuItemRegisterEvent"; +import { isMenuItemRegisterEvent } from "./Events/ui/MenuItemRegisterEvent"; import type { DataLayerEvent } from "./Events/DataLayerEvent"; import type { GameStateEvent } from "./Events/GameStateEvent"; import type { HasPlayerMovedEvent } from "./Events/HasPlayerMovedEvent"; import { isLoadPageEvent } from "./Events/LoadPageEvent"; import { handleMenuItemRegistrationEvent, isMenuItemRegisterIframeEvent } from "./Events/ui/MenuItemRegisterEvent"; -import { isTriggerMessageHandlerEvent, triggerMessageEventHandler } from './Events/ui/TriggerMessageEventHandler'; +import { SetTilesEvent, isSetTilesEvent } from "./Events/SetTilesEvent"; + +type AnswererCallback = ( + query: IframeQueryMap[T]["query"] +) => IframeQueryMap[T]["answer"] | Promise; /** * Listens to messages from iframes and turn those messages into easy to use observables. * Also allows to send messages to those iframes. */ class IframeListener { - private readonly _chatStream: Subject = new Subject(); public readonly chatStream = this._chatStream.asObservable(); @@ -83,9 +90,6 @@ class IframeListener { private readonly _setPropertyStream: Subject = new Subject(); public readonly setPropertyStream = this._setPropertyStream.asObservable(); - private readonly _gameStateStream: Subject = new Subject(); - public readonly gameStateStream = this._gameStateStream.asObservable(); - private readonly _dataLayerChangeStream: Subject = new Subject(); public readonly dataLayerChangeStream = this._dataLayerChangeStream.asObservable(); @@ -104,113 +108,170 @@ class IframeListener { private readonly _loadSoundStream: Subject = new Subject(); public readonly loadSoundStream = this._loadSoundStream.asObservable(); + private readonly _setTilesStream: Subject = new Subject(); + public readonly setTilesStream = this._setTilesStream.asObservable(); + private readonly iframes = new Set(); private readonly iframeCloseCallbacks = new Map void)[]>(); private readonly scripts = new Map(); private sendPlayerMove: boolean = false; + private answerers: { + [key in keyof IframeQueryMap]?: AnswererCallback; + } = {}; + init() { - window.addEventListener("message", (message: TypedMessageEvent>) => { - // Do we trust the sender of this message? - // Let's only accept messages from the iframe that are allowed. - // Note: maybe we could restrict on the domain too for additional security (in case the iframe goes to another domain). - let foundSrc: string | undefined; + window.addEventListener( + "message", + ( + message: TypedMessageEvent> + ) => { + // Do we trust the sender of this message? + // Let's only accept messages from the iframe that are allowed. + // Note: maybe we could restrict on the domain too for additional security (in case the iframe goes to another domain). + let foundSrc: string | undefined; - let iframe: HTMLIFrameElement; - for (iframe of this.iframes) { - if (iframe.contentWindow === message.source) { - foundSrc = iframe.src; - break; - } - } - - if (foundSrc === undefined) { - return; - } - - const payload = message.data; - if (isIframeEventWrapper(payload)) { - if (payload.type === 'showLayer' && isLayerEvent(payload.data)) { - this._showLayerStream.next(payload.data); - } else if (payload.type === 'hideLayer' && isLayerEvent(payload.data)) { - this._hideLayerStream.next(payload.data); - } else if (payload.type === 'setProperty' && isSetPropertyEvent(payload.data)) { - this._setPropertyStream.next(payload.data); - } else if (payload.type === 'chat' && isChatEvent(payload.data)) { - this._chatStream.next(payload.data); - } else if (payload.type === 'openPopup' && isOpenPopupEvent(payload.data)) { - this._openPopupStream.next(payload.data); - } else if (payload.type === 'closePopup' && isClosePopupEvent(payload.data)) { - this._closePopupStream.next(payload.data); - } - else if (payload.type === 'openTab' && isOpenTabEvent(payload.data)) { - scriptUtils.openTab(payload.data.url); - } - else if (payload.type === 'goToPage' && isGoToPageEvent(payload.data)) { - scriptUtils.goToPage(payload.data.url); - } - else if (payload.type === 'loadPage' && isLoadPageEvent(payload.data)) { - this._loadPageStream.next(payload.data.url); - } - else if (payload.type === 'playSound' && isPlaySoundEvent(payload.data)) { - this._playSoundStream.next(payload.data); - } - else if (payload.type === 'stopSound' && isStopSoundEvent(payload.data)) { - this._stopSoundStream.next(payload.data); - } - else if (payload.type === 'loadSound' && isLoadSoundEvent(payload.data)) { - this._loadSoundStream.next(payload.data); - } - else if (payload.type === 'openCoWebSite' && isOpenCoWebsite(payload.data)) { - scriptUtils.openCoWebsite(payload.data.url, foundSrc); + let iframe: HTMLIFrameElement | undefined; + for (iframe of this.iframes) { + if (iframe.contentWindow === message.source) { + foundSrc = iframe.src; + break; + } } - else if (payload.type === 'closeCoWebSite') { - scriptUtils.closeCoWebSite(); + const payload = message.data; + + if (foundSrc === undefined || iframe === undefined) { + if (isIframeEventWrapper(payload)) { + console.warn( + "It seems an iFrame is trying to communicate with WorkAdventure but was not explicitly granted the permission to do so. " + + "If you are looking to use the WorkAdventure Scripting API inside an iFrame, you should allow the " + + 'iFrame to communicate with WorkAdventure by using the "openWebsiteAllowApi" property in your map (or passing "true" as a second' + + "parameter to WA.nav.openCoWebSite())" + ); + } + return; } - else if (payload.type === 'disablePlayerControls') { - this._disablePlayerControlStream.next(); - } - else if (payload.type === 'restorePlayerControls') { - this._enablePlayerControlStream.next(); - } else if (payload.type === 'displayBubble') { - this._displayBubbleStream.next(); - } else if (payload.type === 'removeBubble') { - this._removeBubbleStream.next(); - } else if (payload.type == "getState") { - this._gameStateStream.next(); - } else if (payload.type == "onPlayerMove") { - this.sendPlayerMove = true - } else if (payload.type == "getDataLayer") { - this._dataLayerChangeStream.next(); - } else if (isMenuItemRegisterIframeEvent(payload)) { - const data = payload.data.menutItem; - // @ts-ignore - this.iframeCloseCallbacks.get(iframe).push(() => { - this._unregisterMenuCommandStream.next(data); - }) - handleMenuItemRegistrationEvent(payload.data) - } else if (isTriggerMessageHandlerEvent(payload)) { - triggerMessageEventHandler(payload) - } - } - }, false); + foundSrc = this.getBaseUrl(foundSrc, message.source); + if (isIframeQueryWrapper(payload)) { + const queryId = payload.id; + const query = payload.query as IframeQuery; + + const answerer = this.answerers[query.type] as AnswererCallback | undefined; + if (answerer === undefined) { + const errorMsg = + 'The iFrame sent a message of type "' + + query.type + + '" but there is no service configured to answer these messages.'; + console.error(errorMsg); + iframe.contentWindow?.postMessage( + { + id: queryId, + type: query.type, + error: errorMsg, + } as IframeErrorAnswerEvent, + "*" + ); + return; + } + + Promise.resolve(answerer(query.data)) + .then((value) => { + iframe?.contentWindow?.postMessage( + { + id: queryId, + type: query.type, + data: value, + }, + "*" + ); + }) + .catch((reason) => { + console.error("An error occurred while responding to an iFrame query.", reason); + let reasonMsg: string; + if (reason instanceof Error) { + reasonMsg = reason.message; + } else { + reasonMsg = reason.toString(); + } + + iframe?.contentWindow?.postMessage( + { + id: queryId, + type: query.type, + error: reasonMsg, + } as IframeErrorAnswerEvent, + "*" + ); + }); + } else if (isIframeEventWrapper(payload)) { + if (payload.type === "showLayer" && isLayerEvent(payload.data)) { + this._showLayerStream.next(payload.data); + } else if (payload.type === "hideLayer" && isLayerEvent(payload.data)) { + this._hideLayerStream.next(payload.data); + } else if (payload.type === "setProperty" && isSetPropertyEvent(payload.data)) { + this._setPropertyStream.next(payload.data); + } else if (payload.type === "chat" && isChatEvent(payload.data)) { + this._chatStream.next(payload.data); + } else if (payload.type === "openPopup" && isOpenPopupEvent(payload.data)) { + this._openPopupStream.next(payload.data); + } else if (payload.type === "closePopup" && isClosePopupEvent(payload.data)) { + this._closePopupStream.next(payload.data); + } else if (payload.type === "openTab" && isOpenTabEvent(payload.data)) { + scriptUtils.openTab(payload.data.url); + } else if (payload.type === "goToPage" && isGoToPageEvent(payload.data)) { + scriptUtils.goToPage(payload.data.url); + } else if (payload.type === "loadPage" && isLoadPageEvent(payload.data)) { + this._loadPageStream.next(payload.data.url); + } else if (payload.type === "playSound" && isPlaySoundEvent(payload.data)) { + this._playSoundStream.next(payload.data); + } else if (payload.type === "stopSound" && isStopSoundEvent(payload.data)) { + this._stopSoundStream.next(payload.data); + } else if (payload.type === "loadSound" && isLoadSoundEvent(payload.data)) { + this._loadSoundStream.next(payload.data); + } else if (payload.type === "openCoWebSite" && isOpenCoWebsite(payload.data)) { + scriptUtils.openCoWebsite( + payload.data.url, + foundSrc, + payload.data.allowApi, + payload.data.allowPolicy + ); + } else if (payload.type === "closeCoWebSite") { + scriptUtils.closeCoWebSite(); + } else if (payload.type === "disablePlayerControls") { + this._disablePlayerControlStream.next(); + } else if (payload.type === "restorePlayerControls") { + this._enablePlayerControlStream.next(); + } else if (payload.type === "displayBubble") { + this._displayBubbleStream.next(); + } else if (payload.type === "removeBubble") { + this._removeBubbleStream.next(); + } else if (payload.type == "onPlayerMove") { + this.sendPlayerMove = true; + } else if (payload.type == "getDataLayer") { + this._dataLayerChangeStream.next(); + } else if (isMenuItemRegisterIframeEvent(payload)) { + const data = payload.data.menutItem; + // @ts-ignore + this.iframeCloseCallbacks.get(iframe).push(() => { + this._unregisterMenuCommandStream.next(data); + }); + handleMenuItemRegistrationEvent(payload.data); + } else if (payload.type == "setTiles" && isSetTilesEvent(payload.data)) { + this._setTilesStream.next(payload.data); + } + } + }, + false + ); } sendDataLayerEvent(dataLayerEvent: DataLayerEvent) { this.postMessage({ - 'type': 'dataLayer', - 'data': dataLayerEvent - }) - } - - - sendGameStateEvent(gameStateEvent: GameStateEvent) { - this.postMessage({ - 'type': 'gameState', - 'data': gameStateEvent + type: "dataLayer", + data: dataLayerEvent, }); } @@ -223,25 +284,25 @@ class IframeListener { } unregisterIframe(iframe: HTMLIFrameElement): void { - this.iframeCloseCallbacks.get(iframe)?.forEach(callback => { + this.iframeCloseCallbacks.get(iframe)?.forEach((callback) => { callback(); }); this.iframes.delete(iframe); } registerScript(scriptUrl: string): void { - console.log('Loading map related script at ', scriptUrl) + console.log("Loading map related script at ", scriptUrl); - if (!process.env.NODE_ENV || process.env.NODE_ENV === 'development') { + if (!process.env.NODE_ENV || process.env.NODE_ENV === "development") { // Using external iframe mode ( - const iframe = document.createElement('iframe'); + const iframe = document.createElement("iframe"); iframe.id = IframeListener.getIFrameId(scriptUrl); - iframe.style.display = 'none'; - iframe.src = '/iframe.html?script=' + encodeURIComponent(scriptUrl); + iframe.style.display = "none"; + iframe.src = "/iframe.html?script=" + encodeURIComponent(scriptUrl); // We are putting a sandbox on this script because it will run in the same domain as the main website. - iframe.sandbox.add('allow-scripts'); - iframe.sandbox.add('allow-top-navigation-by-user-activation'); + iframe.sandbox.add("allow-scripts"); + iframe.sandbox.add("allow-top-navigation-by-user-activation"); document.body.prepend(iframe); @@ -249,36 +310,50 @@ class IframeListener { this.registerIframe(iframe); } else { // production code - const iframe = document.createElement('iframe'); + const iframe = document.createElement("iframe"); iframe.id = IframeListener.getIFrameId(scriptUrl); - iframe.style.display = 'none'; + iframe.style.display = "none"; // We are putting a sandbox on this script because it will run in the same domain as the main website. - iframe.sandbox.add('allow-scripts'); - iframe.sandbox.add('allow-top-navigation-by-user-activation'); + iframe.sandbox.add("allow-scripts"); + iframe.sandbox.add("allow-top-navigation-by-user-activation"); //iframe.src = "data:text/html;charset=utf-8," + escape(html); - iframe.srcdoc = '\n' + - '\n' + + iframe.srcdoc = + "\n" + + "\n" + '\n' + - '\n' + - '\n' + - '\n' + - '\n' + - '\n' + - '\n'; + "\n" + + '\n' + + '\n' + + "\n" + + "\n" + + "\n"; document.body.prepend(iframe); this.scripts.set(scriptUrl, iframe); this.registerIframe(iframe); } + } - + private getBaseUrl(src: string, source: MessageEventSource | null): string { + for (const script of this.scripts) { + if (script[1].contentWindow === source) { + return script[0]; + } + } + return src; } private static getIFrameId(scriptUrl: string): string { - return 'script' + btoa(scriptUrl); + return "script" + btoa(scriptUrl); } unregisterScript(scriptUrl: string): void { @@ -295,47 +370,47 @@ class IframeListener { sendUserInputChat(message: string) { this.postMessage({ - 'type': 'userInputChat', - 'data': { - 'message': message, - } as UserInputChatEvent + type: "userInputChat", + data: { + message: message, + } as UserInputChatEvent, }); } sendEnterEvent(name: string) { this.postMessage({ - 'type': 'enterEvent', - 'data': { - "name": name - } as EnterLeaveEvent + type: "enterEvent", + data: { + name: name, + } as EnterLeaveEvent, }); } sendLeaveEvent(name: string) { this.postMessage({ - 'type': 'leaveEvent', - 'data': { - "name": name - } as EnterLeaveEvent + type: "leaveEvent", + data: { + name: name, + } as EnterLeaveEvent, }); } hasPlayerMoved(event: HasPlayerMovedEvent) { if (this.sendPlayerMove) { this.postMessage({ - 'type': 'hasPlayerMoved', - 'data': event + type: "hasPlayerMoved", + data: event, }); } } sendButtonClickedEvent(popupId: number, buttonId: number): void { this.postMessage({ - 'type': 'buttonClickedEvent', - 'data': { + type: "buttonClickedEvent", + data: { popupId, - buttonId - } as ButtonClickedEvent + buttonId, + } as ButtonClickedEvent, }); } @@ -344,10 +419,30 @@ class IframeListener { */ public postMessage(message: IframeResponseEvent) { for (const iframe of this.iframes) { - iframe.contentWindow?.postMessage(message, '*'); + iframe.contentWindow?.postMessage(message, "*"); } } + /** + * Registers a callback that can be used to respond to some query (as defined in the IframeQueryMap type). + * + * Important! There can be only one "answerer" so registering a new one will unregister the old one. + * + * @param key The "type" of the query we are answering + * @param callback + */ + public registerAnswerer>( + key: T, + callback: AnswererCallback, + typeChecker?: Guard + ): void { + //@ts-ignore + this.answerers[key] = callback; + } + + public unregisterAnswerer(key: keyof IframeQueryMap): void { + delete this.answerers[key]; + } } export const iframeListener = new IframeListener(); diff --git a/front/src/Api/ScriptUtils.ts b/front/src/Api/ScriptUtils.ts index e1c94507..0dbe40fe 100644 --- a/front/src/Api/ScriptUtils.ts +++ b/front/src/Api/ScriptUtils.ts @@ -1,21 +1,19 @@ -import {coWebsiteManager} from "../WebRtc/CoWebsiteManager"; +import { coWebsiteManager } from "../WebRtc/CoWebsiteManager"; class ScriptUtils { - - public openTab(url : string){ + public openTab(url: string) { window.open(url); } - public goToPage(url : string){ - window.location.href = url; - + public goToPage(url: string) { + window.location.href = url; } - public openCoWebsite(url: string, base: string) { - coWebsiteManager.loadCoWebsite(url, base); + public openCoWebsite(url: string, base: string, api: boolean, policy: string) { + coWebsiteManager.loadCoWebsite(url, base, api, policy); } - public closeCoWebSite(){ + public closeCoWebSite() { coWebsiteManager.closeCoWebsite(); } } diff --git a/front/src/Api/iframe/IframeApiContribution.ts b/front/src/Api/iframe/IframeApiContribution.ts index f3b25999..96548d5e 100644 --- a/front/src/Api/iframe/IframeApiContribution.ts +++ b/front/src/Api/iframe/IframeApiContribution.ts @@ -1,20 +1,66 @@ import type * as tg from "generic-type-guard"; -import type { IframeEvent, IframeEventMap, IframeResponseEventMap } from '../Events/IframeEvent'; +import type { + IframeEvent, + IframeEventMap, + IframeQuery, + IframeQueryMap, + IframeResponseEventMap, +} from "../Events/IframeEvent"; +import type { IframeQueryWrapper } from "../Events/IframeEvent"; export function sendToWorkadventure(content: IframeEvent) { - window.parent.postMessage(content, "*") + window.parent.postMessage(content, "*"); } -type GuardedType> = Guard extends tg.TypeGuard ? T : never -export interface IframeCallback> { +let queryNumber = 0; - typeChecker: Guard, - callback: (payloadData: T) => void +export const answerPromises = new Map< + number, + { + resolve: ( + value: + | IframeQueryMap[keyof IframeQueryMap]["answer"] + | PromiseLike + ) => void; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + reject: (reason?: any) => void; + } +>(); + +export function queryWorkadventure( + content: IframeQuery +): Promise { + return new Promise((resolve, reject) => { + window.parent.postMessage( + { + id: queryNumber, + query: content, + } as IframeQueryWrapper, + "*" + ); + + answerPromises.set(queryNumber, { + resolve, + reject, + }); + + queryNumber++; + }); +} + +type GuardedType> = Guard extends tg.TypeGuard ? T : never; + +export interface IframeCallback< + Key extends keyof IframeResponseEventMap, + T = IframeResponseEventMap[Key], + Guard = tg.TypeGuard +> { + typeChecker: Guard; + callback: (payloadData: T) => void; } export interface IframeCallbackContribution extends IframeCallback { - - type: Key + type: Key; } /** @@ -23,9 +69,10 @@ export interface IframeCallbackContribution>, -}> { - - abstract callbacks: T["callbacks"] +export abstract class IframeApiContribution< + T extends { + callbacks: Array>; + } +> { + abstract callbacks: T["callbacks"]; } diff --git a/front/src/Api/iframe/Ui/MenuItem.ts b/front/src/Api/iframe/Ui/MenuItem.ts index 9782ea7a..aa61d749 100644 --- a/front/src/Api/iframe/Ui/MenuItem.ts +++ b/front/src/Api/iframe/Ui/MenuItem.ts @@ -1,11 +1,11 @@ -import type { MenuItemClickedEvent } from '../../Events/ui/MenuItemClickedEvent'; -import { iframeListener } from '../../IframeListener'; +import type { MenuItemClickedEvent } from "../../Events/ui/MenuItemClickedEvent"; +import { iframeListener } from "../../IframeListener"; export function sendMenuClickedEvent(menuItem: string) { iframeListener.postMessage({ - 'type': 'menuItemClicked', - 'data': { + type: "menuItemClicked", + data: { menuItem: menuItem, - } as MenuItemClickedEvent + } as MenuItemClickedEvent, }); -} \ No newline at end of file +} diff --git a/front/src/Api/iframe/Ui/TriggerMessage.ts b/front/src/Api/iframe/Ui/TriggerMessage.ts index af0e20ce..333e6992 100644 --- a/front/src/Api/iframe/Ui/TriggerMessage.ts +++ b/front/src/Api/iframe/Ui/TriggerMessage.ts @@ -1,22 +1,25 @@ - -import { removeTriggerMessage, triggerMessage, TriggerMessageEvent } from '../../Events/ui/TriggerMessageEvent'; -import { sendToWorkadventure } from '../IframeApiContribution'; +import { + MessageReferenceEvent, + removeTriggerMessage, + triggerMessage, + TriggerMessageEvent, +} from "../../Events/ui/TriggerMessageEvent"; +import { queryWorkadventure } from "../IframeApiContribution"; function uuidv4() { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { - const r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8); + return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => { + const r = (Math.random() * 16) | 0, + v = c === "x" ? r : (r & 0x3) | 0x8; return v.toString(16); }); } -export let triggerMessageInstance: TriggerMessage | undefined = undefined - - +export let triggerMessageInstance: TriggerMessage | undefined = undefined; export class TriggerMessage { - uuid: string + uuid: string; constructor(private message: string, private callback: () => void) { - this.uuid = uuidv4() + this.uuid = uuidv4(); if (triggerMessageInstance) { triggerMessageInstance.remove(); } @@ -24,28 +27,24 @@ export class TriggerMessage { this.create(); } - create(): this { - sendToWorkadventure({ + async create() { + await queryWorkadventure({ type: triggerMessage, data: { message: this.message, - uuid: this.uuid - } as TriggerMessageEvent - }) - return this - } - - remove() { - sendToWorkadventure({ - type: removeTriggerMessage, - data: { - uuid: this.uuid - } as TriggerMessageEvent - }) - triggerMessageInstance = undefined - } - - trigger() { + uuid: this.uuid, + } as TriggerMessageEvent, + }); this.callback(); } -} \ No newline at end of file + + async remove() { + await queryWorkadventure({ + type: removeTriggerMessage, + data: { + uuid: this.uuid, + } as MessageReferenceEvent, + }); + triggerMessageInstance = undefined; + } +} diff --git a/front/src/Api/iframe/chat.ts b/front/src/Api/iframe/chat.ts index 7d8e6f71..5797df5a 100644 --- a/front/src/Api/iframe/chat.ts +++ b/front/src/Api/iframe/chat.ts @@ -1,30 +1,30 @@ -import type { ChatEvent } from '../Events/ChatEvent' -import { isUserInputChatEvent, UserInputChatEvent } from '../Events/UserInputChatEvent' -import { IframeApiContribution, sendToWorkadventure } from './IframeApiContribution' +import type { ChatEvent } from "../Events/ChatEvent"; +import { isUserInputChatEvent, UserInputChatEvent } from "../Events/UserInputChatEvent"; +import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution"; import { apiCallback } from "./registeredCallbacks"; -import {Subject} from "rxjs"; +import { Subject } from "rxjs"; const chatStream = new Subject(); -class WorkadventureChatCommands extends IframeApiContribution { - - callbacks = [apiCallback({ - callback: (event: UserInputChatEvent) => { - chatStream.next(event.message); - }, - type: "userInputChat", - typeChecker: isUserInputChatEvent - })] - +export class WorkadventureChatCommands extends IframeApiContribution { + callbacks = [ + apiCallback({ + callback: (event: UserInputChatEvent) => { + chatStream.next(event.message); + }, + type: "userInputChat", + typeChecker: isUserInputChatEvent, + }), + ]; sendChatMessage(message: string, author: string) { sendToWorkadventure({ - type: 'chat', + type: "chat", data: { - 'message': message, - 'author': author - } - }) + message: message, + author: author, + }, + }); } /** @@ -35,4 +35,4 @@ class WorkadventureChatCommands extends IframeApiContribution { - callbacks = [] +export class WorkadventureControlsCommands extends IframeApiContribution { + callbacks = []; disablePlayerControls(): void { - sendToWorkadventure({ 'type': 'disablePlayerControls', data: null }); + sendToWorkadventure({ type: "disablePlayerControls", data: null }); } restorePlayerControls(): void { - sendToWorkadventure({ 'type': 'restorePlayerControls', data: null }); + sendToWorkadventure({ type: "restorePlayerControls", data: null }); } } - export default new WorkadventureControlsCommands(); diff --git a/front/src/Api/iframe/nav.ts b/front/src/Api/iframe/nav.ts index 0d31c1ea..f051a7c0 100644 --- a/front/src/Api/iframe/nav.ts +++ b/front/src/Api/iframe/nav.ts @@ -1,57 +1,56 @@ -import type { GoToPageEvent } from '../Events/GoToPageEvent'; -import type { OpenTabEvent } from '../Events/OpenTabEvent'; -import { IframeApiContribution, sendToWorkadventure } from './IframeApiContribution'; -import type {OpenCoWebSiteEvent} from "../Events/OpenCoWebSiteEvent"; -import type {LoadPageEvent} from "../Events/LoadPageEvent"; - - -class WorkadventureNavigationCommands extends IframeApiContribution { - callbacks = [] +import type { GoToPageEvent } from "../Events/GoToPageEvent"; +import type { OpenTabEvent } from "../Events/OpenTabEvent"; +import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution"; +import type { OpenCoWebSiteEvent } from "../Events/OpenCoWebSiteEvent"; +import type { LoadPageEvent } from "../Events/LoadPageEvent"; +export class WorkadventureNavigationCommands extends IframeApiContribution { + callbacks = []; openTab(url: string): void { sendToWorkadventure({ - "type": 'openTab', - "data": { - url - } + type: "openTab", + data: { + url, + }, }); } goToPage(url: string): void { sendToWorkadventure({ - "type": 'goToPage', - "data": { - url - } + type: "goToPage", + data: { + url, + }, }); } goToRoom(url: string): void { sendToWorkadventure({ - "type": 'loadPage', - "data": { - url - } + type: "loadPage", + data: { + url, + }, }); } - openCoWebSite(url: string): void { + openCoWebSite(url: string, allowApi: boolean = false, allowPolicy: string = ""): void { sendToWorkadventure({ - "type": 'openCoWebSite', - "data": { - url - } + type: "openCoWebSite", + data: { + url, + allowApi, + allowPolicy, + }, }); } closeCoWebSite(): void { sendToWorkadventure({ - "type": 'closeCoWebSite', - data: null + type: "closeCoWebSite", + data: null, }); } } - export default new WorkadventureNavigationCommands(); diff --git a/front/src/Api/iframe/player.ts b/front/src/Api/iframe/player.ts index 67c012f7..e130d3f2 100644 --- a/front/src/Api/iframe/player.ts +++ b/front/src/Api/iframe/player.ts @@ -1,29 +1,29 @@ -import {IframeApiContribution, sendToWorkadventure} from "./IframeApiContribution"; -import type {HasPlayerMovedEvent, HasPlayerMovedEventCallback} from "../Events/HasPlayerMovedEvent"; -import {Subject} from "rxjs"; -import {apiCallback} from "./registeredCallbacks"; -import {isHasPlayerMovedEvent} from "../Events/HasPlayerMovedEvent"; +import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution"; +import type { HasPlayerMovedEvent, HasPlayerMovedEventCallback } from "../Events/HasPlayerMovedEvent"; +import { Subject } from "rxjs"; +import { apiCallback } from "./registeredCallbacks"; +import { isHasPlayerMovedEvent } from "../Events/HasPlayerMovedEvent"; const moveStream = new Subject(); -class WorkadventurePlayerCommands extends IframeApiContribution { +export class WorkadventurePlayerCommands extends IframeApiContribution { callbacks = [ apiCallback({ - type: 'hasPlayerMoved', + type: "hasPlayerMoved", typeChecker: isHasPlayerMovedEvent, callback: (payloadData) => { moveStream.next(payloadData); - } + }, }), - ] + ]; onPlayerMove(callback: HasPlayerMovedEventCallback): void { moveStream.subscribe(callback); sendToWorkadventure({ - type: 'onPlayerMove', - data: null - }) + type: "onPlayerMove", + data: null, + }); } } -export default new WorkadventurePlayerCommands(); \ No newline at end of file +export default new WorkadventurePlayerCommands(); diff --git a/front/src/Api/iframe/room.ts b/front/src/Api/iframe/room.ts index aed4d983..deee0e2a 100644 --- a/front/src/Api/iframe/room.ts +++ b/front/src/Api/iframe/room.ts @@ -1,87 +1,81 @@ import { Subject } from "rxjs"; -import { EnterLeaveEvent, isEnterLeaveEvent } from '../Events/EnterLeaveEvent'; -import {IframeApiContribution, sendToWorkadventure} from './IframeApiContribution'; + +import { isDataLayerEvent } from "../Events/DataLayerEvent"; +import { EnterLeaveEvent, isEnterLeaveEvent } from "../Events/EnterLeaveEvent"; +import { isGameStateEvent } from "../Events/GameStateEvent"; + +import { IframeApiContribution, queryWorkadventure, sendToWorkadventure } from "./IframeApiContribution"; import { apiCallback } from "./registeredCallbacks"; -import type {LayerEvent} from "../Events/LayerEvent"; -import type {SetPropertyEvent} from "../Events/setPropertyEvent"; -import type {GameStateEvent} from "../Events/GameStateEvent"; -import type {ITiledMap} from "../../Phaser/Map/ITiledMap"; -import type {DataLayerEvent} from "../Events/DataLayerEvent"; -import {isGameStateEvent} from "../Events/GameStateEvent"; -import {isDataLayerEvent} from "../Events/DataLayerEvent"; + +import type { ITiledMap } from "../../Phaser/Map/ITiledMap"; +import type { DataLayerEvent } from "../Events/DataLayerEvent"; +import type { GameStateEvent } from "../Events/GameStateEvent"; const enterStreams: Map> = new Map>(); const leaveStreams: Map> = new Map>(); const dataLayerResolver = new Subject(); const stateResolvers = new Subject(); -let immutableData: GameStateEvent; +let immutableDataPromise: Promise | undefined = undefined; interface Room { - id: string, - mapUrl: string, - map: ITiledMap, - startLayer: string | null + id: string; + mapUrl: string; + map: ITiledMap; + startLayer: string | null; } interface User { - id: string | undefined, - nickName: string | null, - tags: string[] + id: string | undefined; + nickName: string | null; + tags: string[]; } +interface TileDescriptor { + x: number; + y: number; + tile: number | string; + layer: string; +} function getGameState(): Promise { - if (immutableData) { - return Promise.resolve(immutableData); - } - else { - return new Promise((resolver, thrower) => { - stateResolvers.subscribe(resolver); - sendToWorkadventure({type: "getState", data: null}); - }) + if (immutableDataPromise === undefined) { + immutableDataPromise = queryWorkadventure({ type: "getState", data: undefined }); } + return immutableDataPromise; } function getDataLayer(): Promise { return new Promise((resolver, thrower) => { dataLayerResolver.subscribe(resolver); - sendToWorkadventure({type: "getDataLayer", data: null}) - }) + sendToWorkadventure({ type: "getDataLayer", data: null }); + }); } -class WorkadventureRoomCommands extends IframeApiContribution { +export class WorkadventureRoomCommands extends IframeApiContribution { callbacks = [ apiCallback({ callback: (payloadData: EnterLeaveEvent) => { enterStreams.get(payloadData.name)?.next(); }, type: "enterEvent", - typeChecker: isEnterLeaveEvent + typeChecker: isEnterLeaveEvent, }), apiCallback({ type: "leaveEvent", typeChecker: isEnterLeaveEvent, callback: (payloadData) => { leaveStreams.get(payloadData.name)?.next(); - } - }), - apiCallback({ - type: "gameState", - typeChecker: isGameStateEvent, - callback: (payloadData) => { - stateResolvers.next(payloadData); - } + }, }), apiCallback({ type: "dataLayer", typeChecker: isDataLayerEvent, callback: (payloadData) => { dataLayerResolver.next(payloadData); - } + }, }), - ] - + ]; onEnterZone(name: string, callback: () => void): void { let subject = enterStreams.get(name); @@ -90,7 +84,6 @@ class WorkadventureRoomCommands extends IframeApiContribution void): void { let subject = leaveStreams.get(name); @@ -101,35 +94,44 @@ class WorkadventureRoomCommands extends IframeApiContribution { return getGameState().then((gameState) => { - return getDataLayer().then((mapJson) => { - return {id: gameState.roomId, map: mapJson.data as ITiledMap, mapUrl: gameState.mapUrl, startLayer: gameState.startLayerName}; - }) - }) + return getDataLayer().then((mapJson) => { + return { + id: gameState.roomId, + map: mapJson.data as ITiledMap, + mapUrl: gameState.mapUrl, + startLayer: gameState.startLayerName, + }; + }); + }); } getCurrentUser(): Promise { return getGameState().then((gameState) => { - return {id: gameState.uuid, nickName: gameState.nickname, tags: gameState.tags}; - }) + return { id: gameState.uuid, nickName: gameState.nickname, tags: gameState.tags }; + }); + } + setTiles(tiles: TileDescriptor[]) { + sendToWorkadventure({ + type: "setTiles", + data: tiles, + }); } - } - export default new WorkadventureRoomCommands(); diff --git a/front/src/Api/iframe/sound.ts b/front/src/Api/iframe/sound.ts index 70430b46..1e5d6157 100644 --- a/front/src/Api/iframe/sound.ts +++ b/front/src/Api/iframe/sound.ts @@ -1,17 +1,15 @@ -import type { LoadSoundEvent } from '../Events/LoadSoundEvent'; -import type { PlaySoundEvent } from '../Events/PlaySoundEvent'; -import type { StopSoundEvent } from '../Events/StopSoundEvent'; -import { IframeApiContribution, sendToWorkadventure } from './IframeApiContribution'; -import {Sound} from "./Sound/Sound"; +import type { LoadSoundEvent } from "../Events/LoadSoundEvent"; +import type { PlaySoundEvent } from "../Events/PlaySoundEvent"; +import type { StopSoundEvent } from "../Events/StopSoundEvent"; +import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution"; +import { Sound } from "./Sound/Sound"; -class WorkadventureSoundCommands extends IframeApiContribution { - callbacks = [] +export class WorkadventureSoundCommands extends IframeApiContribution { + callbacks = []; loadSound(url: string): Sound { return new Sound(url); } - } - export default new WorkadventureSoundCommands(); diff --git a/front/src/Api/iframe/ui.ts b/front/src/Api/iframe/ui.ts index 834cc347..c1fa85b5 100644 --- a/front/src/Api/iframe/ui.ts +++ b/front/src/Api/iframe/ui.ts @@ -1,20 +1,29 @@ -import { isButtonClickedEvent } from '../Events/ButtonClickedEvent'; -import { isMenuItemClickedEvent } from '../Events/ui/MenuItemClickedEvent'; -import { isMessageReferenceEvent } from '../Events/ui/TriggerMessageEvent'; -import { IframeApiContribution, sendToWorkadventure } from './IframeApiContribution'; +import { isButtonClickedEvent } from "../Events/ButtonClickedEvent"; +import { isMenuItemClickedEvent } from "../Events/ui/MenuItemClickedEvent"; +import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution"; import { apiCallback } from "./registeredCallbacks"; import type { ButtonClickedCallback, ButtonDescriptor } from "./Ui/ButtonDescriptor"; import { Popup } from "./Ui/Popup"; -import { TriggerMessage, triggerMessageInstance } from './Ui/TriggerMessage'; +import { TriggerMessage } from "./Ui/TriggerMessage"; let popupId = 0; const popups: Map = new Map(); -const popupCallbacks: Map> = new Map>(); +const popupCallbacks: Map> = new Map< + number, + Map +>(); -const menuCallbacks: Map void> = new Map() +const menuCallbacks: Map void> = new Map(); -class WorkAdventureUiCommands extends IframeApiContribution { +interface ZonedPopupOptions { + zone: string; + objectLayerName?: string; + popupText: string; + delay?: number; + popupOptions: Array; +} +export class WorkAdventureUiCommands extends IframeApiContribution { callbacks = [ apiCallback({ type: "buttonClickedEvent", @@ -28,28 +37,20 @@ class WorkAdventureUiCommands extends IframeApiContribution { + callback: (event) => { const callback = menuCallbacks.get(event.menuItem); if (callback) { - callback(event.menuItem) + callback(event.menuItem); } - } + }, }), - apiCallback({ - type: "messageTriggered", - typeChecker: isMessageReferenceEvent, - callback: event => { - triggerMessageInstance?.trigger(); - } - }) ]; - openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[]): Popup { popupId++; const popup = new Popup(popupId); @@ -67,41 +68,42 @@ class WorkAdventureUiCommands extends IframeApiContribution { return { label: button.label, - className: button.className + className: button.className, }; - }) - } + }), + }, }); - popups.set(popupId, popup) + popups.set(popupId, popup); return popup; } registerMenuCommand(commandDescriptor: string, callback: (commandDescriptor: string) => void) { menuCallbacks.set(commandDescriptor, callback); sendToWorkadventure({ - 'type': 'registerMenuCommand', - 'data': { - menutItem: commandDescriptor - } + type: "registerMenuCommand", + data: { + menutItem: commandDescriptor, + }, }); } displayBubble(): void { - sendToWorkadventure({ 'type': 'displayBubble', data: null }); + sendToWorkadventure({ type: "displayBubble", data: null }); } removeBubble(): void { - sendToWorkadventure({ 'type': 'removeBubble', data: null }); + sendToWorkadventure({ type: "removeBubble", data: null }); } + triggerMessage(message: string, callback: () => void): TriggerMessage { return new TriggerMessage(message, callback); } diff --git a/front/src/Components/App.svelte b/front/src/Components/App.svelte index 2e159d2d..8ade9398 100644 --- a/front/src/Components/App.svelte +++ b/front/src/Components/App.svelte @@ -1,5 +1,5 @@
@@ -68,6 +71,7 @@ --> {#if $gameOverlayVisibilityStore}
+
diff --git a/front/src/Components/CameraControls.svelte b/front/src/Components/CameraControls.svelte index 5c17a9fe..d6b31af4 100644 --- a/front/src/Components/CameraControls.svelte +++ b/front/src/Components/CameraControls.svelte @@ -7,6 +7,11 @@ import cinemaCloseImg from "./images/cinema-close.svg"; import microphoneImg from "./images/microphone.svg"; import microphoneCloseImg from "./images/microphone-close.svg"; + import layoutPresentationImg from "./images/layout-presentation.svg"; + import layoutChatImg from "./images/layout-chat.svg"; + import {layoutModeStore} from "../Stores/StreamableCollectionStore"; + import {LayoutMode} from "../WebRtc/LayoutManager"; + import {peerStore} from "../Stores/PeerStore"; function screenSharingClick(): void { if ($requestedScreenSharingState === true) { @@ -32,10 +37,24 @@ } } + function switchLayoutMode() { + if ($layoutModeStore === LayoutMode.Presentation) { + $layoutModeStore = LayoutMode.VideoChat; + } else { + $layoutModeStore = LayoutMode.Presentation; + } + }
+
+ {#if $layoutModeStore === LayoutMode.Presentation } + Switch to mosaic mode + {:else} + Switch to presentation mode + {/if} +
{#if $requestedScreenSharingState} Start screen sharing diff --git a/front/src/Components/CustomCharacterScene/CustomCharacterScene.svelte b/front/src/Components/CustomCharacterScene/CustomCharacterScene.svelte index 1807f15d..684975bb 100644 --- a/front/src/Components/CustomCharacterScene/CustomCharacterScene.svelte +++ b/front/src/Components/CustomCharacterScene/CustomCharacterScene.svelte @@ -1,11 +1,11 @@ + +
+ {#each [...$streamableCollectionStore.values()] as peer (peer.uniqueId)} + + {/each} +
diff --git a/front/src/Components/Video/LocalStreamMediaBox.svelte b/front/src/Components/Video/LocalStreamMediaBox.svelte new file mode 100644 index 00000000..55c7f22c --- /dev/null +++ b/front/src/Components/Video/LocalStreamMediaBox.svelte @@ -0,0 +1,16 @@ + + + +
+ {#if stream} + + {/if} +
diff --git a/front/src/Components/Video/MediaBox.svelte b/front/src/Components/Video/MediaBox.svelte new file mode 100644 index 00000000..9160a7a9 --- /dev/null +++ b/front/src/Components/Video/MediaBox.svelte @@ -0,0 +1,20 @@ + + +
+ {#if streamable instanceof VideoPeer} + + {:else if streamable instanceof ScreenSharingPeer} + + {:else} + + {/if} +
diff --git a/front/src/Components/Video/PresentationLayout.svelte b/front/src/Components/Video/PresentationLayout.svelte new file mode 100644 index 00000000..f68dd2f1 --- /dev/null +++ b/front/src/Components/Video/PresentationLayout.svelte @@ -0,0 +1,24 @@ + + +
+ {#if $videoFocusStore } + + {/if} +
+ diff --git a/front/src/Components/Video/ScreenSharingMediaBox.svelte b/front/src/Components/Video/ScreenSharingMediaBox.svelte new file mode 100644 index 00000000..c6e1564f --- /dev/null +++ b/front/src/Components/Video/ScreenSharingMediaBox.svelte @@ -0,0 +1,33 @@ + + +
+ {#if $statusStore === 'connecting'} +
+ {/if} + {#if $statusStore === 'error'} +
+ {/if} + {#if $streamStore === null} + {name} + {:else} + + {/if} +
+ + diff --git a/front/src/Components/Video/VideoMediaBox.svelte b/front/src/Components/Video/VideoMediaBox.svelte new file mode 100644 index 00000000..1a581914 --- /dev/null +++ b/front/src/Components/Video/VideoMediaBox.svelte @@ -0,0 +1,48 @@ + + +
+ {#if $statusStore === 'connecting'} +
+ {/if} + {#if $statusStore === 'error'} +
+ {/if} + {#if !$constraintStore || $constraintStore.video === false} + {name} + {/if} + {#if $constraintStore && $constraintStore.audio === false} + Muted + {/if} + + {#if $streamStore } + + {/if} + + {#if $constraintStore && $constraintStore.audio !== false} + + {/if} +
+ diff --git a/front/src/Components/Video/VideoOverlay.svelte b/front/src/Components/Video/VideoOverlay.svelte new file mode 100644 index 00000000..feb13743 --- /dev/null +++ b/front/src/Components/Video/VideoOverlay.svelte @@ -0,0 +1,23 @@ + + +
+ {#if $layoutModeStore === LayoutMode.Presentation } + + {:else } + + {/if} +
+ + diff --git a/front/src/Components/Video/images/blockSign.svg b/front/src/Components/Video/images/blockSign.svg new file mode 100644 index 00000000..c64ba294 --- /dev/null +++ b/front/src/Components/Video/images/blockSign.svg @@ -0,0 +1,22 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/front/src/Components/Video/images/report.svg b/front/src/Components/Video/images/report.svg new file mode 100644 index 00000000..14753256 --- /dev/null +++ b/front/src/Components/Video/images/report.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front/src/Components/Video/utils.ts b/front/src/Components/Video/utils.ts new file mode 100644 index 00000000..ca1f3b41 --- /dev/null +++ b/front/src/Components/Video/utils.ts @@ -0,0 +1,27 @@ +export function getColorByString(str: string): string | null { + let hash = 0; + if (str.length === 0) { + return null; + } + for (let i = 0; i < str.length; i++) { + hash = str.charCodeAt(i) + ((hash << 5) - hash); + hash = hash & hash; + } + let color = "#"; + for (let i = 0; i < 3; i++) { + const value = (hash >> (i * 8)) & 255; + color += ("00" + value.toString(16)).substr(-2); + } + return color; +} + +export function srcObject(node: HTMLVideoElement, stream: MediaStream) { + node.srcObject = stream; + return { + update(newStream: MediaStream) { + if (node.srcObject != newStream) { + node.srcObject = newStream; + } + }, + }; +} diff --git a/front/src/Components/images/layout-chat.svg b/front/src/Components/images/layout-chat.svg new file mode 100644 index 00000000..af071d49 --- /dev/null +++ b/front/src/Components/images/layout-chat.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front/src/Components/images/layout-presentation.svg b/front/src/Components/images/layout-presentation.svg new file mode 100644 index 00000000..bf65d002 --- /dev/null +++ b/front/src/Components/images/layout-presentation.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front/src/Connexion/Room.ts b/front/src/Connexion/Room.ts index 3ae8d2ed..7b138198 100644 --- a/front/src/Connexion/Room.ts +++ b/front/src/Connexion/Room.ts @@ -1,60 +1,71 @@ import Axios from "axios"; -import {PUSHER_URL} from "../Enum/EnvironmentVariable"; -import type {CharacterTexture} from "./LocalUser"; +import { PUSHER_URL } from "../Enum/EnvironmentVariable"; +import type { CharacterTexture } from "./LocalUser"; -export class MapDetail{ - constructor(public readonly mapUrl: string, public readonly textures : CharacterTexture[]|undefined) { - } +export class MapDetail { + constructor(public readonly mapUrl: string, public readonly textures: CharacterTexture[] | undefined) {} } export class Room { public readonly id: string; public readonly isPublic: boolean; - private mapUrl: string|undefined; - private textures: CharacterTexture[]|undefined; - private instance: string|undefined; + private mapUrl: string | undefined; + private textures: CharacterTexture[] | undefined; + private instance: string | undefined; private _search: URLSearchParams; constructor(id: string) { - const url = new URL(id, 'https://example.com'); + const url = new URL(id, "https://example.com"); this.id = url.pathname; - if (this.id.startsWith('/')) { + if (this.id.startsWith("/")) { this.id = this.id.substr(1); } - if (this.id.startsWith('_/')) { + if (this.id.startsWith("_/")) { this.isPublic = true; - } else if (this.id.startsWith('@/')) { + } else if (this.id.startsWith("@/")) { this.isPublic = false; } else { - throw new Error('Invalid room ID'); + throw new Error("Invalid room ID"); } this._search = new URLSearchParams(url.search); } - public static getIdFromIdentifier(identifier: string, baseUrl: string, currentInstance: string): {roomId: string, hash: string} { - let roomId = ''; - let hash = ''; - if (!identifier.startsWith('/_/') && !identifier.startsWith('/@/')) { //relative file link + public static getIdFromIdentifier( + identifier: string, + baseUrl: string, + currentInstance: string + ): { roomId: string; hash: string | null } { + let roomId = ""; + let hash = null; + if (!identifier.startsWith("/_/") && !identifier.startsWith("/@/")) { + //relative file link //Relative identifier can be deep enough to rewrite the base domain, so we cannot use the variable 'baseUrl' as the actual base url for the URL objects. //We instead use 'workadventure' as a dummy base value. const baseUrlObject = new URL(baseUrl); - const absoluteExitSceneUrl = new URL(identifier, 'http://workadventure/_/'+currentInstance+'/'+baseUrlObject.hostname+baseUrlObject.pathname); + const absoluteExitSceneUrl = new URL( + identifier, + "http://workadventure/_/" + currentInstance + "/" + baseUrlObject.hostname + baseUrlObject.pathname + ); roomId = absoluteExitSceneUrl.pathname; //in case of a relative url, we need to create a public roomId roomId = roomId.substring(1); //remove the leading slash hash = absoluteExitSceneUrl.hash; hash = hash.substring(1); //remove the leading diese - } else { //absolute room Id - const parts = identifier.split('#'); + if (!hash.length) { + hash = null; + } + } else { + //absolute room Id + const parts = identifier.split("#"); roomId = parts[0]; roomId = roomId.substring(1); //remove the leading slash if (parts.length > 1) { - hash = parts[1] + hash = parts[1]; } } - return {roomId, hash} + return { roomId, hash }; } public async getMapDetail(): Promise { @@ -66,8 +77,8 @@ export class Room { if (this.isPublic) { const match = /_\/[^/]+\/(.+)/.exec(this.id); - if (!match) throw new Error('Could not extract url from "'+this.id+'"'); - this.mapUrl = window.location.protocol+'//'+match[1]; + if (!match) throw new Error('Could not extract url from "' + this.id + '"'); + this.mapUrl = window.location.protocol + "//" + match[1]; resolve(new MapDetail(this.mapUrl, this.textures)); return; } else { @@ -75,14 +86,16 @@ export class Room { const urlParts = this.parsePrivateUrl(this.id); Axios.get(`${PUSHER_URL}/map`, { - params: urlParts - }).then(({data}) => { - console.log('Map ', this.id, ' resolves to URL ', data.mapUrl); - resolve(data); - return; - }).catch((reason) => { - reject(reason); - }); + params: urlParts, + }) + .then(({ data }) => { + console.log("Map ", this.id, " resolves to URL ", data.mapUrl); + resolve(data); + return; + }) + .catch((reason) => { + reject(reason); + }); } }); } @@ -99,37 +112,36 @@ export class Room { if (this.isPublic) { const match = /_\/([^/]+)\/.+/.exec(this.id); - if (!match) throw new Error('Could not extract instance from "'+this.id+'"'); + if (!match) throw new Error('Could not extract instance from "' + this.id + '"'); this.instance = match[1]; return this.instance; } else { const match = /@\/([^/]+)\/([^/]+)\/.+/.exec(this.id); - if (!match) throw new Error('Could not extract instance from "'+this.id+'"'); - this.instance = match[1]+'/'+match[2]; + if (!match) throw new Error('Could not extract instance from "' + this.id + '"'); + this.instance = match[1] + "/" + match[2]; return this.instance; } } - private parsePrivateUrl(url: string): { organizationSlug: string, worldSlug: string, roomSlug?: string } { + private parsePrivateUrl(url: string): { organizationSlug: string; worldSlug: string; roomSlug?: string } { const regex = /@\/([^/]+)\/([^/]+)(?:\/([^/]*))?/gm; const match = regex.exec(url); if (!match) { - throw new Error('Invalid URL '+url); + throw new Error("Invalid URL " + url); } - const results: { organizationSlug: string, worldSlug: string, roomSlug?: string } = { + const results: { organizationSlug: string; worldSlug: string; roomSlug?: string } = { organizationSlug: match[1], worldSlug: match[2], - } + }; if (match[3] !== undefined) { results.roomSlug = match[3]; } return results; } - public isDisconnected(): boolean - { - const alone = this._search.get('alone'); - if (alone && alone !== '0' && alone.toLowerCase() !== 'false') { + public isDisconnected(): boolean { + const alone = this._search.get("alone"); + if (alone && alone !== "0" && alone.toLowerCase() !== "false") { return true; } return false; diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index dc9830c3..1b080a55 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -11,7 +11,8 @@ import { RoomJoinedMessage, ServerToClientMessage, SetPlayerDetailsMessage, - SilentMessage, StopGlobalMessage, + SilentMessage, + StopGlobalMessage, UserJoinedMessage, UserLeftMessage, UserMovedMessage, @@ -31,17 +32,22 @@ import { EmotePromptMessage, SendUserMessage, BanUserMessage, -} from "../Messages/generated/messages_pb" +} from "../Messages/generated/messages_pb"; import type { UserSimplePeerInterface } from "../WebRtc/SimplePeer"; import Direction = PositionMessage.Direction; import { ProtobufClientUtils } from "../Network/ProtobufClientUtils"; import { EventMessage, - GroupCreatedUpdatedMessageInterface, ItemEventMessageInterface, - MessageUserJoined, OnConnectInterface, PlayGlobalMessageInterface, PositionInterface, + GroupCreatedUpdatedMessageInterface, + ItemEventMessageInterface, + MessageUserJoined, + OnConnectInterface, + PlayGlobalMessageInterface, + PositionInterface, RoomJoinedMessageInterface, - ViewportInterface, WebRtcDisconnectMessageInterface, + ViewportInterface, + WebRtcDisconnectMessageInterface, WebRtcSignalReceivedMessageInterface, } from "./ConnexionModels"; import type { BodyResourceDescriptionInterface } from "../Phaser/Entity/PlayerTextures"; @@ -61,7 +67,8 @@ export class RoomConnection implements RoomConnection { private closed: boolean = false; private tags: string[] = []; - public static setWebsocketFactory(websocketFactory: (url: string) => any): void { // eslint-disable-line @typescript-eslint/no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + public static setWebsocketFactory(websocketFactory: (url: string) => any): void { RoomConnection.websocketFactory = websocketFactory; } @@ -70,27 +77,35 @@ export class RoomConnection implements RoomConnection { * @param token A JWT token containing the UUID of the user * @param roomId The ID of the room in the form "_/[instance]/[map_url]" or "@/[org]/[event]/[map]" */ - public constructor(token: string | null, roomId: string, name: string, characterLayers: string[], position: PositionInterface, viewport: ViewportInterface, companion: string | null) { + public constructor( + token: string | null, + roomId: string, + name: string, + characterLayers: string[], + position: PositionInterface, + viewport: ViewportInterface, + companion: string | null + ) { let url = new URL(PUSHER_URL, window.location.toString()).toString(); - url = url.replace('http://', 'ws://').replace('https://', 'wss://'); - if (!url.endsWith('/')) { - url += '/'; + url = url.replace("http://", "ws://").replace("https://", "wss://"); + if (!url.endsWith("/")) { + url += "/"; } - url += 'room'; - url += '?roomId=' + (roomId ? encodeURIComponent(roomId) : ''); - url += '&token=' + (token ? encodeURIComponent(token) : ''); - url += '&name=' + encodeURIComponent(name); + url += "room"; + url += "?roomId=" + (roomId ? encodeURIComponent(roomId) : ""); + url += "&token=" + (token ? encodeURIComponent(token) : ""); + url += "&name=" + encodeURIComponent(name); for (const layer of characterLayers) { - url += '&characterLayers=' + encodeURIComponent(layer); + url += "&characterLayers=" + encodeURIComponent(layer); } - url += '&x=' + Math.floor(position.x); - url += '&y=' + Math.floor(position.y); - url += '&top=' + Math.floor(viewport.top); - url += '&bottom=' + Math.floor(viewport.bottom); - url += '&left=' + Math.floor(viewport.left); - url += '&right=' + Math.floor(viewport.right); - if (typeof companion === 'string') { - url += '&companion=' + encodeURIComponent(companion); + url += "&x=" + Math.floor(position.x); + url += "&y=" + Math.floor(position.y); + url += "&top=" + Math.floor(viewport.top); + url += "&bottom=" + Math.floor(viewport.bottom); + url += "&left=" + Math.floor(viewport.left); + url += "&right=" + Math.floor(viewport.right); + if (typeof companion === "string") { + url += "&companion=" + encodeURIComponent(companion); } if (RoomConnection.websocketFactory) { @@ -99,7 +114,7 @@ export class RoomConnection implements RoomConnection { this.socket = new WebSocket(url); } - this.socket.binaryType = 'arraybuffer'; + this.socket.binaryType = "arraybuffer"; let interval: ReturnType | undefined = undefined; @@ -109,7 +124,7 @@ export class RoomConnection implements RoomConnection { interval = setInterval(() => this.socket.send(pingMessage.serializeBinary().buffer), manualPingDelay); }; - this.socket.addEventListener('close', (event) => { + this.socket.addEventListener("close", (event) => { if (interval) { clearInterval(interval); } @@ -126,7 +141,7 @@ export class RoomConnection implements RoomConnection { if (message.hasBatchmessage()) { for (const subMessage of (message.getBatchmessage() as BatchMessage).getPayloadList()) { - let event: string|null = null; + let event: string | null = null; let payload; if (subMessage.hasUsermovedmessage()) { event = EventMessage.USER_MOVED; @@ -150,7 +165,7 @@ export class RoomConnection implements RoomConnection { const emoteMessage = subMessage.getEmoteeventmessage() as EmoteEventMessage; emoteEventStream.fire(emoteMessage.getActoruserid(), emoteMessage.getEmote()); } else { - throw new Error('Unexpected batch message type'); + throw new Error("Unexpected batch message type"); } if (event) { @@ -171,8 +186,8 @@ export class RoomConnection implements RoomConnection { this.dispatch(EventMessage.CONNECT, { connection: this, room: { - items - } as RoomJoinedMessageInterface + items, + } as RoomJoinedMessageInterface, }); } else if (message.hasWorldfullmessage()) { worldFullMessageStream.onMessage(); @@ -183,7 +198,10 @@ export class RoomConnection implements RoomConnection { } else if (message.hasWebrtcsignaltoclientmessage()) { this.dispatch(EventMessage.WEBRTC_SIGNAL, message.getWebrtcsignaltoclientmessage()); } else if (message.hasWebrtcscreensharingsignaltoclientmessage()) { - this.dispatch(EventMessage.WEBRTC_SCREEN_SHARING_SIGNAL, message.getWebrtcscreensharingsignaltoclientmessage()); + this.dispatch( + EventMessage.WEBRTC_SCREEN_SHARING_SIGNAL, + message.getWebrtcscreensharingsignaltoclientmessage() + ); } else if (message.hasWebrtcstartmessage()) { this.dispatch(EventMessage.WEBRTC_START, message.getWebrtcstartmessage()); } else if (message.hasWebrtcdisconnectmessage()) { @@ -205,10 +223,9 @@ export class RoomConnection implements RoomConnection { } else if (message.hasRefreshroommessage()) { //todo: implement a way to notify the user the room was refreshed. } else { - throw new Error('Unknown message received'); + throw new Error("Unknown message received"); } - - } + }; } private dispatch(event: string, payload: unknown): void { @@ -243,16 +260,16 @@ export class RoomConnection implements RoomConnection { positionMessage.setY(Math.floor(y)); let directionEnum: Direction; switch (direction) { - case 'up': + case "up": directionEnum = Direction.UP; break; - case 'down': + case "down": directionEnum = Direction.DOWN; break; - case 'left': + case "left": directionEnum = Direction.LEFT; break; - case 'right': + case "right": directionEnum = Direction.RIGHT; break; default: @@ -327,15 +344,17 @@ export class RoomConnection implements RoomConnection { private toMessageUserJoined(message: UserJoinedMessage): MessageUserJoined { const position = message.getPosition(); if (position === undefined) { - throw new Error('Invalid JOIN_ROOM message'); + throw new Error("Invalid JOIN_ROOM message"); } - const characterLayers = message.getCharacterlayersList().map((characterLayer: CharacterLayerMessage): BodyResourceDescriptionInterface => { - return { - name: characterLayer.getName(), - img: characterLayer.getUrl() - } - }) + const characterLayers = message + .getCharacterlayersList() + .map((characterLayer: CharacterLayerMessage): BodyResourceDescriptionInterface => { + return { + name: characterLayer.getName(), + img: characterLayer.getUrl(), + }; + }); const companion = message.getCompanion(); @@ -345,8 +364,8 @@ export class RoomConnection implements RoomConnection { characterLayers, visitCardUrl: message.getVisitcardurl(), position: ProtobufClientUtils.toPointInterface(position), - companion: companion ? companion.getName() : null - } + companion: companion ? companion.getName() : null, + }; } public onUserMoved(callback: (message: UserMovedMessage) => void): void { @@ -372,7 +391,9 @@ export class RoomConnection implements RoomConnection { }); } - public onGroupUpdatedOrCreated(callback: (groupCreateUpdateMessage: GroupCreatedUpdatedMessageInterface) => void): void { + public onGroupUpdatedOrCreated( + callback: (groupCreateUpdateMessage: GroupCreatedUpdatedMessageInterface) => void + ): void { this.onMessage(EventMessage.GROUP_CREATE_UPDATE, (message: GroupUpdateMessage) => { callback(this.toGroupCreatedUpdatedMessage(message)); }); @@ -381,14 +402,14 @@ export class RoomConnection implements RoomConnection { private toGroupCreatedUpdatedMessage(message: GroupUpdateMessage): GroupCreatedUpdatedMessageInterface { const position = message.getPosition(); if (position === undefined) { - throw new Error('Missing position in GROUP_CREATE_UPDATE'); + throw new Error("Missing position in GROUP_CREATE_UPDATE"); } return { groupId: message.getGroupid(), position: position.toObject(), - groupSize: message.getGroupsize() - } + groupSize: message.getGroupsize(), + }; } public onGroupDeleted(callback: (groupId: number) => void): void { @@ -404,7 +425,7 @@ export class RoomConnection implements RoomConnection { } public onConnectError(callback: (error: Event) => void): void { - this.socket.addEventListener('error', callback) + this.socket.addEventListener("error", callback); } public onConnect(callback: (roomConnection: OnConnectInterface) => void): void { @@ -476,11 +497,11 @@ export class RoomConnection implements RoomConnection { } public onServerDisconnected(callback: () => void): void { - this.socket.addEventListener('close', (event) => { + this.socket.addEventListener("close", (event) => { if (this.closed === true || connectionManager.unloading) { return; } - console.log('Socket closed with code ' + event.code + ". Reason: " + event.reason); + console.log("Socket closed with code " + event.code + ". Reason: " + event.reason); if (event.code === 1000) { // Normal closure case return; @@ -490,14 +511,14 @@ export class RoomConnection implements RoomConnection { } public getUserId(): number { - if (this.userId === null) throw 'UserId cannot be null!' + if (this.userId === null) throw "UserId cannot be null!"; return this.userId; } disconnectMessage(callback: (message: WebRtcDisconnectMessageInterface) => void): void { this.onMessage(EventMessage.WEBRTC_DISCONNECT, (message: WebRtcDisconnectMessage) => { callback({ - userId: message.getUserid() + userId: message.getUserid(), }); }); } @@ -521,21 +542,22 @@ export class RoomConnection implements RoomConnection { itemId: message.getItemid(), event: message.getEvent(), parameters: JSON.parse(message.getParametersjson()), - state: JSON.parse(message.getStatejson()) + state: JSON.parse(message.getStatejson()), }); }); } public uploadAudio(file: FormData) { - return Axios.post(`${UPLOADER_URL}/upload-audio-message`, file).then((res: { data: {} }) => { - return res.data; - }).catch((err) => { - console.error(err); - throw err; - }); + return Axios.post(`${UPLOADER_URL}/upload-audio-message`, file) + .then((res: { data: {} }) => { + return res.data; + }) + .catch((err) => { + console.error(err); + throw err; + }); } - public receivePlayGlobalMessage(callback: (message: PlayGlobalMessageInterface) => void) { return this.onMessage(EventMessage.PLAY_GLOBAL_MESSAGE, (message: PlayGlobalMessage) => { callback({ @@ -605,12 +627,12 @@ export class RoomConnection implements RoomConnection { } public isAdmin(): boolean { - return this.hasTag('admin'); + return this.hasTag("admin"); } public emitEmoteEvent(emoteName: string): void { const emoteMessage = new EmotePromptMessage(); - emoteMessage.setEmote(emoteName) + emoteMessage.setEmote(emoteName); const clientToServerMessage = new ClientToServerMessage(); clientToServerMessage.setEmotepromptmessage(emoteMessage); @@ -618,7 +640,7 @@ export class RoomConnection implements RoomConnection { this.socket.send(clientToServerMessage.serializeBinary().buffer); } - public getAllTags() : string[] { + public getAllTags(): string[] { return this.tags; } } diff --git a/front/src/Phaser/Components/TextUtils.ts b/front/src/Phaser/Components/TextUtils.ts index db9a97fb..e1ef6d21 100644 --- a/front/src/Phaser/Components/TextUtils.ts +++ b/front/src/Phaser/Components/TextUtils.ts @@ -1,35 +1,35 @@ -import type {ITiledMapObject} from "../Map/ITiledMap"; -import type {GameScene} from "../Game/GameScene"; +import type { ITiledMapObject } from "../Map/ITiledMap"; +import type { GameScene } from "../Game/GameScene"; export class TextUtils { public static createTextFromITiledMapObject(scene: GameScene, object: ITiledMapObject): void { if (object.text === undefined) { - throw new Error('This object has not textual representation.'); + throw new Error("This object has not textual representation."); } const options: { - fontStyle?: string, - fontSize?: string, - fontFamily?: string, - color?: string, - align?: string, + fontStyle?: string; + fontSize?: string; + fontFamily?: string; + color?: string; + align?: string; wordWrap?: { - width: number, - useAdvancedWrap?: boolean - } + width: number; + useAdvancedWrap?: boolean; + }; } = {}; if (object.text.italic) { - options.fontStyle = 'italic'; + options.fontStyle = "italic"; } // Note: there is no support for "strikeout" and "underline" let fontSize: number = 16; if (object.text.pixelsize) { fontSize = object.text.pixelsize; } - options.fontSize = fontSize + 'px'; + options.fontSize = fontSize + "px"; if (object.text.fontfamily) { - options.fontFamily = '"'+object.text.fontfamily+'"'; + options.fontFamily = '"' + object.text.fontfamily + '"'; } - let color = '#000000'; + let color = "#000000"; if (object.text.color !== undefined) { color = object.text.color; } @@ -38,13 +38,12 @@ export class TextUtils { options.wordWrap = { width: object.width, //useAdvancedWrap: true - } + }; } if (object.text.halign !== undefined) { options.align = object.text.halign; } - console.warn(options); const textElem = scene.add.text(object.x, object.y, object.text.text, options); textElem.setAngle(object.rotation); } diff --git a/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts b/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts index 95f00a9e..d2a659ec 100644 --- a/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts +++ b/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts @@ -1,90 +1,124 @@ import LoaderPlugin = Phaser.Loader.LoaderPlugin; -import type {CharacterTexture} from "../../Connexion/LocalUser"; -import {BodyResourceDescriptionInterface, LAYERS, PLAYER_RESOURCES} from "./PlayerTextures"; +import type { CharacterTexture } from "../../Connexion/LocalUser"; +import { BodyResourceDescriptionInterface, LAYERS, PLAYER_RESOURCES } from "./PlayerTextures"; export interface FrameConfig { - frameWidth: number, - frameHeight: number, + frameWidth: number; + frameHeight: number; } export const loadAllLayers = (load: LoaderPlugin): BodyResourceDescriptionInterface[][] => { - const returnArray:BodyResourceDescriptionInterface[][] = []; - LAYERS.forEach(layer => { - const layerArray:BodyResourceDescriptionInterface[] = []; + const returnArray: BodyResourceDescriptionInterface[][] = []; + LAYERS.forEach((layer) => { + const layerArray: BodyResourceDescriptionInterface[] = []; Object.values(layer).forEach((textureDescriptor) => { layerArray.push(textureDescriptor); - load.spritesheet(textureDescriptor.name,textureDescriptor.img,{frameWidth: 32, frameHeight: 32}); - }) - returnArray.push(layerArray) + load.spritesheet(textureDescriptor.name, textureDescriptor.img, { frameWidth: 32, frameHeight: 32 }); + }); + returnArray.push(layerArray); }); return returnArray; -} +}; export const loadAllDefaultModels = (load: LoaderPlugin): BodyResourceDescriptionInterface[] => { const returnArray = Object.values(PLAYER_RESOURCES); returnArray.forEach((playerResource: BodyResourceDescriptionInterface) => { - load.spritesheet(playerResource.name, playerResource.img, {frameWidth: 32, frameHeight: 32}); + load.spritesheet(playerResource.name, playerResource.img, { frameWidth: 32, frameHeight: 32 }); }); return returnArray; -} +}; -export const loadCustomTexture = (loaderPlugin: LoaderPlugin, texture: CharacterTexture) : Promise => { - const name = 'customCharacterTexture'+texture.id; - const playerResourceDescriptor: BodyResourceDescriptionInterface = {name, img: texture.url, level: texture.level} +export const loadCustomTexture = ( + loaderPlugin: LoaderPlugin, + texture: CharacterTexture +): Promise => { + const name = "customCharacterTexture" + texture.id; + const playerResourceDescriptor: BodyResourceDescriptionInterface = { name, img: texture.url, level: texture.level }; return createLoadingPromise(loaderPlugin, playerResourceDescriptor, { frameWidth: 32, - frameHeight: 32 + frameHeight: 32, }); -} +}; -export const lazyLoadPlayerCharacterTextures = (loadPlugin: LoaderPlugin, texturekeys:Array): Promise => { - const promisesList:Promise[] = []; - texturekeys.forEach((textureKey: string|BodyResourceDescriptionInterface) => { +export const lazyLoadPlayerCharacterTextures = ( + loadPlugin: LoaderPlugin, + texturekeys: Array +): Promise => { + const promisesList: Promise[] = []; + texturekeys.forEach((textureKey: string | BodyResourceDescriptionInterface) => { try { //TODO refactor const playerResourceDescriptor = getRessourceDescriptor(textureKey); if (playerResourceDescriptor && !loadPlugin.textureManager.exists(playerResourceDescriptor.name)) { - promisesList.push(createLoadingPromise(loadPlugin, playerResourceDescriptor, { - frameWidth: 32, - frameHeight: 32 - })); + promisesList.push( + createLoadingPromise(loadPlugin, playerResourceDescriptor, { + frameWidth: 32, + frameHeight: 32, + }) + ); } - }catch (err){ + } catch (err) { console.error(err); } }); - let returnPromise:Promise>; + let returnPromise: Promise>; if (promisesList.length > 0) { loadPlugin.start(); returnPromise = Promise.all(promisesList).then(() => texturekeys); } else { returnPromise = Promise.resolve(texturekeys); } - return returnPromise.then((keys) => keys.map((key) => { - return typeof key !== 'string' ? key.name : key; - })) -} -export const getRessourceDescriptor = (textureKey: string|BodyResourceDescriptionInterface): BodyResourceDescriptionInterface => { - if (typeof textureKey !== 'string' && textureKey.img) { + //If the loading fail, we render the default model instead. + return returnPromise + .then((keys) => + keys.map((key) => { + return typeof key !== "string" ? key.name : key; + }) + ) + .catch(() => lazyLoadPlayerCharacterTextures(loadPlugin, ["color_22", "eyes_23"])); +}; + +export const getRessourceDescriptor = ( + textureKey: string | BodyResourceDescriptionInterface +): BodyResourceDescriptionInterface => { + if (typeof textureKey !== "string" && textureKey.img) { return textureKey; } - const textureName:string = typeof textureKey === 'string' ? textureKey : textureKey.name; + const textureName: string = typeof textureKey === "string" ? textureKey : textureKey.name; const playerResource = PLAYER_RESOURCES[textureName]; if (playerResource !== undefined) return playerResource; - for (let i=0; i { - return new Promise((res) => { +export const createLoadingPromise = ( + loadPlugin: LoaderPlugin, + playerResourceDescriptor: BodyResourceDescriptionInterface, + frameConfig: FrameConfig +) => { + return new Promise((res, rej) => { + console.log("count", loadPlugin.listenerCount("loaderror")); if (loadPlugin.textureManager.exists(playerResourceDescriptor.name)) { return res(playerResourceDescriptor); } loadPlugin.spritesheet(playerResourceDescriptor.name, playerResourceDescriptor.img, frameConfig); - loadPlugin.once('filecomplete-spritesheet-' + playerResourceDescriptor.name, () => res(playerResourceDescriptor)); + const errorCallback = (file: { src: string }) => { + if (file.src !== playerResourceDescriptor.img) return; + console.error("failed loading player ressource: ", playerResourceDescriptor); + rej(playerResourceDescriptor); + loadPlugin.off("filecomplete-spritesheet-" + playerResourceDescriptor.name, successCallback); + loadPlugin.off("loaderror", errorCallback); + }; + const successCallback = () => { + loadPlugin.off("loaderror", errorCallback); + res(playerResourceDescriptor); + }; + + loadPlugin.once("filecomplete-spritesheet-" + playerResourceDescriptor.name, successCallback); + loadPlugin.on("loaderror", errorCallback); }); -} +}; diff --git a/front/src/Phaser/Game/DirtyScene.ts b/front/src/Phaser/Game/DirtyScene.ts index 20602cca..13b0fa14 100644 --- a/front/src/Phaser/Game/DirtyScene.ts +++ b/front/src/Phaser/Game/DirtyScene.ts @@ -1,17 +1,17 @@ -import {ResizableScene} from "../Login/ResizableScene"; +import { ResizableScene } from "../Login/ResizableScene"; import GameObject = Phaser.GameObjects.GameObject; import Events = Phaser.Scenes.Events; import AnimationEvents = Phaser.Animations.Events; import StructEvents = Phaser.Structs.Events; -import {SKIP_RENDER_OPTIMIZATIONS} from "../../Enum/EnvironmentVariable"; +import { SKIP_RENDER_OPTIMIZATIONS } from "../../Enum/EnvironmentVariable"; /** * A scene that can track its dirty/pristine state. */ export abstract class DirtyScene extends ResizableScene { private isAlreadyTracking: boolean = false; - protected dirty:boolean = true; - private objectListChanged:boolean = true; + protected dirty: boolean = true; + private objectListChanged: boolean = true; private physicsEnabled: boolean = false; /** @@ -59,7 +59,6 @@ export abstract class DirtyScene extends ResizableScene { this.physicsEnabled = false; } }); - } private trackAnimation(): void { @@ -71,7 +70,7 @@ export abstract class DirtyScene extends ResizableScene { } public markDirty(): void { - this.events.once(Phaser.Scenes.Events.POST_UPDATE, () => this.dirty = true); + this.events.once(Phaser.Scenes.Events.POST_UPDATE, () => (this.dirty = true)); } public onResize(): void { diff --git a/front/src/Phaser/Game/GameManager.ts b/front/src/Phaser/Game/GameManager.ts index a694b32e..3e39de9a 100644 --- a/front/src/Phaser/Game/GameManager.ts +++ b/front/src/Phaser/Game/GameManager.ts @@ -1,26 +1,24 @@ -import {GameScene} from "./GameScene"; -import {connectionManager} from "../../Connexion/ConnectionManager"; -import type {Room} from "../../Connexion/Room"; -import {MenuScene, MenuSceneName} from "../Menu/MenuScene"; -import {LoginSceneName} from "../Login/LoginScene"; -import {SelectCharacterSceneName} from "../Login/SelectCharacterScene"; -import {EnableCameraSceneName} from "../Login/EnableCameraScene"; -import {localUserStore} from "../../Connexion/LocalUserStore"; -import {get} from "svelte/store"; -import {requestedCameraState, requestedMicrophoneState} from "../../Stores/MediaStore"; -import {helpCameraSettingsVisibleStore} from "../../Stores/HelpCameraSettingsStore"; - - +import { GameScene } from "./GameScene"; +import { connectionManager } from "../../Connexion/ConnectionManager"; +import type { Room } from "../../Connexion/Room"; +import { MenuScene, MenuSceneName } from "../Menu/MenuScene"; +import { LoginSceneName } from "../Login/LoginScene"; +import { SelectCharacterSceneName } from "../Login/SelectCharacterScene"; +import { EnableCameraSceneName } from "../Login/EnableCameraScene"; +import { localUserStore } from "../../Connexion/LocalUserStore"; +import { get } from "svelte/store"; +import { requestedCameraState, requestedMicrophoneState } from "../../Stores/MediaStore"; +import { helpCameraSettingsVisibleStore } from "../../Stores/HelpCameraSettingsStore"; /** * This class should be responsible for any scene starting/stopping */ export class GameManager { - private playerName: string|null; - private characterLayers: string[]|null; - private companion: string|null; - private startRoom!:Room; - currentGameSceneName: string|null = null; + private playerName: string | null; + private characterLayers: string[] | null; + private companion: string | null; + private startRoom!: Room; + currentGameSceneName: string | null = null; constructor() { this.playerName = localUserStore.getName(); @@ -51,23 +49,22 @@ export class GameManager { localUserStore.setCharacterLayers(layers); } - getPlayerName(): string|null { + getPlayerName(): string | null { return this.playerName; } getCharacterLayers(): string[] { if (!this.characterLayers) { - throw 'characterLayers are not set'; + throw "characterLayers are not set"; } return this.characterLayers; } - - setCompanion(companion: string|null): void { + setCompanion(companion: string | null): void { this.companion = companion; } - getCompanion(): string|null { + getCompanion(): string | null { return this.companion; } @@ -76,18 +73,21 @@ export class GameManager { const mapDetail = await room.getMapDetail(); const gameIndex = scenePlugin.getIndex(roomID); - if(gameIndex === -1){ - const game : Phaser.Scene = new GameScene(room, mapDetail.mapUrl); + if (gameIndex === -1) { + const game: Phaser.Scene = new GameScene(room, mapDetail.mapUrl); scenePlugin.add(roomID, game, false); } } public goToStartingMap(scenePlugin: Phaser.Scenes.ScenePlugin): void { - console.log('starting '+ (this.currentGameSceneName || this.startRoom.id)) + console.log("starting " + (this.currentGameSceneName || this.startRoom.id)); scenePlugin.start(this.currentGameSceneName || this.startRoom.id); scenePlugin.launch(MenuSceneName); - if(!localUserStore.getHelpCameraSettingsShown() && (!get(requestedMicrophoneState) || !get(requestedCameraState))){ + if ( + !localUserStore.getHelpCameraSettingsShown() && + (!get(requestedMicrophoneState) || !get(requestedCameraState)) + ) { helpCameraSettingsVisibleStore.set(true); localUserStore.setHelpCameraSettingsShown(); } @@ -104,7 +104,7 @@ export class GameManager { * This will close the socket connections and stop the gameScene, but won't remove it. */ leaveGame(scene: Phaser.Scene, targetSceneName: string, sceneClass: Phaser.Scene): void { - if (this.currentGameSceneName === null) throw 'No current scene id set!'; + if (this.currentGameSceneName === null) throw "No current scene id set!"; const gameScene: GameScene = scene.scene.get(this.currentGameSceneName) as GameScene; gameScene.cleanupClosingScene(); scene.scene.stop(this.currentGameSceneName); @@ -123,13 +123,13 @@ export class GameManager { scene.scene.start(this.currentGameSceneName); scene.scene.wake(MenuSceneName); } else { - scene.scene.run(fallbackSceneName) + scene.scene.run(fallbackSceneName); } } public getCurrentGameScene(scene: Phaser.Scene): GameScene { - if (this.currentGameSceneName === null) throw 'No current scene id set!'; - return scene.scene.get(this.currentGameSceneName) as GameScene + if (this.currentGameSceneName === null) throw "No current scene id set!"; + return scene.scene.get(this.currentGameSceneName) as GameScene; } } diff --git a/front/src/Phaser/Game/GameMap.ts b/front/src/Phaser/Game/GameMap.ts index fba410d9..a616cf4a 100644 --- a/front/src/Phaser/Game/GameMap.ts +++ b/front/src/Phaser/Game/GameMap.ts @@ -1,9 +1,13 @@ -import type {ITiledMap, ITiledMapLayer, ITiledMapLayerProperty} from "../Map/ITiledMap"; +import type { ITiledMap, ITiledMapLayer, ITiledMapLayerProperty } from "../Map/ITiledMap"; import { flattenGroupLayersMap } from "../Map/LayersFlattener"; import TilemapLayer = Phaser.Tilemaps.TilemapLayer; import { DEPTH_OVERLAY_INDEX } from "./DepthIndexes"; -export type PropertyChangeCallback = (newValue: string | number | boolean | undefined, oldValue: string | number | boolean | undefined, allProps: Map) => void; +export type PropertyChangeCallback = ( + newValue: string | number | boolean | undefined, + oldValue: string | number | boolean | undefined, + allProps: Map +) => void; /** * A wrapper around a ITiledMap interface to provide additional capabilities. @@ -13,39 +17,56 @@ export class GameMap { private key: number | undefined; private lastProperties = new Map(); private callbacks = new Map>(); + private tileNameMap = new Map(); - private tileSetPropertyMap: { [tile_index: number]: Array } = {} + private tileSetPropertyMap: { [tile_index: number]: Array } = {}; public readonly flatLayers: ITiledMapLayer[]; public readonly phaserLayers: TilemapLayer[] = []; - public exitUrls: Array = [] + public exitUrls: Array = []; - public constructor(private map: ITiledMap, phaserMap: Phaser.Tilemaps.Tilemap, terrains: Array) { + public hasStartTile = false; + + public constructor( + private map: ITiledMap, + phaserMap: Phaser.Tilemaps.Tilemap, + terrains: Array + ) { this.flatLayers = flattenGroupLayersMap(map); let depth = -2; for (const layer of this.flatLayers) { - if(layer.type === 'tilelayer'){ + if (layer.type === "tilelayer") { this.phaserLayers.push(phaserMap.createLayer(layer.name, terrains, 0, 0).setDepth(depth)); } - if (layer.type === 'objectgroup' && layer.name === 'floorLayer') { + if (layer.type === "objectgroup" && layer.name === "floorLayer") { depth = DEPTH_OVERLAY_INDEX; } } for (const tileset of map.tilesets) { - tileset?.tiles?.forEach(tile => { + tileset?.tiles?.forEach((tile) => { if (tile.properties) { - this.tileSetPropertyMap[tileset.firstgid + tile.id] = tile.properties - tile.properties.forEach(prop => { + this.tileSetPropertyMap[tileset.firstgid + tile.id] = tile.properties; + tile.properties.forEach((prop) => { + if (prop.name == "name" && typeof prop.value == "string") { + this.tileNameMap.set(prop.value, tileset.firstgid + tile.id); + } if (prop.name == "exitUrl" && typeof prop.value == "string") { this.exitUrls.push(prop.value); + } else if (prop.name == "start") { + this.hasStartTile = true; } - }) + }); } - }) + }); } } - + public getPropertiesForIndex(index: number): Array { + if (this.tileSetPropertyMap[index]) { + return this.tileSetPropertyMap[index]; + } + return []; + } /** * Sets the position of the current player (in pixels) @@ -89,7 +110,7 @@ export class GameMap { const properties = new Map(); for (const layer of this.flatLayers) { - if (layer.type !== 'tilelayer') { + if (layer.type !== "tilelayer") { continue; } @@ -99,7 +120,7 @@ export class GameMap { if (tiles[key] == 0) { continue; } - tileIndex = tiles[key] + tileIndex = tiles[key]; } // There is a tile in this layer, let's embed the properties @@ -113,24 +134,33 @@ export class GameMap { } if (tileIndex) { - this.tileSetPropertyMap[tileIndex]?.forEach(property => { + this.tileSetPropertyMap[tileIndex]?.forEach((property) => { if (property.value) { - properties.set(property.name, property.value) + properties.set(property.name, property.value); } else if (properties.has(property.name)) { - properties.delete(property.name) + properties.delete(property.name); } - }) + }); } } return properties; } - public getMap(): ITiledMap{ + public getMap(): ITiledMap { return this.map; } - private trigger(propName: string, oldValue: string | number | boolean | undefined, newValue: string | number | boolean | undefined, allProps: Map) { + private getTileProperty(index: number): Array { + return this.tileSetPropertyMap[index]; + } + + private trigger( + propName: string, + oldValue: string | number | boolean | undefined, + newValue: string | number | boolean | undefined, + allProps: Map + ) { const callbacksArray = this.callbacks.get(propName); if (callbacksArray !== undefined) { for (const callback of callbacksArray) { @@ -159,10 +189,53 @@ export class GameMap { return this.phaserLayers.find((layer) => layer.layer.name === layerName); } - public addTerrain(terrain : Phaser.Tilemaps.Tileset): void { + public addTerrain(terrain: Phaser.Tilemaps.Tileset): void { for (const phaserLayer of this.phaserLayers) { phaserLayer.tileset.push(terrain); } } + private putTileInFlatLayer(index: number, x: number, y: number, layer: string): void { + const fLayer = this.findLayer(layer); + if (fLayer == undefined) { + console.error("The layer that you want to change doesn't exist."); + return; + } + if (fLayer.type !== "tilelayer") { + console.error("The layer that you want to change is not a tilelayer. Tile can only be put in tilelayer."); + return; + } + if (typeof fLayer.data === "string") { + console.error("Data of the layer that you want to change is only readable."); + return; + } + fLayer.data[x + y * fLayer.height] = index; + } + + public putTile(tile: string | number, x: number, y: number, layer: string): void { + const phaserLayer = this.findPhaserLayer(layer); + if (phaserLayer) { + const tileIndex = this.getIndexForTileType(tile); + if (tileIndex !== undefined) { + this.putTileInFlatLayer(tileIndex, x, y, layer); + const phaserTile = phaserLayer.putTileAt(tileIndex, x, y); + for (const property of this.getTileProperty(tileIndex)) { + if (property.name === "collides" && property.value === "true") { + phaserTile.setCollision(true); + } + } + } else { + console.error("The tile that you want to place doesn't exist."); + } + } else { + console.error("The layer that you want to change is not a tilelayer. Tile can only be put in tilelayer."); + } + } + + private getIndexForTileType(tile: string | number): number | undefined { + if (typeof tile == "number") { + return tile; + } + return this.tileNameMap.get(tile); + } } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 2cb4a363..530a31e2 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1,4 +1,3 @@ -import { Queue } from 'queue-typescript'; import type { Subscription } from "rxjs"; import { GlobalMessageManager } from "../../Administration/GlobalMessageManager"; import { userMessageManager } from "../../Administration/UserMessageManager"; @@ -12,47 +11,37 @@ import type { OnConnectInterface, PointInterface, PositionInterface, - RoomJoinedMessageInterface + RoomJoinedMessageInterface, } from "../../Connexion/ConnexionModels"; -import { localUserStore } from "../../Connexion/LocalUserStore"; -import { Room } from "../../Connexion/Room"; -import type { RoomConnection } from "../../Connexion/RoomConnection"; -import { worldFullMessageStream } from "../../Connexion/WorldFullMessageStream"; +import { DEBUG_MODE, JITSI_PRIVATE_MODE, MAX_PER_GROUP, POSITION_DELAY } from "../../Enum/EnvironmentVariable"; + +import { Queue } from "queue-typescript"; import { - DEBUG_MODE, - JITSI_PRIVATE_MODE, - MAX_PER_GROUP, - POSITION_DELAY -} from "../../Enum/EnvironmentVariable"; -import { TextureError } from "../../Exception/TextureError"; -import type { UserMovedMessage } from "../../Messages/generated/messages_pb"; -import { ProtobufClientUtils } from "../../Network/ProtobufClientUtils"; -import { peerStore } from "../../Stores/PeerStore"; -import { touchScreenManager } from "../../Touch/TouchScreenManager"; -import { urlManager } from "../../Url/UrlManager"; -import { audioManager } from "../../WebRtc/AudioManager"; -import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager"; -import { HtmlUtils } from "../../WebRtc/HtmlUtils"; -import { jitsiFactory } from "../../WebRtc/JitsiFactory"; -import { - AUDIO_LOOP_PROPERTY, AUDIO_VOLUME_PROPERTY, CenterListener, + AUDIO_LOOP_PROPERTY, + AUDIO_VOLUME_PROPERTY, + Box, JITSI_MESSAGE_PROPERTIES, layoutManager, - LayoutMode, ON_ACTION_TRIGGER_BUTTON, TRIGGER_JITSI_PROPERTIES, TRIGGER_WEBSITE_PROPERTIES, - WEBSITE_MESSAGE_PROPERTIES + WEBSITE_MESSAGE_PROPERTIES, } from "../../WebRtc/LayoutManager"; +import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager"; +import type { UserMovedMessage } from "../../Messages/generated/messages_pb"; +import { ProtobufClientUtils } from "../../Network/ProtobufClientUtils"; +import type { RoomConnection } from "../../Connexion/RoomConnection"; +import { Room } from "../../Connexion/Room"; +import { jitsiFactory } from "../../WebRtc/JitsiFactory"; +import { urlManager } from "../../Url/UrlManager"; +import { audioManager } from "../../WebRtc/AudioManager"; +import { TextureError } from "../../Exception/TextureError"; +import { localUserStore } from "../../Connexion/LocalUserStore"; +import { HtmlUtils } from "../../WebRtc/HtmlUtils"; import { mediaManager } from "../../WebRtc/MediaManager"; -import { SimplePeer, UserSimplePeerInterface } from "../../WebRtc/SimplePeer"; -import { lazyLoadCompanionResource } from "../Companion/CompanionTexturesLoadingManager"; -import { ChatModeIcon } from "../Components/ChatModeIcon"; +import { SimplePeer } from "../../WebRtc/SimplePeer"; import { addLoader } from "../Components/Loader"; -import { joystickBaseImg, joystickBaseKey, joystickThumbImg, joystickThumbKey } from "../Components/MobileJoystick"; import { OpenChatIcon, openChatIconName } from "../Components/OpenChatIcon"; -import { PresentationModeIcon } from "../Components/PresentationModeIcon"; -import { TextUtils } from "../Components/TextUtils"; import { lazyLoadPlayerCharacterTextures, loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager"; import { RemotePlayer } from "../Entity/RemotePlayer"; import type { ActionableItem } from "../Items/ActionableItem"; @@ -63,21 +52,15 @@ import type { ITiledMapLayer, ITiledMapLayerProperty, ITiledMapObject, - ITiledMapTileLayer, - ITiledTileSet + ITiledTileSet, } from "../Map/ITiledMap"; -import { MenuScene, MenuSceneName } from '../Menu/MenuScene'; +import { MenuScene, MenuSceneName } from "../Menu/MenuScene"; import { PlayerAnimationDirections } from "../Player/Animation"; import { hasMovedEventName, Player, requestEmoteEventName } from "../Player/Player"; import { ErrorSceneName } from "../Reconnecting/ErrorScene"; import { ReconnectingSceneName } from "../Reconnecting/ReconnectingScene"; -import { waScaleManager } from "../Services/WaScaleManager"; -import { PinchManager } from "../UserInput/PinchManager"; import { UserInputManager } from "../UserInput/UserInputManager"; import type { AddPlayerInterface } from "./AddPlayerInterface"; -import { DEPTH_OVERLAY_INDEX } from "./DepthIndexes"; -import { DirtyScene } from "./DirtyScene"; -import { EmoteManager } from "./EmoteManager"; import { gameManager } from "./GameManager"; import { GameMap } from "./GameMap"; import { PlayerMovement } from "./PlayerMovement"; @@ -88,53 +71,64 @@ import CanvasTexture = Phaser.Textures.CanvasTexture; import GameObject = Phaser.GameObjects.GameObject; import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR; import DOMElement = Phaser.GameObjects.DOMElement; -import EVENT_TYPE = Phaser.Scenes.Events +import { worldFullMessageStream } from "../../Connexion/WorldFullMessageStream"; +import { lazyLoadCompanionResource } from "../Companion/CompanionTexturesLoadingManager"; +import { DirtyScene } from "./DirtyScene"; +import { TextUtils } from "../Components/TextUtils"; +import { touchScreenManager } from "../../Touch/TouchScreenManager"; +import { PinchManager } from "../UserInput/PinchManager"; +import { joystickBaseImg, joystickBaseKey, joystickThumbImg, joystickThumbKey } from "../Components/MobileJoystick"; +import { waScaleManager } from "../Services/WaScaleManager"; +import { EmoteManager } from "./EmoteManager"; +import EVENT_TYPE = Phaser.Scenes.Events; import RenderTexture = Phaser.GameObjects.RenderTexture; import Tilemap = Phaser.Tilemaps.Tilemap; -import type { HasPlayerMovedEvent } from '../../Api/Events/HasPlayerMovedEvent'; +import type { HasPlayerMovedEvent } from "../../Api/Events/HasPlayerMovedEvent"; import AnimatedTiles from "phaser-animated-tiles"; +import { StartPositionCalculator } from "./StartPositionCalculator"; import { soundManager } from "./SoundManager"; -import { removeTriggerMessageEvent, sendMessageTriggeredEvent, triggerMessageEvent } from '../../Api/Events/ui/TriggerMessageEventHandler'; +import { peerStore, screenSharingPeerStore } from "../../Stores/PeerStore"; +import { videoFocusStore } from "../../Stores/VideoFocusStore"; +import { biggestAvailableAreaStore } from "../../Stores/BiggestAvailableAreaStore"; +import { isMessageReferenceEvent, isTriggerMessageEvent } from "../../Api/Events/ui/TriggerMessageEvent"; export interface GameSceneInitInterface { - initPosition: PointInterface | null, - reconnecting: boolean + initPosition: PointInterface | null; + reconnecting: boolean; } interface InitUserPositionEventInterface { - type: 'InitUserPositionEvent' - event: MessageUserPositionInterface[] + type: "InitUserPositionEvent"; + event: MessageUserPositionInterface[]; } interface AddPlayerEventInterface { - type: 'AddPlayerEvent' - event: AddPlayerInterface + type: "AddPlayerEvent"; + event: AddPlayerInterface; } interface RemovePlayerEventInterface { - type: 'RemovePlayerEvent' - userId: number + type: "RemovePlayerEvent"; + userId: number; } interface UserMovedEventInterface { - type: 'UserMovedEvent' - event: MessageUserMovedInterface + type: "UserMovedEvent"; + event: MessageUserMovedInterface; } interface GroupCreatedUpdatedEventInterface { - type: 'GroupCreatedUpdatedEvent' - event: GroupCreatedUpdatedMessageInterface + type: "GroupCreatedUpdatedEvent"; + event: GroupCreatedUpdatedMessageInterface; } interface DeleteGroupEventInterface { - type: 'DeleteGroupEvent' - groupId: number + type: "DeleteGroupEvent"; + groupId: number; } -const defaultStartLayerName = 'start'; - -export class GameScene extends DirtyScene implements CenterListener { +export class GameScene extends DirtyScene { Terrains: Array; CurrentPlayer!: Player; MapPlayers!: Phaser.Physics.Arcade.Group; @@ -144,23 +138,38 @@ export class GameScene extends DirtyScene implements CenterListener { mapFile!: ITiledMap; animatedTiles!: AnimatedTiles; groups: Map; - startX!: number; - startY!: number; circleTexture!: CanvasTexture; circleRedTexture!: CanvasTexture; - pendingEvents: Queue = new Queue(); + pendingEvents: Queue< + | InitUserPositionEventInterface + | AddPlayerEventInterface + | RemovePlayerEventInterface + | UserMovedEventInterface + | GroupCreatedUpdatedEventInterface + | DeleteGroupEventInterface + > = new Queue< + | InitUserPositionEventInterface + | AddPlayerEventInterface + | RemovePlayerEventInterface + | UserMovedEventInterface + | GroupCreatedUpdatedEventInterface + | DeleteGroupEventInterface + >(); private initPosition: PositionInterface | null = null; private playersPositionInterpolator = new PlayersPositionInterpolator(); public connection: RoomConnection | undefined; private simplePeer!: SimplePeer; private GlobalMessageManager!: GlobalMessageManager; private connectionAnswerPromise: Promise; - private connectionAnswerPromiseResolve!: (value: RoomJoinedMessageInterface | PromiseLike) => void; + private connectionAnswerPromiseResolve!: ( + value: RoomJoinedMessageInterface | PromiseLike + ) => void; // A promise that will resolve when the "create" method is called (signaling loading is ended) private createPromise: Promise; private createPromiseResolve!: (value?: void | PromiseLike) => void; private iframeSubscriptionList!: Array; private peerStoreUnsubscribe!: () => void; + private biggestAvailableAreaStoreUnsubscribe!: () => void; MapUrlFile: string; RoomId: string; instance: string; @@ -168,21 +177,18 @@ export class GameScene extends DirtyScene implements CenterListener { currentTick!: number; lastSentTick!: number; // The last tick at which a position was sent. lastMoveEventSent: HasPlayerMovedEvent = { - direction: '', + direction: "", moving: false, x: -1000, - y: -1000 - } + y: -1000, + }; - private presentationModeSprite!: Sprite; - private chatModeSprite!: Sprite; private gameMap!: GameMap; private actionableItems: Map = new Map(); // The item that can be selected by pressing the space key. private outlinedItem: ActionableItem | null = null; public userInputManager!: UserInputManager; private isReconnecting: boolean | undefined = undefined; - private startLayerName!: string | null; private openChatIcon!: OpenChatIcon; private playerName!: string; private characterLayers!: string[]; @@ -193,16 +199,17 @@ export class GameScene extends DirtyScene implements CenterListener { private pinchManager: PinchManager | undefined; private mapTransitioning: boolean = false; //used to prevent transitions happenning at the same time. private emoteManager!: EmoteManager; + private preloading: boolean = true; + startPositionCalculator!: StartPositionCalculator; constructor(private room: Room, MapUrlFile: string, customKey?: string | undefined) { super({ - key: customKey ?? room.id + key: customKey ?? room.id, }); this.Terrains = []; this.groups = new Map(); this.instance = room.getInstance(); - this.MapUrlFile = MapUrlFile; this.RoomId = room.id; @@ -224,50 +231,70 @@ export class GameScene extends DirtyScene implements CenterListener { } } - this.load.image(openChatIconName, 'resources/objects/talk.png'); + this.load.image(openChatIconName, "resources/objects/talk.png"); if (touchScreenManager.supportTouchScreen) { this.load.image(joystickBaseKey, joystickBaseImg); this.load.image(joystickThumbKey, joystickThumbImg); } - this.load.audio('audio-webrtc-in', '/resources/objects/webrtc-in.mp3'); - this.load.audio('audio-webrtc-out', '/resources/objects/webrtc-out.mp3'); + this.load.audio("audio-webrtc-in", "/resources/objects/webrtc-in.mp3"); + this.load.audio("audio-webrtc-out", "/resources/objects/webrtc-out.mp3"); //this.load.audio('audio-report-message', '/resources/objects/report-message.mp3'); this.sound.pauseOnBlur = false; this.load.on(FILE_LOAD_ERROR, (file: { src: string }) => { // If we happen to be in HTTP and we are trying to load a URL in HTTPS only... (this happens only in dev environments) - if (window.location.protocol === 'http:' && file.src === this.MapUrlFile && file.src.startsWith('http:') && this.originalMapUrl === undefined) { + if ( + window.location.protocol === "http:" && + file.src === this.MapUrlFile && + file.src.startsWith("http:") && + this.originalMapUrl === undefined + ) { this.originalMapUrl = this.MapUrlFile; - this.MapUrlFile = this.MapUrlFile.replace('http://', 'https://'); + this.MapUrlFile = this.MapUrlFile.replace("http://", "https://"); this.load.tilemapTiledJSON(this.MapUrlFile, this.MapUrlFile); - this.load.on('filecomplete-tilemapJSON-' + this.MapUrlFile, (key: string, type: string, data: unknown) => { - this.onMapLoad(data); - }); + this.load.on( + "filecomplete-tilemapJSON-" + this.MapUrlFile, + (key: string, type: string, data: unknown) => { + this.onMapLoad(data); + } + ); return; } // 127.0.0.1, localhost and *.localhost are considered secure, even on HTTP. // So if we are in https, we can still try to load a HTTP local resource (can be useful for testing purposes) // See https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure const url = new URL(file.src); - const host = url.host.split(':')[0]; - if (window.location.protocol === 'https:' && file.src === this.MapUrlFile && (host === '127.0.0.1' || host === 'localhost' || host.endsWith('.localhost')) && this.originalMapUrl === undefined) { + const host = url.host.split(":")[0]; + if ( + window.location.protocol === "https:" && + file.src === this.MapUrlFile && + (host === "127.0.0.1" || host === "localhost" || host.endsWith(".localhost")) && + this.originalMapUrl === undefined + ) { this.originalMapUrl = this.MapUrlFile; - this.MapUrlFile = this.MapUrlFile.replace('https://', 'http://'); + this.MapUrlFile = this.MapUrlFile.replace("https://", "http://"); this.load.tilemapTiledJSON(this.MapUrlFile, this.MapUrlFile); - this.load.on('filecomplete-tilemapJSON-' + this.MapUrlFile, (key: string, type: string, data: unknown) => { - this.onMapLoad(data); - }); + this.load.on( + "filecomplete-tilemapJSON-" + this.MapUrlFile, + (key: string, type: string, data: unknown) => { + this.onMapLoad(data); + } + ); return; } - this.scene.start(ErrorSceneName, { - title: 'Network error', - subTitle: 'An error occurred while loading resource:', - message: this.originalMapUrl ?? file.src - }); + //once preloading is over, we don't want loading errors to crash the game, so we need to disable this behavior after preloading. + console.error("Error when loading: ", file); + if (this.preloading) { + this.scene.start(ErrorSceneName, { + title: "Network error", + subTitle: "An error occurred while loading resource:", + message: this.originalMapUrl ?? file.src, + }); + } }); - this.load.scenePlugin('AnimatedTiles', AnimatedTiles, 'animatedTiles', 'animatedTiles'); - this.load.on('filecomplete-tilemapJSON-' + this.MapUrlFile, (key: string, type: string, data: unknown) => { + this.load.scenePlugin("AnimatedTiles", AnimatedTiles, "animatedTiles", "animatedTiles"); + this.load.on("filecomplete-tilemapJSON-" + this.MapUrlFile, (key: string, type: string, data: unknown) => { this.onMapLoad(data); }); //TODO strategy to add access token @@ -279,14 +306,13 @@ export class GameScene extends DirtyScene implements CenterListener { this.onMapLoad(data); } - this.load.spritesheet('layout_modes', 'resources/objects/layout_modes.png', { frameWidth: 32, frameHeight: 32 }); - this.load.bitmapFont('main_font', 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml'); + this.load.bitmapFont("main_font", "resources/fonts/arcade.png", "resources/fonts/arcade.xml"); //eslint-disable-next-line @typescript-eslint/no-explicit-any (this.load as any).rexWebFont({ custom: { - families: ['Press Start 2P'], - urls: ['/resources/fonts/fonts.css'], - testString: 'abcdefg' + families: ["Press Start 2P"], + urls: ["/resources/fonts/fonts.css"], + testString: "abcdefg", }, }); @@ -300,21 +326,21 @@ export class GameScene extends DirtyScene implements CenterListener { // Triggered when the map is loaded // Load tiles attached to the map recursively this.mapFile = data.data; - const url = this.MapUrlFile.substr(0, this.MapUrlFile.lastIndexOf('/')); + const url = this.MapUrlFile.substr(0, this.MapUrlFile.lastIndexOf("/")); this.mapFile.tilesets.forEach((tileset) => { - if (typeof tileset.name === 'undefined' || typeof tileset.image === 'undefined') { - console.warn("Don't know how to handle tileset ", tileset) + if (typeof tileset.name === "undefined" || typeof tileset.image === "undefined") { + console.warn("Don't know how to handle tileset ", tileset); return; } //TODO strategy to add access token this.load.image(`${url}/${tileset.image}`, `${url}/${tileset.image}`); - }) + }); // Scan the object layers for objects to load and load them. const objects = new Map(); for (const layer of this.mapFile.layers) { - if (layer.type === 'objectgroup') { + if (layer.type === "objectgroup") { for (const object of layer.objects) { let objectsOfType: ITiledMapObject[] | undefined; if (!objects.has(object.type)) { @@ -322,7 +348,7 @@ export class GameScene extends DirtyScene implements CenterListener { } else { objectsOfType = objects.get(object.type); if (objectsOfType === undefined) { - throw new Error('Unexpected object type not found'); + throw new Error("Unexpected object type not found"); } } objectsOfType.push(object); @@ -337,8 +363,8 @@ export class GameScene extends DirtyScene implements CenterListener { let itemFactory: ItemFactoryInterface; switch (itemType) { - case 'computer': { - const module = await import('../Items/Computer/computer'); + case "computer": { + const module = await import("../Items/Computer/computer"); itemFactory = module.default; break; } @@ -350,7 +376,7 @@ export class GameScene extends DirtyScene implements CenterListener { itemFactory.preload(this.load); this.load.start(); // Let's manually start the loader because the import might be over AFTER the loading ends. - this.load.on('complete', () => { + this.load.on("complete", () => { // FIXME: the factory might fail because the resources might not be loaded yet... // We would need to add a loader ended event in addition to the createPromise this.createPromise.then(async () => { @@ -390,21 +416,23 @@ export class GameScene extends DirtyScene implements CenterListener { //hook create scene create(): void { + this.preloading = false; this.trackDirtyAnims(); gameManager.gameSceneIsCreated(this); urlManager.pushRoomIdToUrl(this.room); - this.startLayerName = urlManager.getStartLayerNameFromUrl(); if (touchScreenManager.supportTouchScreen) { this.pinchManager = new PinchManager(this); } - this.messageSubscription = worldFullMessageStream.stream.subscribe((message) => this.showWorldFullError(message)) + this.messageSubscription = worldFullMessageStream.stream.subscribe((message) => + this.showWorldFullError(message) + ); const playerName = gameManager.getPlayerName(); if (!playerName) { - throw 'playerName is not set'; + throw "playerName is not set"; } this.playerName = playerName; this.characterLayers = gameManager.getCharacterLayers(); @@ -412,9 +440,18 @@ export class GameScene extends DirtyScene implements CenterListener { //initalise map this.Map = this.add.tilemap(this.MapUrlFile); - const mapDirUrl = this.MapUrlFile.substr(0, this.MapUrlFile.lastIndexOf('/')); + const mapDirUrl = this.MapUrlFile.substr(0, this.MapUrlFile.lastIndexOf("/")); this.mapFile.tilesets.forEach((tileset: ITiledTileSet) => { - this.Terrains.push(this.Map.addTilesetImage(tileset.name, `${mapDirUrl}/${tileset.image}`, tileset.tilewidth, tileset.tileheight, tileset.margin, tileset.spacing/*, tileset.firstgid*/)); + this.Terrains.push( + this.Map.addTilesetImage( + tileset.name, + `${mapDirUrl}/${tileset.image}`, + tileset.tilewidth, + tileset.tileheight, + tileset.margin, + tileset.spacing /*, tileset.firstgid*/ + ) + ); }); //permit to set bound collision @@ -423,8 +460,7 @@ export class GameScene extends DirtyScene implements CenterListener { //add layer on map this.gameMap = new GameMap(this.mapFile, this.Map, this.Terrains); for (const layer of this.gameMap.flatLayers) { - if (layer.type === 'tilelayer') { - + if (layer.type === "tilelayer") { const exitSceneUrl = this.getExitSceneUrl(layer); if (exitSceneUrl !== undefined) { this.loadNextGame(exitSceneUrl); @@ -434,7 +470,7 @@ export class GameScene extends DirtyScene implements CenterListener { this.loadNextGame(exitUrl); } } - if (layer.type === 'objectgroup') { + if (layer.type === "objectgroup") { for (const object of layer.objects) { if (object.text) { TextUtils.createTextFromITiledMapObject(this, object); @@ -443,11 +479,16 @@ export class GameScene extends DirtyScene implements CenterListener { } } - this.gameMap.exitUrls.forEach(exitUrl => { - this.loadNextGame(exitUrl) - }) + this.gameMap.exitUrls.forEach((exitUrl) => { + this.loadNextGame(exitUrl); + }); - this.initStartXAndStartY(); + this.startPositionCalculator = new StartPositionCalculator( + this.gameMap, + this.mapFile, + this.initPosition, + urlManager.getStartLayerNameFromUrl() + ); //add entities this.Objects = new Array(); @@ -455,13 +496,12 @@ export class GameScene extends DirtyScene implements CenterListener { //initialise list of other player this.MapPlayers = this.physics.add.group({ immovable: true }); - //create input to move this.userInputManager = new UserInputManager(this); mediaManager.setUserInputManager(this.userInputManager); if (localUserStore.getFullscreen()) { - document.querySelector('body')?.requestFullscreen(); + document.querySelector("body")?.requestFullscreen(); } //notify game manager can to create currentUser in map @@ -471,7 +511,7 @@ export class GameScene extends DirtyScene implements CenterListener { this.initCamera(); this.animatedTiles.init(this.Map); - this.events.on('tileanimationupdate', () => this.dirty = true); + this.events.on("tileanimationupdate", () => (this.dirty = true)); this.initCirclesCanvas(); @@ -499,26 +539,18 @@ export class GameScene extends DirtyScene implements CenterListener { this.outlinedItem?.activate(); }); - this.presentationModeSprite = new PresentationModeIcon(this, 36, this.game.renderer.height - 2); - this.presentationModeSprite.on('pointerup', this.switchLayoutMode.bind(this)); - this.chatModeSprite = new ChatModeIcon(this, 70, this.game.renderer.height - 2); - this.chatModeSprite.on('pointerup', this.switchLayoutMode.bind(this)); - this.openChatIcon = new OpenChatIcon(this, 2, this.game.renderer.height - 2) - - // FIXME: change this to use the UserInputManager class for input - // FIXME: Comment this feature because when user write M key in report input, the layout change. - /*this.input.keyboard.on('keyup-M', () => { - this.switchLayoutMode(); - });*/ + this.openChatIcon = new OpenChatIcon(this, 2, this.game.renderer.height - 2); this.reposition(); // From now, this game scene will be notified of reposition events - layoutManager.setListener(this); + this.biggestAvailableAreaStoreUnsubscribe = biggestAvailableAreaStore.subscribe((box) => + this.updateCameraOffset(box) + ); + this.triggerOnMapLayerPropertyChange(); this.listenToIframeEvents(); - if (!this.room.isDisconnected()) { this.connect(); } @@ -529,12 +561,12 @@ export class GameScene extends DirtyScene implements CenterListener { this.peerStoreUnsubscribe = peerStore.subscribe((peers) => { const newPeerNumber = peers.size; if (newPeerNumber > oldPeerNumber) { - this.sound.play('audio-webrtc-in', { - volume: 0.2 + this.sound.play("audio-webrtc-in", { + volume: 0.2, }); } else if (newPeerNumber < oldPeerNumber) { - this.sound.play('audio-webrtc-out', { - volume: 0.2 + this.sound.play("audio-webrtc-out", { + volume: 0.2, }); } oldPeerNumber = newPeerNumber; @@ -547,223 +579,235 @@ export class GameScene extends DirtyScene implements CenterListener { private connect(): void { const camera = this.cameras.main; - connectionManager.connectToRoomSocket( - this.RoomId, - this.playerName, - this.characterLayers, - { - x: this.startX, - y: this.startY - }, - { - left: camera.scrollX, - top: camera.scrollY, - right: camera.scrollX + camera.width, - bottom: camera.scrollY + camera.height, - }, - this.companion - ).then((onConnect: OnConnectInterface) => { - this.connection = onConnect.connection; + connectionManager + .connectToRoomSocket( + this.RoomId, + this.playerName, + this.characterLayers, + { + ...this.startPositionCalculator.startPosition, + }, + { + left: camera.scrollX, + top: camera.scrollY, + right: camera.scrollX + camera.width, + bottom: camera.scrollY + camera.height, + }, + this.companion + ) + .then((onConnect: OnConnectInterface) => { + this.connection = onConnect.connection; - this.connection.onUserJoins((message: MessageUserJoined) => { - const userMessage: AddPlayerInterface = { - userId: message.userId, - characterLayers: message.characterLayers, - name: message.name, - position: message.position, - visitCardUrl: message.visitCardUrl, - companion: message.companion - } - this.addPlayer(userMessage); - }); + this.connection.onUserJoins((message: MessageUserJoined) => { + const userMessage: AddPlayerInterface = { + userId: message.userId, + characterLayers: message.characterLayers, + name: message.name, + position: message.position, + visitCardUrl: message.visitCardUrl, + companion: message.companion, + }; + this.addPlayer(userMessage); + }); - this.connection.onUserMoved((message: UserMovedMessage) => { - const position = message.getPosition(); - if (position === undefined) { - throw new Error('Position missing from UserMovedMessage'); - } + this.connection.onUserMoved((message: UserMovedMessage) => { + const position = message.getPosition(); + if (position === undefined) { + throw new Error("Position missing from UserMovedMessage"); + } - const messageUserMoved: MessageUserMovedInterface = { - userId: message.getUserid(), - position: ProtobufClientUtils.toPointInterface(position) - } + const messageUserMoved: MessageUserMovedInterface = { + userId: message.getUserid(), + position: ProtobufClientUtils.toPointInterface(position), + }; - this.updatePlayerPosition(messageUserMoved); - }); + this.updatePlayerPosition(messageUserMoved); + }); - this.connection.onUserLeft((userId: number) => { - this.removePlayer(userId); - }); + this.connection.onUserLeft((userId: number) => { + this.removePlayer(userId); + }); - this.connection.onGroupUpdatedOrCreated((groupPositionMessage: GroupCreatedUpdatedMessageInterface) => { - this.shareGroupPosition(groupPositionMessage); - }) + this.connection.onGroupUpdatedOrCreated((groupPositionMessage: GroupCreatedUpdatedMessageInterface) => { + this.shareGroupPosition(groupPositionMessage); + }); - this.connection.onGroupDeleted((groupId: number) => { - try { - this.deleteGroup(groupId); - } catch (e) { - console.error(e); - } - }) + this.connection.onGroupDeleted((groupId: number) => { + try { + this.deleteGroup(groupId); + } catch (e) { + console.error(e); + } + }); - this.connection.onServerDisconnected(() => { - console.log('Player disconnected from server. Reloading scene.'); - this.cleanupClosingScene(); + this.connection.onServerDisconnected(() => { + console.log("Player disconnected from server. Reloading scene."); + this.cleanupClosingScene(); - const gameSceneKey = 'somekey' + Math.round(Math.random() * 10000); - const game: Phaser.Scene = new GameScene(this.room, this.MapUrlFile, gameSceneKey); - this.scene.add(gameSceneKey, game, true, - { + const gameSceneKey = "somekey" + Math.round(Math.random() * 10000); + const game: Phaser.Scene = new GameScene(this.room, this.MapUrlFile, gameSceneKey); + this.scene.add(gameSceneKey, game, true, { initPosition: { x: this.CurrentPlayer.x, - y: this.CurrentPlayer.y + y: this.CurrentPlayer.y, }, - reconnecting: true + reconnecting: true, }); - this.scene.stop(this.scene.key); - this.scene.remove(this.scene.key); - }) + this.scene.stop(this.scene.key); + this.scene.remove(this.scene.key); + }); - this.connection.onActionableEvent((message => { - const item = this.actionableItems.get(message.itemId); - if (item === undefined) { - console.warn('Received an event about object "' + message.itemId + '" but cannot find this item on the map.'); - return; - } - item.fire(message.event, message.state, message.parameters); - })); - - /** - * Triggered when we receive the JWT token to connect to Jitsi - */ - this.connection.onStartJitsiRoom((jwt, room) => { - this.startJitsi(room, jwt); - }); - - // When connection is performed, let's connect SimplePeer - this.simplePeer = new SimplePeer(this.connection, !this.room.isPublic, this.playerName); - peerStore.connectToSimplePeer(this.simplePeer); - this.GlobalMessageManager = new GlobalMessageManager(this.connection); - userMessageManager.setReceiveBanListener(this.bannedUser.bind(this)); - - const self = this; - this.simplePeer.registerPeerConnectionListener({ - onConnect(user: UserSimplePeerInterface) { - self.presentationModeSprite.setVisible(true); - self.chatModeSprite.setVisible(true); - self.openChatIcon.setVisible(true); - audioManager.decreaseVolume(); - }, - onDisconnect(userId: number) { - if (self.simplePeer.getNbConnections() === 0) { - self.presentationModeSprite.setVisible(false); - self.chatModeSprite.setVisible(false); - self.openChatIcon.setVisible(false); - audioManager.restoreVolume(); + this.connection.onActionableEvent((message) => { + const item = this.actionableItems.get(message.itemId); + if (item === undefined) { + console.warn( + 'Received an event about object "' + + message.itemId + + '" but cannot find this item on the map.' + ); + return; } - } - }) + item.fire(message.event, message.state, message.parameters); + }); - //listen event to share position of user - this.CurrentPlayer.on(hasMovedEventName, this.pushPlayerPosition.bind(this)) - this.CurrentPlayer.on(hasMovedEventName, this.outlineItem.bind(this)) - this.CurrentPlayer.on(hasMovedEventName, (event: HasPlayerMovedEvent) => { - this.gameMap.setPosition(event.x, event.y); - }) + /** + * Triggered when we receive the JWT token to connect to Jitsi + */ + this.connection.onStartJitsiRoom((jwt, room) => { + this.startJitsi(room, jwt); + }); - //this.initUsersPosition(roomJoinedMessage.users); - this.connectionAnswerPromiseResolve(onConnect.room); - // Analyze tags to find if we are admin. If yes, show console. + // When connection is performed, let's connect SimplePeer + this.simplePeer = new SimplePeer(this.connection, !this.room.isPublic, this.playerName); + peerStore.connectToSimplePeer(this.simplePeer); + screenSharingPeerStore.connectToSimplePeer(this.simplePeer); + videoFocusStore.connectToSimplePeer(this.simplePeer); + this.GlobalMessageManager = new GlobalMessageManager(this.connection); + userMessageManager.setReceiveBanListener(this.bannedUser.bind(this)); + const self = this; + this.simplePeer.registerPeerConnectionListener({ + onConnect(peer) { + self.openChatIcon.setVisible(true); + audioManager.decreaseVolume(); + }, + onDisconnect(userId: number) { + if (self.simplePeer.getNbConnections() === 0) { + self.openChatIcon.setVisible(false); + audioManager.restoreVolume(); + } + }, + }); - this.scene.wake(); - this.scene.stop(ReconnectingSceneName); + //listen event to share position of user + this.CurrentPlayer.on(hasMovedEventName, this.pushPlayerPosition.bind(this)); + this.CurrentPlayer.on(hasMovedEventName, this.outlineItem.bind(this)); + this.CurrentPlayer.on(hasMovedEventName, (event: HasPlayerMovedEvent) => { + this.gameMap.setPosition(event.x, event.y); + }); - //init user position and play trigger to check layers properties - this.gameMap.setPosition(this.CurrentPlayer.x, this.CurrentPlayer.y); - }); + //this.initUsersPosition(roomJoinedMessage.users); + this.connectionAnswerPromiseResolve(onConnect.room); + // Analyze tags to find if we are admin. If yes, show console. + + this.scene.wake(); + this.scene.stop(ReconnectingSceneName); + + //init user position and play trigger to check layers properties + this.gameMap.setPosition(this.CurrentPlayer.x, this.CurrentPlayer.y); + }); } //todo: into dedicated classes private initCirclesCanvas(): void { // Let's generate the circle for the group delimiter - let circleElement = Object.values(this.textures.list).find((object: Texture) => object.key === 'circleSprite-white'); + let circleElement = Object.values(this.textures.list).find( + (object: Texture) => object.key === "circleSprite-white" + ); if (circleElement) { - this.textures.remove('circleSprite-white'); + this.textures.remove("circleSprite-white"); } - circleElement = Object.values(this.textures.list).find((object: Texture) => object.key === 'circleSprite-red'); + circleElement = Object.values(this.textures.list).find((object: Texture) => object.key === "circleSprite-red"); if (circleElement) { - this.textures.remove('circleSprite-red'); + this.textures.remove("circleSprite-red"); } //create white circle canvas use to create sprite - this.circleTexture = this.textures.createCanvas('circleSprite-white', 96, 96); + this.circleTexture = this.textures.createCanvas("circleSprite-white", 96, 96); const context = this.circleTexture.context; context.beginPath(); context.arc(48, 48, 48, 0, 2 * Math.PI, false); // context.lineWidth = 5; - context.strokeStyle = '#ffffff'; + context.strokeStyle = "#ffffff"; context.stroke(); this.circleTexture.refresh(); //create red circle canvas use to create sprite - this.circleRedTexture = this.textures.createCanvas('circleSprite-red', 96, 96); + this.circleRedTexture = this.textures.createCanvas("circleSprite-red", 96, 96); const contextRed = this.circleRedTexture.context; contextRed.beginPath(); contextRed.arc(48, 48, 48, 0, 2 * Math.PI, false); //context.lineWidth = 5; - contextRed.strokeStyle = '#ff0000'; + contextRed.strokeStyle = "#ff0000"; contextRed.stroke(); this.circleRedTexture.refresh(); } - private safeParseJSONstring(jsonString: string | undefined, propertyName: string) { try { return jsonString ? JSON.parse(jsonString) : {}; } catch (e) { console.warn('Invalid JSON found in property "' + propertyName + '" of the map:' + jsonString, e); - return {} + return {}; } } private triggerOnMapLayerPropertyChange() { - this.gameMap.onPropertyChange('exitSceneUrl', (newValue, oldValue) => { + this.gameMap.onPropertyChange("exitSceneUrl", (newValue, oldValue) => { if (newValue) this.onMapExit(newValue as string); }); - this.gameMap.onPropertyChange('exitUrl', (newValue, oldValue) => { + this.gameMap.onPropertyChange("exitUrl", (newValue, oldValue) => { if (newValue) this.onMapExit(newValue as string); }); - this.gameMap.onPropertyChange('openWebsite', (newValue, oldValue, allProps) => { + this.gameMap.onPropertyChange("openWebsite", (newValue, oldValue, allProps) => { if (newValue === undefined) { - layoutManager.removeActionButton('openWebsite', this.userInputManager); + layoutManager.removeActionButton("openWebsite", this.userInputManager); coWebsiteManager.closeCoWebsite(); } else { const openWebsiteFunction = () => { - coWebsiteManager.loadCoWebsite(newValue as string, this.MapUrlFile, allProps.get('openWebsiteAllowApi') as boolean | undefined, allProps.get('openWebsitePolicy') as string | undefined); - layoutManager.removeActionButton('openWebsite', this.userInputManager); + coWebsiteManager.loadCoWebsite( + newValue as string, + this.MapUrlFile, + allProps.get("openWebsiteAllowApi") as boolean | undefined, + allProps.get("openWebsitePolicy") as string | undefined + ); + layoutManager.removeActionButton("openWebsite", this.userInputManager); }; const openWebsiteTriggerValue = allProps.get(TRIGGER_WEBSITE_PROPERTIES); if (openWebsiteTriggerValue && openWebsiteTriggerValue === ON_ACTION_TRIGGER_BUTTON) { let message = allProps.get(WEBSITE_MESSAGE_PROPERTIES); if (message === undefined) { - message = 'Press SPACE or touch here to open web site'; + message = "Press SPACE or touch here to open web site"; } - layoutManager.addActionButton('openWebsite', message.toString(), () => { - openWebsiteFunction(); - }, this.userInputManager); + layoutManager.addActionButton( + "openWebsite", + message.toString(), + () => { + openWebsiteFunction(); + }, + this.userInputManager + ); } else { openWebsiteFunction(); } } }); - this.gameMap.onPropertyChange('jitsiRoom', (newValue, oldValue, allProps) => { + this.gameMap.onPropertyChange("jitsiRoom", (newValue, oldValue, allProps) => { if (newValue === undefined) { - layoutManager.removeActionButton('jitsiRoom', this.userInputManager); + layoutManager.removeActionButton("jitsiRoom", this.userInputManager); this.stopJitsi(); } else { const openJitsiRoomFunction = () => { @@ -776,44 +820,52 @@ export class GameScene extends DirtyScene implements CenterListener { } else { this.startJitsi(roomName, undefined); } - layoutManager.removeActionButton('jitsiRoom', this.userInputManager); - } + layoutManager.removeActionButton("jitsiRoom", this.userInputManager); + }; const jitsiTriggerValue = allProps.get(TRIGGER_JITSI_PROPERTIES); if (jitsiTriggerValue && jitsiTriggerValue === ON_ACTION_TRIGGER_BUTTON) { let message = allProps.get(JITSI_MESSAGE_PROPERTIES); if (message === undefined) { - message = 'Press SPACE or touch here to enter Jitsi Meet room'; + message = "Press SPACE or touch here to enter Jitsi Meet room"; } - layoutManager.addActionButton('jitsiRoom', message.toString(), () => { - openJitsiRoomFunction(); - }, this.userInputManager); + layoutManager.addActionButton( + "jitsiRoom", + message.toString(), + () => { + openJitsiRoomFunction(); + }, + this.userInputManager + ); } else { openJitsiRoomFunction(); } } }); - this.gameMap.onPropertyChange('silent', (newValue, oldValue) => { - if (newValue === undefined || newValue === false || newValue === '') { + this.gameMap.onPropertyChange("silent", (newValue, oldValue) => { + if (newValue === undefined || newValue === false || newValue === "") { this.connection?.setSilent(false); } else { this.connection?.setSilent(true); } }); - this.gameMap.onPropertyChange('playAudio', (newValue, oldValue, allProps) => { + this.gameMap.onPropertyChange("playAudio", (newValue, oldValue, allProps) => { const volume = allProps.get(AUDIO_VOLUME_PROPERTY) as number | undefined; const loop = allProps.get(AUDIO_LOOP_PROPERTY) as boolean | undefined; - newValue === undefined ? audioManager.unloadAudio() : audioManager.playAudio(newValue, this.getMapDirUrl(), volume, loop); + newValue === undefined + ? audioManager.unloadAudio() + : audioManager.playAudio(newValue, this.getMapDirUrl(), volume, loop); }); // TODO: This legacy property should be removed at some point - this.gameMap.onPropertyChange('playAudioLoop', (newValue, oldValue) => { - newValue === undefined ? audioManager.unloadAudio() : audioManager.playAudio(newValue, this.getMapDirUrl(), undefined, true); + this.gameMap.onPropertyChange("playAudioLoop", (newValue, oldValue) => { + newValue === undefined + ? audioManager.unloadAudio() + : audioManager.playAudio(newValue, this.getMapDirUrl(), undefined, true); }); - this.gameMap.onPropertyChange('zone', (newValue, oldValue) => { - if (newValue === undefined || newValue === false || newValue === '') { + this.gameMap.onPropertyChange("zone", (newValue, oldValue) => { + if (newValue === undefined || newValue === false || newValue === "") { iframeListener.sendLeaveEvent(oldValue as string); - } else { iframeListener.sendEnterEvent(newValue as string); } @@ -822,168 +874,236 @@ export class GameScene extends DirtyScene implements CenterListener { private listenToIframeEvents(): void { this.iframeSubscriptionList = []; - this.iframeSubscriptionList.push(iframeListener.openPopupStream.subscribe((openPopupEvent) => { - - let objectLayerSquare: ITiledMapObject; - const targetObjectData = this.getObjectLayerData(openPopupEvent.targetObject); - if (targetObjectData !== undefined) { - objectLayerSquare = targetObjectData; - } else { - console.error("Error while opening a popup. Cannot find an object on the map with name '" + openPopupEvent.targetObject + "'. The first parameter of WA.openPopup() must be the name of a rectangle object in your map."); - return; - } - const escapedMessage = HtmlUtils.escapeHtml(openPopupEvent.message); - let html = `