From 6540f15c5bfbd182c97d10ed93450abd468a3e69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Fri, 11 Mar 2022 17:02:58 +0100 Subject: [PATCH] Refactoring Woka management (#1810) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Wrap websockets with HyperExpress * Add endpoints on pusher to resolve wokas * getting textures urls from pusher * Adding OpenAPI documentation for the pusher. The pusher now exposes a "/openapi" endpoint and a "/swagger-ui/" endpoint. * revert FRONT_URL * playerTextures metadata is being loaded via Phaser.Loader * fetch textures every time character or customize scene is open * Heavy changes: refactoring the pusher to always send the textures (and the front to accept them) * Sending character layer details to admin * Cleaning commented code * Fixing regex * Fix woka endpoints on pusher * Change error wording on pusher * Working on integration of the woka-list with the new admin endpoint. * Switching from "name" to "id" in texture object + using zod for woka/list validation * Add position on default woka data * Remove async on pusher option method * Fix woka list url * add options for /register * Fxiing loading the Woka list * Actually returning something in logout-callback * Copying messages to back too * remove customize button if no body parts are available (#1952) * remove customize button if no body parts are available * remove unused position field from PlayerTexturesCollection interface * removed unused label field * fix LocalUser test * little PlayerTextures class refactor * Fixing linting * Fixing missing Openapi packages in prod * Fixing back build Co-authored-by: Hanusiak Piotr Co-authored-by: David NĂ©grier * Add returns on pusher endpoints Co-authored-by: Alexis Faizeau Co-authored-by: Hanusiak Piotr Co-authored-by: Piotr Hanusiak --- .github/workflows/build-and-deploy.yml | 2 +- .github/workflows/continuous_integration.yml | 2 +- back/src/Messages/JsonMessages/.gitignore | 2 + back/src/Model/GameRoom.ts | 9 +- back/src/Services/AdminApi.ts | 4 +- .../src/Services/AdminApi/CharacterTexture.ts | 11 - back/src/Services/AdminApi/MapDetailsData.ts | 21 - back/src/Services/AdminApi/RoomRedirect.ts | 8 - back/src/Services/SocketManager.ts | 1 + docker-compose.e2e.yml | 2 + docker-compose.yaml | 1 + docs/dev/README.md | 7 + docs/dev/wokas.md | 30 + .../SelectCharacterScene.svelte | 13 +- front/src/Connexion/ConnectionManager.ts | 33 +- front/src/Connexion/ConnexionModels.ts | 2 +- front/src/Connexion/LocalUser.ts | 21 +- front/src/Connexion/Room.ts | 10 +- front/src/Connexion/RoomConnection.ts | 52 +- front/src/Phaser/Entity/Character.ts | 13 +- front/src/Phaser/Entity/PlayerTextures.ts | 532 +---- .../Entity/PlayerTexturesLoadingManager.ts | 70 +- front/src/Phaser/Game/GameScene.ts | 32 +- .../Phaser/Login/AbstractCharacterScene.ts | 32 +- front/src/Phaser/Login/CustomizeScene.ts | 67 +- front/src/Phaser/Login/EntryScene.ts | 8 + .../src/Phaser/Login/SelectCharacterScene.ts | 72 +- front/src/Stores/SelectCharacterSceneStore.ts | 3 + .../Phaser/Game/PlayerTexturesLoadingTest.ts | 28 - messages/JsonMessages/AdminApiData.ts | 2 - messages/JsonMessages/CharacterTexture.ts | 16 - messages/JsonMessages/MapDetailsData.ts | 2 - messages/JsonMessages/RegisterData.ts | 3 +- messages/package.json | 3 +- messages/protos/messages.proto | 6 + pusher/data/woka.json | 1851 +++++++++++++++++ pusher/package.json | 16 +- pusher/src/App.ts | 43 +- pusher/src/Controller/AdminController.ts | 128 +- .../src/Controller/AuthenticateController.ts | 349 ++-- pusher/src/Controller/BaseController.ts | 45 - pusher/src/Controller/BaseHttpController.ts | 47 + pusher/src/Controller/DebugController.ts | 59 +- pusher/src/Controller/IoSocketController.ts | 102 +- pusher/src/Controller/MapController.ts | 164 +- .../src/Controller/OpenIdProfileController.ts | 34 +- pusher/src/Controller/PrometheusController.ts | 21 +- pusher/src/Controller/SwaggerController.ts | 64 + pusher/src/Controller/WokaListController.ts | 52 + pusher/src/Enum/EnvironmentVariable.ts | 3 + pusher/src/Enum/PlayerTextures.ts | 48 + pusher/src/Middleware/AdminToken.ts | 22 + pusher/src/Middleware/Cors.ts | 18 + pusher/src/Middleware/HasToken.ts | 16 + .../Model/Websocket/ExAdminSocketInterface.ts | 4 +- .../src/Model/Websocket/ExSocketInterface.ts | 14 +- pusher/src/Model/Websocket/ProtobufUtils.ts | 10 +- pusher/src/Server/server/app.ts | 13 - pusher/src/Server/server/baseapp.ts | 109 - pusher/src/Server/server/formdata.ts | 99 - pusher/src/Server/server/sslapp.ts | 13 - pusher/src/Server/server/types.ts | 11 - pusher/src/Server/server/utils.ts | 36 - pusher/src/Server/sifrr.server.ts | 19 - pusher/src/Services/AdminApi.ts | 50 +- pusher/src/Services/AdminWokaService.ts | 29 + pusher/src/Services/LocalWokaService.ts | 74 + pusher/src/Services/SocketManager.ts | 58 +- pusher/src/Services/WokaService.ts | 5 + pusher/src/Services/WokaServiceInterface.ts | 8 + pusher/yarn.lock | 1035 +++++---- 71 files changed, 3979 insertions(+), 1810 deletions(-) create mode 100644 back/src/Messages/JsonMessages/.gitignore delete mode 100644 back/src/Services/AdminApi/CharacterTexture.ts delete mode 100644 back/src/Services/AdminApi/MapDetailsData.ts delete mode 100644 back/src/Services/AdminApi/RoomRedirect.ts create mode 100644 docs/dev/wokas.md create mode 100644 front/src/Stores/SelectCharacterSceneStore.ts delete mode 100644 front/tests/Phaser/Game/PlayerTexturesLoadingTest.ts delete mode 100644 messages/JsonMessages/CharacterTexture.ts create mode 100644 pusher/data/woka.json delete mode 100644 pusher/src/Controller/BaseController.ts create mode 100644 pusher/src/Controller/BaseHttpController.ts create mode 100644 pusher/src/Controller/SwaggerController.ts create mode 100644 pusher/src/Controller/WokaListController.ts create mode 100644 pusher/src/Enum/PlayerTextures.ts create mode 100644 pusher/src/Middleware/AdminToken.ts create mode 100644 pusher/src/Middleware/Cors.ts create mode 100644 pusher/src/Middleware/HasToken.ts delete mode 100644 pusher/src/Server/server/app.ts delete mode 100644 pusher/src/Server/server/baseapp.ts delete mode 100644 pusher/src/Server/server/formdata.ts delete mode 100644 pusher/src/Server/server/sslapp.ts delete mode 100644 pusher/src/Server/server/types.ts delete mode 100644 pusher/src/Server/server/utils.ts delete mode 100644 pusher/src/Server/sifrr.server.ts create mode 100644 pusher/src/Services/AdminWokaService.ts create mode 100644 pusher/src/Services/LocalWokaService.ts create mode 100644 pusher/src/Services/WokaService.ts create mode 100644 pusher/src/Services/WokaServiceInterface.ts diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index e924cc11..9b17136d 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -81,7 +81,7 @@ jobs: working-directory: messages - name: Build proto messages - run: yarn run proto && yarn run copy-to-back + run: yarn run proto && yarn run copy-to-back && yarn run json-copy-to-back working-directory: messages # docker diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index bf433a0a..8554d079 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -155,7 +155,7 @@ jobs: working-directory: "messages" - name: "Build proto messages" - run: yarn run proto && yarn run copy-to-back + run: yarn run proto && yarn run copy-to-back && yarn run json-copy-to-back working-directory: "messages" - name: "Build" diff --git a/back/src/Messages/JsonMessages/.gitignore b/back/src/Messages/JsonMessages/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/back/src/Messages/JsonMessages/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/back/src/Model/GameRoom.ts b/back/src/Model/GameRoom.ts index 7d7b24a5..0e8203cf 100644 --- a/back/src/Model/GameRoom.ts +++ b/back/src/Model/GameRoom.ts @@ -27,7 +27,7 @@ import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils"; import { RoomSocket, ZoneSocket } from "src/RoomManager"; import { Admin } from "../Model/Admin"; import { adminApi } from "../Services/AdminApi"; -import { isMapDetailsData, MapDetailsData } from "../Services/AdminApi/MapDetailsData"; +import { isMapDetailsData, MapDetailsData } from "../Messages/JsonMessages/MapDetailsData"; import { ITiledMap } from "@workadventure/tiled-map-type-guard/dist"; import { mapFetcher } from "../Services/MapFetcher"; import { VariablesManager } from "../Services/VariablesManager"; @@ -35,7 +35,7 @@ import { ADMIN_API_URL } from "../Enum/EnvironmentVariable"; import { LocalUrlError } from "../Services/LocalUrlError"; import { emitErrorOnRoomSocket } from "../Services/MessageHelpers"; import { VariableError } from "../Services/VariableError"; -import { isRoomRedirect } from "../Services/AdminApi/RoomRedirect"; +import { isRoomRedirect } from "../Messages/JsonMessages/RoomRedirect"; export type ConnectCallback = (user: User, group: Group) => void; export type DisconnectCallback = (user: User, group: Group) => void; @@ -571,8 +571,11 @@ export class GameRoom { return { mapUrl, policy_type: 1, - textures: [], tags: [], + authenticationMandatory: null, + roomSlug: null, + contactPage: null, + group: null, }; } diff --git a/back/src/Services/AdminApi.ts b/back/src/Services/AdminApi.ts index f4fa40b6..148877af 100644 --- a/back/src/Services/AdminApi.ts +++ b/back/src/Services/AdminApi.ts @@ -1,7 +1,7 @@ import { ADMIN_API_TOKEN, ADMIN_API_URL } from "../Enum/EnvironmentVariable"; import Axios from "axios"; -import { isMapDetailsData, MapDetailsData } from "./AdminApi/MapDetailsData"; -import { isRoomRedirect, RoomRedirect } from "./AdminApi/RoomRedirect"; +import { isMapDetailsData, MapDetailsData } from "../Messages/JsonMessages/MapDetailsData"; +import { isRoomRedirect, RoomRedirect } from "../Messages/JsonMessages/RoomRedirect"; class AdminApi { async fetchMapDetails(playUri: string): Promise { diff --git a/back/src/Services/AdminApi/CharacterTexture.ts b/back/src/Services/AdminApi/CharacterTexture.ts deleted file mode 100644 index 055b3033..00000000 --- a/back/src/Services/AdminApi/CharacterTexture.ts +++ /dev/null @@ -1,11 +0,0 @@ -import * as tg from "generic-type-guard"; - -export const isCharacterTexture = new tg.IsInterface() - .withProperties({ - id: tg.isNumber, - level: tg.isNumber, - url: tg.isString, - rights: tg.isString, - }) - .get(); -export type CharacterTexture = tg.GuardedType; diff --git a/back/src/Services/AdminApi/MapDetailsData.ts b/back/src/Services/AdminApi/MapDetailsData.ts deleted file mode 100644 index d3402b92..00000000 --- a/back/src/Services/AdminApi/MapDetailsData.ts +++ /dev/null @@ -1,21 +0,0 @@ -import * as tg from "generic-type-guard"; -import { isCharacterTexture } from "./CharacterTexture"; -import { isAny, isNumber } from "generic-type-guard"; - -/*const isNumericEnum = - (vs: T) => - (v: any): v is T => - typeof v === "number" && v in vs;*/ - -export const isMapDetailsData = new tg.IsInterface() - .withProperties({ - mapUrl: tg.isString, - policy_type: isNumber, //isNumericEnum(GameRoomPolicyTypes), - tags: tg.isArray(tg.isString), - textures: tg.isArray(isCharacterTexture), - }) - .withOptionalProperties({ - roomSlug: tg.isUnion(tg.isString, tg.isNull), // deprecated - }) - .get(); -export type MapDetailsData = tg.GuardedType; diff --git a/back/src/Services/AdminApi/RoomRedirect.ts b/back/src/Services/AdminApi/RoomRedirect.ts deleted file mode 100644 index 7257ebd3..00000000 --- a/back/src/Services/AdminApi/RoomRedirect.ts +++ /dev/null @@ -1,8 +0,0 @@ -import * as tg from "generic-type-guard"; - -export const isRoomRedirect = new tg.IsInterface() - .withProperties({ - redirectUrl: tg.isString, - }) - .get(); -export type RoomRedirect = tg.GuardedType; diff --git a/back/src/Services/SocketManager.ts b/back/src/Services/SocketManager.ts index 9233811b..1d26f001 100644 --- a/back/src/Services/SocketManager.ts +++ b/back/src/Services/SocketManager.ts @@ -103,6 +103,7 @@ export class SocketManager { const roomJoinedMessage = new RoomJoinedMessage(); roomJoinedMessage.setTagList(joinRoomMessage.getTagList()); roomJoinedMessage.setUserroomtoken(joinRoomMessage.getUserroomtoken()); + roomJoinedMessage.setCharacterlayerList(joinRoomMessage.getCharacterlayerList()); for (const [itemId, item] of room.getItemsState().entries()) { const itemStateMessage = new ItemStateMessage(); diff --git a/docker-compose.e2e.yml b/docker-compose.e2e.yml index 52e312b5..25dafee4 100644 --- a/docker-compose.e2e.yml +++ b/docker-compose.e2e.yml @@ -28,6 +28,8 @@ services: dockerfile: pusher/Dockerfile command: yarn run runprod volumes: [] + environment: + ENABLE_OPENAPI_ENDPOINT: "false" back: image: 'wa-back-e2e' diff --git a/docker-compose.yaml b/docker-compose.yaml index 3c16ce00..99885ca2 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -89,6 +89,7 @@ services: OPID_USERNAME_CLAIM: $OPID_USERNAME_CLAIM OPID_LOCALE_CLAIM: $OPID_LOCALE_CLAIM DISABLE_ANONYMOUS: $DISABLE_ANONYMOUS + ENABLE_OPENAPI_ENDPOINT: "true" volumes: - ./pusher:/usr/src/app labels: diff --git a/docs/dev/README.md b/docs/dev/README.md index d05c4884..fae0ffd0 100644 --- a/docs/dev/README.md +++ b/docs/dev/README.md @@ -13,4 +13,11 @@ Check out the [contributing guide](../../CONTRIBUTING.md) ## Front documentation +- [How to add translations](how-to-translate.md) - [How to add new functions in the scripting API](contributing-to-scripting-api.md) +- [About Wokas](wokas.md) + +## Pusher documentation + +The Pusher is exposing its HTTP API as "OpenAPI" endpoint. +You can browse this API at `http://pusher.workadventure.localhost/swagger-ui/`. diff --git a/docs/dev/wokas.md b/docs/dev/wokas.md new file mode 100644 index 00000000..e59e082d --- /dev/null +++ b/docs/dev/wokas.md @@ -0,0 +1,30 @@ +# About Wokas + +Wokas are made of a set of layers (for custom wokas), or of only 1 layers (if selected from the first screen) + +Internally, each layer has: + +- a name +- a URL + +## Connection to a map + +When a user connects to a map, it sends, as a web-socket parameter, the list of layer **names**. + +The pusher is in charge of converting those layer names into the URLs. This way, a client cannot send any random +URL to the pusher. + +When the pusher receives the layer names, it validates these names and sends back the URLs + sends the names+urls to the back. +If the layers cannot be validated, the websocket connections sends an error message and closes. The user is sent back to the "choose your Woka" screen. + +## Getting the list of available Wokas + +The pusher can send the list of available Wokas to the user. +It can actually query the admin for this list, if needed (= if an admin is configured) + +## In the pusher + +The pusher contains a classes in charge of managing the Wokas: + +- `LocalWokaService`: used when no admin is connected. Returns a hard-coded list of Wokas (stored in `pusher/data/woka.json`). +- `AdminWokaService`: used to delegate the list of Wokas to the admin. diff --git a/front/src/Components/selectCharacter/SelectCharacterScene.svelte b/front/src/Components/selectCharacter/SelectCharacterScene.svelte index 9f6e5f9c..807cf88f 100644 --- a/front/src/Components/selectCharacter/SelectCharacterScene.svelte +++ b/front/src/Components/selectCharacter/SelectCharacterScene.svelte @@ -2,6 +2,7 @@ import type { Game } from "../../Phaser/Game/Game"; import { SelectCharacterScene, SelectCharacterSceneName } from "../../Phaser/Login/SelectCharacterScene"; import LL from "../../i18n/i18n-svelte"; + import { customizeAvailableStore } from "../../Stores/SelectCharacterSceneStore"; export let game: Game; @@ -40,11 +41,13 @@ class="selectCharacterSceneFormSubmit nes-btn is-primary" on:click|preventDefault={cameraScene}>{$LL.woka.selectWoka.continue()} - + {#if $customizeAvailableStore} + + {/if} diff --git a/front/src/Connexion/ConnectionManager.ts b/front/src/Connexion/ConnectionManager.ts index 62daa58d..004f9039 100644 --- a/front/src/Connexion/ConnectionManager.ts +++ b/front/src/Connexion/ConnectionManager.ts @@ -138,7 +138,7 @@ class ConnectionManager { console.error("Invalid data received from /register route. Data: ", data); throw new Error("Invalid data received from /register route."); } - this.localUser = new LocalUser(data.userUuid, data.textures, data.email); + this.localUser = new LocalUser(data.userUuid, data.email); this.authToken = data.authToken; localUserStore.saveUser(this.localUser); localUserStore.setAuthToken(this.authToken); @@ -218,22 +218,6 @@ class ConnectionManager { } } this.localUser = localUserStore.getLocalUser() as LocalUser; //if authToken exist in localStorage then localUser cannot be null - - if (this._currentRoom.textures != undefined && this._currentRoom.textures.length > 0) { - //check if texture was changed - if (this.localUser.textures.length === 0) { - this.localUser.textures = this._currentRoom.textures; - } else { - this._currentRoom.textures.forEach((newTexture) => { - const alreadyExistTexture = this.localUser.textures.find((c) => newTexture.id === c.id); - if (this.localUser.textures.findIndex((c) => newTexture.id === c.id) !== -1) { - return; - } - this.localUser.textures.push(newTexture); - }); - } - localUserStore.saveUser(this.localUser); - } } if (this._currentRoom == undefined) { return Promise.reject(new Error("Invalid URL")); @@ -259,7 +243,7 @@ class ConnectionManager { public async anonymousLogin(isBenchmark: boolean = false): Promise { const data = await axiosWithRetry.post(`${PUSHER_URL}/anonymLogin`).then((res) => res.data); - this.localUser = new LocalUser(data.userUuid, [], data.email); + this.localUser = new LocalUser(data.userUuid, data.email); this.authToken = data.authToken; if (!isBenchmark) { // In benchmark, we don't have a local storage. @@ -269,7 +253,7 @@ class ConnectionManager { } public initBenchmark(): void { - this.localUser = new LocalUser("", []); + this.localUser = new LocalUser(""); } public connectToRoomSocket( @@ -346,16 +330,13 @@ class ConnectionManager { throw new Error("No Auth code provided"); } } - const { authToken, userUuid, textures, email, username, locale } = await Axios.get( - `${PUSHER_URL}/login-callback`, - { - params: { code, nonce, token, playUri: this.currentRoom?.key }, - } - ).then((res) => { + const { authToken, userUuid, email, username, locale } = await Axios.get(`${PUSHER_URL}/login-callback`, { + params: { code, nonce, token, playUri: this.currentRoom?.key }, + }).then((res) => { return res.data; }); localUserStore.setAuthToken(authToken); - this.localUser = new LocalUser(userUuid, textures, email); + this.localUser = new LocalUser(userUuid, email); localUserStore.saveUser(this.localUser); this.authToken = authToken; diff --git a/front/src/Connexion/ConnexionModels.ts b/front/src/Connexion/ConnexionModels.ts index bf834a02..c681fd37 100644 --- a/front/src/Connexion/ConnexionModels.ts +++ b/front/src/Connexion/ConnexionModels.ts @@ -1,7 +1,6 @@ import type { SignalData } from "simple-peer"; import type { RoomConnection } from "./RoomConnection"; import type { BodyResourceDescriptionInterface } from "../Phaser/Entity/PlayerTextures"; -import { PositionMessage_Direction } from "../Messages/ts-proto-generated/messages"; export interface PointInterface { x: number; @@ -83,6 +82,7 @@ export interface RoomJoinedMessageInterface { //groups: GroupCreatedUpdatedMessageInterface[], items: { [itemId: number]: unknown }; variables: Map; + characterLayers: BodyResourceDescriptionInterface[]; } export interface PlayGlobalMessageInterface { diff --git a/front/src/Connexion/LocalUser.ts b/front/src/Connexion/LocalUser.ts index d4498883..4db0d6d7 100644 --- a/front/src/Connexion/LocalUser.ts +++ b/front/src/Connexion/LocalUser.ts @@ -1,10 +1,11 @@ import { MAX_USERNAME_LENGTH } from "../Enum/EnvironmentVariable"; +export type LayerNames = "woka" | "body" | "eyes" | "hair" | "clothes" | "hat" | "accessory"; + export interface CharacterTexture { - id: number; - level: number; + id: string; + layer: LayerNames; url: string; - rights: string; } export const maxUserNameLength: number = MAX_USERNAME_LENGTH; @@ -14,9 +15,11 @@ export function isUserNameValid(value: unknown): boolean { } export function areCharacterLayersValid(value: string[] | null): boolean { - if (!value || !value.length) return false; - for (let i = 0; i < value.length; i++) { - if (/^\w+$/.exec(value[i]) === null) { + if (!value || !value.length) { + return false; + } + for (const layerName of value) { + if (layerName.length === 0 || layerName === " ") { return false; } } @@ -24,9 +27,5 @@ export function areCharacterLayersValid(value: string[] | null): boolean { } export class LocalUser { - constructor( - public readonly uuid: string, - public textures: CharacterTexture[], - public email: string | null = null - ) {} + constructor(public readonly uuid: string, public email: string | null = null) {} } diff --git a/front/src/Connexion/Room.ts b/front/src/Connexion/Room.ts index 213c3019..778f7e35 100644 --- a/front/src/Connexion/Room.ts +++ b/front/src/Connexion/Room.ts @@ -9,7 +9,7 @@ import { isMapDetailsData } from "../Messages/JsonMessages/MapDetailsData"; import { isRoomRedirect } from "../Messages/JsonMessages/RoomRedirect"; export class MapDetail { - constructor(public readonly mapUrl: string, public readonly textures: CharacterTexture[] | undefined) {} + constructor(public readonly mapUrl: string) {} } export interface RoomRedirect { @@ -25,7 +25,6 @@ export class Room { private _authenticationMandatory: boolean = DISABLE_ANONYMOUS; private _iframeAuthentication?: string = OPID_LOGIN_SCREEN_PROVIDER; private _mapUrl: string | undefined; - private _textures: CharacterTexture[] | undefined; private instance: string | undefined; private readonly _search: URLSearchParams; private _contactPage: string | undefined; @@ -118,7 +117,6 @@ export class Room { } else if (isMapDetailsData(data)) { console.log("Map ", this.id, " resolves to URL ", data.mapUrl); this._mapUrl = data.mapUrl; - this._textures = data.textures; this._group = data.group; this._authenticationMandatory = data.authenticationMandatory != null ? data.authenticationMandatory : DISABLE_ANONYMOUS; @@ -128,7 +126,7 @@ export class Room { this._expireOn = new Date(data.expireOn); } this._canReport = data.canReport ?? false; - return new MapDetail(data.mapUrl, data.textures); + return new MapDetail(data.mapUrl); } else { throw new Error("Data received by the /map endpoint of the Pusher is not in a valid format."); } @@ -205,10 +203,6 @@ export class Room { return this.roomUrl.toString(); } - get textures(): CharacterTexture[] | undefined { - return this._textures; - } - get mapUrl(): string { if (!this._mapUrl) { throw new Error("Map URL not fetched yet"); diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index 4e2f8397..20b69d62 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -20,7 +20,7 @@ import type { BodyResourceDescriptionInterface } from "../Phaser/Entity/PlayerTe import { adminMessagesService } from "./AdminMessagesService"; import { connectionManager } from "./ConnectionManager"; import { get } from "svelte/store"; -import { warningContainerStore } from "../Stores/MenuStore"; +import { menuIconVisiblilityStore, menuVisiblilityStore, warningContainerStore } from "../Stores/MenuStore"; import { followStateStore, followRoleStore, followUsersStore } from "../Stores/FollowStore"; import { localUserStore } from "./LocalUserStore"; import { @@ -52,10 +52,14 @@ import { PositionMessage_Direction, SetPlayerDetailsMessage as SetPlayerDetailsMessageTsProto, PingMessage as PingMessageTsProto, + CharacterLayerMessage, } from "../Messages/ts-proto-generated/messages"; import { Subject } from "rxjs"; import { OpenPopupEvent } from "../Api/Events/OpenPopupEvent"; import { match } from "assert"; +import { selectCharacterSceneVisibleStore } from "../Stores/SelectCharacterStore"; +import { gameManager } from "../Phaser/Game/GameManager"; +import { SelectCharacterScene, SelectCharacterSceneName } from "../Phaser/Login/SelectCharacterScene"; const manualPingDelay = 20000; @@ -337,11 +341,28 @@ export class RoomConnection implements RoomConnection { this.tags = roomJoinedMessage.tag; this._userRoomToken = roomJoinedMessage.userRoomToken; + // If one of the URLs sent to us does not exist, let's go to the Woka selection screen. + for (const characterLayer of roomJoinedMessage.characterLayer) { + if (!characterLayer.url) { + this.goToSelectYourWokaScene(); + this.closed = true; + break; + } + } + if (this.closed) { + break; + } + + const characterLayers = roomJoinedMessage.characterLayer.map( + this.mapCharacterLayerToBodyResourceDescription.bind(this) + ); + this._roomJoinedMessageStream.next({ connection: this, room: { items, variables, + characterLayers, } as RoomJoinedMessageInterface, }); break; @@ -351,6 +372,12 @@ export class RoomConnection implements RoomConnection { this.closed = true; break; } + case "invalidTextureMessage": { + this.goToSelectYourWokaScene(); + + this.closed = true; + break; + } case "tokenExpiredMessage": { connectionManager.logout().catch((e) => console.error(e)); this.closed = true; //technically, this isn't needed since loadOpenIDScreen() will do window.location.assign() but I prefer to leave it for consistency @@ -591,6 +618,15 @@ export class RoomConnection implements RoomConnection { }); }*/ + private mapCharacterLayerToBodyResourceDescription( + characterLayer: CharacterLayerMessage + ): BodyResourceDescriptionInterface { + return { + id: characterLayer.name, + img: characterLayer.url, + }; + } + // TODO: move this to protobuf utils private toMessageUserJoined(message: UserJoinedMessageTsProto): MessageUserJoined { const position = message.position; @@ -598,12 +634,7 @@ export class RoomConnection implements RoomConnection { throw new Error("Invalid JOIN_ROOM message"); } - const characterLayers = message.characterLayers.map((characterLayer): BodyResourceDescriptionInterface => { - return { - name: characterLayer.name, - img: characterLayer.url, - }; - }); + const characterLayers = message.characterLayers.map(this.mapCharacterLayerToBodyResourceDescription.bind(this)); const companion = message.companion; @@ -863,4 +894,11 @@ export class RoomConnection implements RoomConnection { public get userRoomToken(): string | undefined { return this._userRoomToken; } + + private goToSelectYourWokaScene(): void { + menuVisiblilityStore.set(false); + menuIconVisiblilityStore.set(false); + selectCharacterSceneVisibleStore.set(true); + gameManager.leaveGame(SelectCharacterSceneName, new SelectCharacterScene()); + } } diff --git a/front/src/Phaser/Entity/Character.ts b/front/src/Phaser/Entity/Character.ts index d1fc91c7..37d92b89 100644 --- a/front/src/Phaser/Entity/Character.ts +++ b/front/src/Phaser/Entity/Character.ts @@ -10,7 +10,7 @@ import type { GameScene } from "../Game/GameScene"; import { DEPTH_INGAME_TEXT_INDEX } from "../Game/DepthIndexes"; import type OutlinePipelinePlugin from "phaser3-rex-plugins/plugins/outlinepipeline-plugin.js"; import { isSilentStore } from "../../Stores/MediaStore"; -import { lazyLoadPlayerCharacterTextures, loadAllDefaultModels } from "./PlayerTexturesLoadingManager"; +import { lazyLoadPlayerCharacterTextures } from "./PlayerTexturesLoadingManager"; import { TexturesHelper } from "../Helpers/TexturesHelper"; import type { PictureStore } from "../../Stores/PictureStore"; import { Unsubscriber, Writable, writable } from "svelte/store"; @@ -83,7 +83,16 @@ export abstract class Character extends Container implements OutlineableInterfac }); }) .catch(() => { - return lazyLoadPlayerCharacterTextures(scene.load, ["color_22", "eyes_23"]).then((textures) => { + return lazyLoadPlayerCharacterTextures(scene.load, [ + { + id: "color_22", + img: "resources/customisation/character_color/character_color21.png", + }, + { + id: "eyes_23", + img: "resources/customisation/character_eyes/character_eyes23.png", + }, + ]).then((textures) => { this.addTextures(textures, frame); this.invisible = false; this.playAnimation(direction, moving); diff --git a/front/src/Phaser/Entity/PlayerTextures.ts b/front/src/Phaser/Entity/PlayerTextures.ts index d4376c66..02d9dd3a 100644 --- a/front/src/Phaser/Entity/PlayerTextures.ts +++ b/front/src/Phaser/Entity/PlayerTextures.ts @@ -5,446 +5,122 @@ export interface BodyResourceDescriptionListInterface { } export interface BodyResourceDescriptionInterface { - name: string; + id: string; img: string; level?: number; } -export const PLAYER_RESOURCES: BodyResourceDescriptionListInterface = { - male1: { name: "male1", img: "resources/characters/pipoya/Male 01-1.png" }, - male2: { name: "male2", img: "resources/characters/pipoya/Male 02-2.png" }, - male3: { name: "male3", img: "resources/characters/pipoya/Male 03-4.png" }, - male4: { name: "male4", img: "resources/characters/pipoya/Male 09-1.png" }, - male5: { name: "male5", img: "resources/characters/pipoya/Male 10-3.png" }, - male6: { name: "male6", img: "resources/characters/pipoya/Male 17-2.png" }, - male7: { name: "male7", img: "resources/characters/pipoya/Male 18-1.png" }, - male8: { name: "male8", img: "resources/characters/pipoya/Male 16-4.png" }, - male9: { name: "male9", img: "resources/characters/pipoya/Male 07-2.png" }, - male10: { name: "male10", img: "resources/characters/pipoya/Male 05-3.png" }, - male11: { name: "male11", img: "resources/characters/pipoya/Teacher male 02.png" }, - male12: { name: "male12", img: "resources/characters/pipoya/su4 Student male 12.png" }, - - Female1: { name: "Female1", img: "resources/characters/pipoya/Female 01-1.png" }, - Female2: { name: "Female2", img: "resources/characters/pipoya/Female 02-2.png" }, - Female3: { name: "Female3", img: "resources/characters/pipoya/Female 03-4.png" }, - Female4: { name: "Female4", img: "resources/characters/pipoya/Female 09-1.png" }, - Female5: { name: "Female5", img: "resources/characters/pipoya/Female 10-3.png" }, - Female6: { name: "Female6", img: "resources/characters/pipoya/Female 17-2.png" }, - Female7: { name: "Female7", img: "resources/characters/pipoya/Female 18-1.png" }, - Female8: { name: "Female8", img: "resources/characters/pipoya/Female 16-4.png" }, - Female9: { name: "Female9", img: "resources/characters/pipoya/Female 07-2.png" }, - Female10: { name: "Female10", img: "resources/characters/pipoya/Female 05-3.png" }, - Female11: { name: "Female11", img: "resources/characters/pipoya/Teacher fmale 02.png" }, - Female12: { name: "Female12", img: "resources/characters/pipoya/su4 Student fmale 12.png" }, +/** + * Temporary object to map layers to the old "level" concept. + */ +export const mapLayerToLevel = { + woka: -1, + body: 0, + eyes: 1, + hair: 2, + clothes: 3, + hat: 4, + accessory: 5, }; -export const COLOR_RESOURCES: BodyResourceDescriptionListInterface = { - color_1: { name: "color_1", img: "resources/customisation/character_color/character_color0.png" }, - color_2: { name: "color_2", img: "resources/customisation/character_color/character_color1.png" }, - color_3: { name: "color_3", img: "resources/customisation/character_color/character_color2.png" }, - color_4: { name: "color_4", img: "resources/customisation/character_color/character_color3.png" }, - color_5: { name: "color_5", img: "resources/customisation/character_color/character_color4.png" }, - color_6: { name: "color_6", img: "resources/customisation/character_color/character_color5.png" }, - color_7: { name: "color_7", img: "resources/customisation/character_color/character_color6.png" }, - color_8: { name: "color_8", img: "resources/customisation/character_color/character_color7.png" }, - color_9: { name: "color_9", img: "resources/customisation/character_color/character_color8.png" }, - color_10: { name: "color_10", img: "resources/customisation/character_color/character_color9.png" }, - color_11: { name: "color_11", img: "resources/customisation/character_color/character_color10.png" }, - color_12: { name: "color_12", img: "resources/customisation/character_color/character_color11.png" }, - color_13: { name: "color_13", img: "resources/customisation/character_color/character_color12.png" }, - color_14: { name: "color_14", img: "resources/customisation/character_color/character_color13.png" }, - color_15: { name: "color_15", img: "resources/customisation/character_color/character_color14.png" }, - color_16: { name: "color_16", img: "resources/customisation/character_color/character_color15.png" }, - color_17: { name: "color_17", img: "resources/customisation/character_color/character_color16.png" }, - color_18: { name: "color_18", img: "resources/customisation/character_color/character_color17.png" }, - color_19: { name: "color_19", img: "resources/customisation/character_color/character_color18.png" }, - color_20: { name: "color_20", img: "resources/customisation/character_color/character_color19.png" }, - color_21: { name: "color_21", img: "resources/customisation/character_color/character_color20.png" }, - color_22: { name: "color_22", img: "resources/customisation/character_color/character_color21.png" }, - color_23: { name: "color_23", img: "resources/customisation/character_color/character_color22.png" }, - color_24: { name: "color_24", img: "resources/customisation/character_color/character_color23.png" }, - color_25: { name: "color_25", img: "resources/customisation/character_color/character_color24.png" }, - color_26: { name: "color_26", img: "resources/customisation/character_color/character_color25.png" }, - color_27: { name: "color_27", img: "resources/customisation/character_color/character_color26.png" }, - color_28: { name: "color_28", img: "resources/customisation/character_color/character_color27.png" }, - color_29: { name: "color_29", img: "resources/customisation/character_color/character_color28.png" }, - color_30: { name: "color_30", img: "resources/customisation/character_color/character_color29.png" }, - color_31: { name: "color_31", img: "resources/customisation/character_color/character_color30.png" }, - color_32: { name: "color_32", img: "resources/customisation/character_color/character_color31.png" }, - color_33: { name: "color_33", img: "resources/customisation/character_color/character_color32.png" }, -}; +export enum PlayerTexturesKey { + Accessory = "accessory", + Body = "body", + Clothes = "clothes", + Eyes = "eyes", + Hair = "hair", + Hat = "hat", + Woka = "woka", +} -export const EYES_RESOURCES: BodyResourceDescriptionListInterface = { - eyes_1: { name: "eyes_1", img: "resources/customisation/character_eyes/character_eyes1.png" }, - eyes_2: { name: "eyes_2", img: "resources/customisation/character_eyes/character_eyes2.png" }, - eyes_3: { name: "eyes_3", img: "resources/customisation/character_eyes/character_eyes3.png" }, - eyes_4: { name: "eyes_4", img: "resources/customisation/character_eyes/character_eyes4.png" }, - eyes_5: { name: "eyes_5", img: "resources/customisation/character_eyes/character_eyes5.png" }, - eyes_6: { name: "eyes_6", img: "resources/customisation/character_eyes/character_eyes6.png" }, - eyes_7: { name: "eyes_7", img: "resources/customisation/character_eyes/character_eyes7.png" }, - eyes_8: { name: "eyes_8", img: "resources/customisation/character_eyes/character_eyes8.png" }, - eyes_9: { name: "eyes_9", img: "resources/customisation/character_eyes/character_eyes9.png" }, - eyes_10: { name: "eyes_10", img: "resources/customisation/character_eyes/character_eyes10.png" }, - eyes_11: { name: "eyes_11", img: "resources/customisation/character_eyes/character_eyes11.png" }, - eyes_12: { name: "eyes_12", img: "resources/customisation/character_eyes/character_eyes12.png" }, - eyes_13: { name: "eyes_13", img: "resources/customisation/character_eyes/character_eyes13.png" }, - eyes_14: { name: "eyes_14", img: "resources/customisation/character_eyes/character_eyes14.png" }, - eyes_15: { name: "eyes_15", img: "resources/customisation/character_eyes/character_eyes15.png" }, - eyes_16: { name: "eyes_16", img: "resources/customisation/character_eyes/character_eyes16.png" }, - eyes_17: { name: "eyes_17", img: "resources/customisation/character_eyes/character_eyes17.png" }, - eyes_18: { name: "eyes_18", img: "resources/customisation/character_eyes/character_eyes18.png" }, - eyes_19: { name: "eyes_19", img: "resources/customisation/character_eyes/character_eyes19.png" }, - eyes_20: { name: "eyes_20", img: "resources/customisation/character_eyes/character_eyes20.png" }, - eyes_21: { name: "eyes_21", img: "resources/customisation/character_eyes/character_eyes21.png" }, - eyes_22: { name: "eyes_22", img: "resources/customisation/character_eyes/character_eyes22.png" }, - eyes_23: { name: "eyes_23", img: "resources/customisation/character_eyes/character_eyes23.png" }, - eyes_24: { name: "eyes_24", img: "resources/customisation/character_eyes/character_eyes24.png" }, - eyes_25: { name: "eyes_25", img: "resources/customisation/character_eyes/character_eyes25.png" }, - eyes_26: { name: "eyes_26", img: "resources/customisation/character_eyes/character_eyes26.png" }, - eyes_27: { name: "eyes_27", img: "resources/customisation/character_eyes/character_eyes27.png" }, - eyes_28: { name: "eyes_28", img: "resources/customisation/character_eyes/character_eyes28.png" }, - eyes_29: { name: "eyes_29", img: "resources/customisation/character_eyes/character_eyes29.png" }, - eyes_30: { name: "eyes_30", img: "resources/customisation/character_eyes/character_eyes30.png" }, -}; +type PlayerTexturesMetadata = Record; -export const HAIR_RESOURCES: BodyResourceDescriptionListInterface = { - hair_1: { name: "hair_1", img: "resources/customisation/character_hairs/character_hairs0.png" }, - hair_2: { name: "hair_2", img: "resources/customisation/character_hairs/character_hairs1.png" }, - hair_3: { name: "hair_3", img: "resources/customisation/character_hairs/character_hairs2.png" }, - hair_4: { name: "hair_4", img: "resources/customisation/character_hairs/character_hairs3.png" }, - hair_5: { name: "hair_5", img: "resources/customisation/character_hairs/character_hairs4.png" }, - hair_6: { name: "hair_6", img: "resources/customisation/character_hairs/character_hairs5.png" }, - hair_7: { name: "hair_7", img: "resources/customisation/character_hairs/character_hairs6.png" }, - hair_8: { name: "hair_8", img: "resources/customisation/character_hairs/character_hairs7.png" }, - hair_9: { name: "hair_9", img: "resources/customisation/character_hairs/character_hairs8.png" }, - hair_10: { name: "hair_10", img: "resources/customisation/character_hairs/character_hairs9.png" }, - hair_11: { name: "hair_11", img: "resources/customisation/character_hairs/character_hairs10.png" }, - hair_12: { name: "hair_12", img: "resources/customisation/character_hairs/character_hairs11.png" }, - hair_13: { name: "hair_13", img: "resources/customisation/character_hairs/character_hairs12.png" }, - hair_14: { name: "hair_14", img: "resources/customisation/character_hairs/character_hairs13.png" }, - hair_15: { name: "hair_15", img: "resources/customisation/character_hairs/character_hairs14.png" }, - hair_16: { name: "hair_16", img: "resources/customisation/character_hairs/character_hairs15.png" }, - hair_17: { name: "hair_17", img: "resources/customisation/character_hairs/character_hairs16.png" }, - hair_18: { name: "hair_18", img: "resources/customisation/character_hairs/character_hairs17.png" }, - hair_19: { name: "hair_19", img: "resources/customisation/character_hairs/character_hairs18.png" }, - hair_20: { name: "hair_20", img: "resources/customisation/character_hairs/character_hairs19.png" }, - hair_21: { name: "hair_21", img: "resources/customisation/character_hairs/character_hairs20.png" }, - hair_22: { name: "hair_22", img: "resources/customisation/character_hairs/character_hairs21.png" }, - hair_23: { name: "hair_23", img: "resources/customisation/character_hairs/character_hairs22.png" }, - hair_24: { name: "hair_24", img: "resources/customisation/character_hairs/character_hairs23.png" }, - hair_25: { name: "hair_25", img: "resources/customisation/character_hairs/character_hairs24.png" }, - hair_26: { name: "hair_26", img: "resources/customisation/character_hairs/character_hairs25.png" }, - hair_27: { name: "hair_27", img: "resources/customisation/character_hairs/character_hairs26.png" }, - hair_28: { name: "hair_28", img: "resources/customisation/character_hairs/character_hairs27.png" }, - hair_29: { name: "hair_29", img: "resources/customisation/character_hairs/character_hairs28.png" }, - hair_30: { name: "hair_30", img: "resources/customisation/character_hairs/character_hairs29.png" }, - hair_31: { name: "hair_31", img: "resources/customisation/character_hairs/character_hairs30.png" }, - hair_32: { name: "hair_32", img: "resources/customisation/character_hairs/character_hairs31.png" }, - hair_33: { name: "hair_33", img: "resources/customisation/character_hairs/character_hairs32.png" }, - hair_34: { name: "hair_34", img: "resources/customisation/character_hairs/character_hairs33.png" }, - hair_35: { name: "hair_35", img: "resources/customisation/character_hairs/character_hairs34.png" }, - hair_36: { name: "hair_36", img: "resources/customisation/character_hairs/character_hairs35.png" }, - hair_37: { name: "hair_37", img: "resources/customisation/character_hairs/character_hairs36.png" }, - hair_38: { name: "hair_38", img: "resources/customisation/character_hairs/character_hairs37.png" }, - hair_39: { name: "hair_39", img: "resources/customisation/character_hairs/character_hairs38.png" }, - hair_40: { name: "hair_40", img: "resources/customisation/character_hairs/character_hairs39.png" }, - hair_41: { name: "hair_41", img: "resources/customisation/character_hairs/character_hairs40.png" }, - hair_42: { name: "hair_42", img: "resources/customisation/character_hairs/character_hairs41.png" }, - hair_43: { name: "hair_43", img: "resources/customisation/character_hairs/character_hairs42.png" }, - hair_44: { name: "hair_44", img: "resources/customisation/character_hairs/character_hairs43.png" }, - hair_45: { name: "hair_45", img: "resources/customisation/character_hairs/character_hairs44.png" }, - hair_46: { name: "hair_46", img: "resources/customisation/character_hairs/character_hairs45.png" }, - hair_47: { name: "hair_47", img: "resources/customisation/character_hairs/character_hairs46.png" }, - hair_48: { name: "hair_48", img: "resources/customisation/character_hairs/character_hairs47.png" }, - hair_49: { name: "hair_49", img: "resources/customisation/character_hairs/character_hairs48.png" }, - hair_50: { name: "hair_50", img: "resources/customisation/character_hairs/character_hairs49.png" }, - hair_51: { name: "hair_51", img: "resources/customisation/character_hairs/character_hairs50.png" }, - hair_52: { name: "hair_52", img: "resources/customisation/character_hairs/character_hairs51.png" }, - hair_53: { name: "hair_53", img: "resources/customisation/character_hairs/character_hairs52.png" }, - hair_54: { name: "hair_54", img: "resources/customisation/character_hairs/character_hairs53.png" }, - hair_55: { name: "hair_55", img: "resources/customisation/character_hairs/character_hairs54.png" }, - hair_56: { name: "hair_56", img: "resources/customisation/character_hairs/character_hairs55.png" }, - hair_57: { name: "hair_57", img: "resources/customisation/character_hairs/character_hairs56.png" }, - hair_58: { name: "hair_58", img: "resources/customisation/character_hairs/character_hairs57.png" }, - hair_59: { name: "hair_59", img: "resources/customisation/character_hairs/character_hairs58.png" }, - hair_60: { name: "hair_60", img: "resources/customisation/character_hairs/character_hairs59.png" }, - hair_61: { name: "hair_61", img: "resources/customisation/character_hairs/character_hairs60.png" }, - hair_62: { name: "hair_62", img: "resources/customisation/character_hairs/character_hairs61.png" }, - hair_63: { name: "hair_63", img: "resources/customisation/character_hairs/character_hairs62.png" }, - hair_64: { name: "hair_64", img: "resources/customisation/character_hairs/character_hairs63.png" }, - hair_65: { name: "hair_65", img: "resources/customisation/character_hairs/character_hairs64.png" }, - hair_66: { name: "hair_66", img: "resources/customisation/character_hairs/character_hairs65.png" }, - hair_67: { name: "hair_67", img: "resources/customisation/character_hairs/character_hairs66.png" }, - hair_68: { name: "hair_68", img: "resources/customisation/character_hairs/character_hairs67.png" }, - hair_69: { name: "hair_69", img: "resources/customisation/character_hairs/character_hairs68.png" }, - hair_70: { name: "hair_70", img: "resources/customisation/character_hairs/character_hairs69.png" }, - hair_71: { name: "hair_71", img: "resources/customisation/character_hairs/character_hairs70.png" }, - hair_72: { name: "hair_72", img: "resources/customisation/character_hairs/character_hairs71.png" }, - hair_73: { name: "hair_73", img: "resources/customisation/character_hairs/character_hairs72.png" }, - hair_74: { name: "hair_74", img: "resources/customisation/character_hairs/character_hairs73.png" }, -}; +interface PlayerTexturesCategory { + collections: PlayerTexturesCollection[]; + required?: boolean; +} -export const CLOTHES_RESOURCES: BodyResourceDescriptionListInterface = { - clothes_1: { name: "clothes_1", img: "resources/customisation/character_clothes/character_clothes0.png" }, - clothes_2: { name: "clothes_2", img: "resources/customisation/character_clothes/character_clothes1.png" }, - clothes_3: { name: "clothes_3", img: "resources/customisation/character_clothes/character_clothes2.png" }, - clothes_4: { name: "clothes_4", img: "resources/customisation/character_clothes/character_clothes3.png" }, - clothes_5: { name: "clothes_5", img: "resources/customisation/character_clothes/character_clothes4.png" }, - clothes_6: { name: "clothes_6", img: "resources/customisation/character_clothes/character_clothes5.png" }, - clothes_7: { name: "clothes_7", img: "resources/customisation/character_clothes/character_clothes6.png" }, - clothes_8: { name: "clothes_8", img: "resources/customisation/character_clothes/character_clothes7.png" }, - clothes_9: { name: "clothes_9", img: "resources/customisation/character_clothes/character_clothes8.png" }, - clothes_10: { name: "clothes_10", img: "resources/customisation/character_clothes/character_clothes9.png" }, - clothes_11: { name: "clothes_11", img: "resources/customisation/character_clothes/character_clothes10.png" }, - clothes_12: { name: "clothes_12", img: "resources/customisation/character_clothes/character_clothes11.png" }, - clothes_13: { name: "clothes_13", img: "resources/customisation/character_clothes/character_clothes12.png" }, - clothes_14: { name: "clothes_14", img: "resources/customisation/character_clothes/character_clothes13.png" }, - clothes_15: { name: "clothes_15", img: "resources/customisation/character_clothes/character_clothes14.png" }, - clothes_16: { name: "clothes_16", img: "resources/customisation/character_clothes/character_clothes15.png" }, - clothes_17: { name: "clothes_17", img: "resources/customisation/character_clothes/character_clothes16.png" }, - clothes_18: { name: "clothes_18", img: "resources/customisation/character_clothes/character_clothes17.png" }, - clothes_19: { name: "clothes_19", img: "resources/customisation/character_clothes/character_clothes18.png" }, - clothes_20: { name: "clothes_20", img: "resources/customisation/character_clothes/character_clothes19.png" }, - clothes_21: { name: "clothes_21", img: "resources/customisation/character_clothes/character_clothes20.png" }, - clothes_22: { name: "clothes_22", img: "resources/customisation/character_clothes/character_clothes21.png" }, - clothes_23: { name: "clothes_23", img: "resources/customisation/character_clothes/character_clothes22.png" }, - clothes_24: { name: "clothes_24", img: "resources/customisation/character_clothes/character_clothes23.png" }, - clothes_25: { name: "clothes_25", img: "resources/customisation/character_clothes/character_clothes24.png" }, - clothes_26: { name: "clothes_26", img: "resources/customisation/character_clothes/character_clothes25.png" }, - clothes_27: { name: "clothes_27", img: "resources/customisation/character_clothes/character_clothes26.png" }, - clothes_28: { name: "clothes_28", img: "resources/customisation/character_clothes/character_clothes27.png" }, - clothes_29: { name: "clothes_29", img: "resources/customisation/character_clothes/character_clothes28.png" }, - clothes_30: { name: "clothes_30", img: "resources/customisation/character_clothes/character_clothes29.png" }, - clothes_31: { name: "clothes_31", img: "resources/customisation/character_clothes/character_clothes30.png" }, - clothes_32: { name: "clothes_32", img: "resources/customisation/character_clothes/character_clothes31.png" }, - clothes_33: { name: "clothes_33", img: "resources/customisation/character_clothes/character_clothes32.png" }, - clothes_34: { name: "clothes_34", img: "resources/customisation/character_clothes/character_clothes33.png" }, - clothes_35: { name: "clothes_35", img: "resources/customisation/character_clothes/character_clothes34.png" }, - clothes_36: { name: "clothes_36", img: "resources/customisation/character_clothes/character_clothes35.png" }, - clothes_37: { name: "clothes_37", img: "resources/customisation/character_clothes/character_clothes36.png" }, - clothes_38: { name: "clothes_38", img: "resources/customisation/character_clothes/character_clothes37.png" }, - clothes_39: { name: "clothes_39", img: "resources/customisation/character_clothes/character_clothes38.png" }, - clothes_40: { name: "clothes_40", img: "resources/customisation/character_clothes/character_clothes39.png" }, - clothes_41: { name: "clothes_41", img: "resources/customisation/character_clothes/character_clothes40.png" }, - clothes_42: { name: "clothes_42", img: "resources/customisation/character_clothes/character_clothes41.png" }, - clothes_43: { name: "clothes_43", img: "resources/customisation/character_clothes/character_clothes42.png" }, - clothes_44: { name: "clothes_44", img: "resources/customisation/character_clothes/character_clothes43.png" }, - clothes_45: { name: "clothes_45", img: "resources/customisation/character_clothes/character_clothes44.png" }, - clothes_46: { name: "clothes_46", img: "resources/customisation/character_clothes/character_clothes45.png" }, - clothes_47: { name: "clothes_47", img: "resources/customisation/character_clothes/character_clothes46.png" }, - clothes_48: { name: "clothes_48", img: "resources/customisation/character_clothes/character_clothes47.png" }, - clothes_49: { name: "clothes_49", img: "resources/customisation/character_clothes/character_clothes48.png" }, - clothes_50: { name: "clothes_50", img: "resources/customisation/character_clothes/character_clothes49.png" }, - clothes_51: { name: "clothes_51", img: "resources/customisation/character_clothes/character_clothes50.png" }, - clothes_52: { name: "clothes_52", img: "resources/customisation/character_clothes/character_clothes51.png" }, - clothes_53: { name: "clothes_53", img: "resources/customisation/character_clothes/character_clothes52.png" }, - clothes_54: { name: "clothes_54", img: "resources/customisation/character_clothes/character_clothes53.png" }, - clothes_55: { name: "clothes_55", img: "resources/customisation/character_clothes/character_clothes54.png" }, - clothes_56: { name: "clothes_56", img: "resources/customisation/character_clothes/character_clothes55.png" }, - clothes_57: { name: "clothes_57", img: "resources/customisation/character_clothes/character_clothes56.png" }, - clothes_58: { name: "clothes_58", img: "resources/customisation/character_clothes/character_clothes57.png" }, - clothes_59: { name: "clothes_59", img: "resources/customisation/character_clothes/character_clothes58.png" }, - clothes_60: { name: "clothes_60", img: "resources/customisation/character_clothes/character_clothes59.png" }, - clothes_61: { name: "clothes_61", img: "resources/customisation/character_clothes/character_clothes60.png" }, - clothes_62: { name: "clothes_62", img: "resources/customisation/character_clothes/character_clothes61.png" }, - clothes_63: { name: "clothes_63", img: "resources/customisation/character_clothes/character_clothes62.png" }, - clothes_64: { name: "clothes_64", img: "resources/customisation/character_clothes/character_clothes63.png" }, - clothes_65: { name: "clothes_65", img: "resources/customisation/character_clothes/character_clothes64.png" }, - clothes_66: { name: "clothes_66", img: "resources/customisation/character_clothes/character_clothes65.png" }, - clothes_67: { name: "clothes_67", img: "resources/customisation/character_clothes/character_clothes66.png" }, - clothes_68: { name: "clothes_68", img: "resources/customisation/character_clothes/character_clothes67.png" }, - clothes_69: { name: "clothes_69", img: "resources/customisation/character_clothes/character_clothes68.png" }, - clothes_70: { name: "clothes_70", img: "resources/customisation/character_clothes/character_clothes69.png" }, - clothes_pride_shirt: { - name: "clothes_pride_shirt", - img: "resources/customisation/character_clothes/pride_shirt.png", - }, - clothes_black_hoodie: { - name: "clothes_black_hoodie", - img: "resources/customisation/character_clothes/black_hoodie.png", - }, - clothes_white_hoodie: { - name: "clothes_white_hoodie", - img: "resources/customisation/character_clothes/white_hoodie.png", - }, - clothes_engelbert: { name: "clothes_engelbert", img: "resources/customisation/character_clothes/engelbert.png" }, -}; +interface PlayerTexturesCollection { + name: string; + textures: PlayerTexturesRecord[]; +} -export const HATS_RESOURCES: BodyResourceDescriptionListInterface = { - hats_1: { name: "hats_1", img: "resources/customisation/character_hats/character_hats1.png" }, - hats_2: { name: "hats_2", img: "resources/customisation/character_hats/character_hats2.png" }, - hats_3: { name: "hats_3", img: "resources/customisation/character_hats/character_hats3.png" }, - hats_4: { name: "hats_4", img: "resources/customisation/character_hats/character_hats4.png" }, - hats_5: { name: "hats_5", img: "resources/customisation/character_hats/character_hats5.png" }, - hats_6: { name: "hats_6", img: "resources/customisation/character_hats/character_hats6.png" }, - hats_7: { name: "hats_7", img: "resources/customisation/character_hats/character_hats7.png" }, - hats_8: { name: "hats_8", img: "resources/customisation/character_hats/character_hats8.png" }, - hats_9: { name: "hats_9", img: "resources/customisation/character_hats/character_hats9.png" }, - hats_10: { name: "hats_10", img: "resources/customisation/character_hats/character_hats10.png" }, - hats_11: { name: "hats_11", img: "resources/customisation/character_hats/character_hats11.png" }, - hats_12: { name: "hats_12", img: "resources/customisation/character_hats/character_hats12.png" }, - hats_13: { name: "hats_13", img: "resources/customisation/character_hats/character_hats13.png" }, - hats_14: { name: "hats_14", img: "resources/customisation/character_hats/character_hats14.png" }, - hats_15: { name: "hats_15", img: "resources/customisation/character_hats/character_hats15.png" }, - hats_16: { name: "hats_16", img: "resources/customisation/character_hats/character_hats16.png" }, - hats_17: { name: "hats_17", img: "resources/customisation/character_hats/character_hats17.png" }, - hats_18: { name: "hats_18", img: "resources/customisation/character_hats/character_hats18.png" }, - hats_19: { name: "hats_19", img: "resources/customisation/character_hats/character_hats19.png" }, - hats_20: { name: "hats_20", img: "resources/customisation/character_hats/character_hats20.png" }, - hats_21: { name: "hats_21", img: "resources/customisation/character_hats/character_hats21.png" }, - hats_22: { name: "hats_22", img: "resources/customisation/character_hats/character_hats22.png" }, - hats_23: { name: "hats_23", img: "resources/customisation/character_hats/character_hats23.png" }, - hats_24: { name: "hats_24", img: "resources/customisation/character_hats/character_hats24.png" }, - hats_25: { name: "hats_25", img: "resources/customisation/character_hats/character_hats25.png" }, - hats_26: { name: "hats_26", img: "resources/customisation/character_hats/character_hats26.png" }, - tinfoil_hat1: { name: "tinfoil_hat1", img: "resources/customisation/character_hats/tinfoil_hat1.png" }, -}; +interface PlayerTexturesRecord { + id: string; + name: string; + url: string; +} -export const ACCESSORIES_RESOURCES: BodyResourceDescriptionListInterface = { - accessory_1: { - name: "accessory_1", - img: "resources/customisation/character_accessories/character_accessories1.png", - }, - accessory_2: { - name: "accessory_2", - img: "resources/customisation/character_accessories/character_accessories2.png", - }, - accessory_3: { - name: "accessory_3", - img: "resources/customisation/character_accessories/character_accessories3.png", - }, - accessory_4: { - name: "accessory_4", - img: "resources/customisation/character_accessories/character_accessories4.png", - }, - accessory_5: { - name: "accessory_5", - img: "resources/customisation/character_accessories/character_accessories5.png", - }, - accessory_6: { - name: "accessory_6", - img: "resources/customisation/character_accessories/character_accessories6.png", - }, - accessory_7: { - name: "accessory_7", - img: "resources/customisation/character_accessories/character_accessories7.png", - }, - accessory_8: { - name: "accessory_8", - img: "resources/customisation/character_accessories/character_accessories8.png", - }, - accessory_9: { - name: "accessory_9", - img: "resources/customisation/character_accessories/character_accessories9.png", - }, - accessory_10: { - name: "accessory_10", - img: "resources/customisation/character_accessories/character_accessories10.png", - }, - accessory_11: { - name: "accessory_11", - img: "resources/customisation/character_accessories/character_accessories11.png", - }, - accessory_12: { - name: "accessory_12", - img: "resources/customisation/character_accessories/character_accessories12.png", - }, - accessory_13: { - name: "accessory_13", - img: "resources/customisation/character_accessories/character_accessories13.png", - }, - accessory_14: { - name: "accessory_14", - img: "resources/customisation/character_accessories/character_accessories14.png", - }, - accessory_15: { - name: "accessory_15", - img: "resources/customisation/character_accessories/character_accessories15.png", - }, - accessory_16: { - name: "accessory_16", - img: "resources/customisation/character_accessories/character_accessories16.png", - }, - accessory_17: { - name: "accessory_17", - img: "resources/customisation/character_accessories/character_accessories17.png", - }, - accessory_18: { - name: "accessory_18", - img: "resources/customisation/character_accessories/character_accessories18.png", - }, - accessory_19: { - name: "accessory_19", - img: "resources/customisation/character_accessories/character_accessories19.png", - }, - accessory_20: { - name: "accessory_20", - img: "resources/customisation/character_accessories/character_accessories20.png", - }, - accessory_21: { - name: "accessory_21", - img: "resources/customisation/character_accessories/character_accessories21.png", - }, - accessory_22: { - name: "accessory_22", - img: "resources/customisation/character_accessories/character_accessories22.png", - }, - accessory_23: { - name: "accessory_23", - img: "resources/customisation/character_accessories/character_accessories23.png", - }, - accessory_24: { - name: "accessory_24", - img: "resources/customisation/character_accessories/character_accessories24.png", - }, - accessory_25: { - name: "accessory_25", - img: "resources/customisation/character_accessories/character_accessories25.png", - }, - accessory_26: { - name: "accessory_26", - img: "resources/customisation/character_accessories/character_accessories26.png", - }, - accessory_27: { - name: "accessory_27", - img: "resources/customisation/character_accessories/character_accessories27.png", - }, - accessory_28: { - name: "accessory_28", - img: "resources/customisation/character_accessories/character_accessories28.png", - }, - accessory_29: { - name: "accessory_29", - img: "resources/customisation/character_accessories/character_accessories29.png", - }, - accessory_30: { - name: "accessory_30", - img: "resources/customisation/character_accessories/character_accessories30.png", - }, - accessory_31: { - name: "accessory_31", - img: "resources/customisation/character_accessories/character_accessories31.png", - }, - accessory_32: { - name: "accessory_32", - img: "resources/customisation/character_accessories/character_accessories32.png", - }, - accessory_mate_bottle: { - name: "accessory_mate_bottle", - img: "resources/customisation/character_accessories/mate_bottle1.png", - }, - accessory_mask: { name: "accessory_mask", img: "resources/customisation/character_accessories/mask.png" }, -}; +export class PlayerTextures { + private PLAYER_RESOURCES: BodyResourceDescriptionListInterface = {}; + private COLOR_RESOURCES: BodyResourceDescriptionListInterface = {}; + private EYES_RESOURCES: BodyResourceDescriptionListInterface = {}; + private HAIR_RESOURCES: BodyResourceDescriptionListInterface = {}; + private CLOTHES_RESOURCES: BodyResourceDescriptionListInterface = {}; + private HATS_RESOURCES: BodyResourceDescriptionListInterface = {}; + private ACCESSORIES_RESOURCES: BodyResourceDescriptionListInterface = {}; + private LAYERS: BodyResourceDescriptionListInterface[] = []; -export const LAYERS: BodyResourceDescriptionListInterface[] = [ - COLOR_RESOURCES, - EYES_RESOURCES, - HAIR_RESOURCES, - CLOTHES_RESOURCES, - HATS_RESOURCES, - ACCESSORIES_RESOURCES, -]; + public loadPlayerTexturesMetadata(metadata: PlayerTexturesMetadata): void { + this.mapTexturesMetadataIntoResources(metadata); + } + + public getTexturesResources(key: PlayerTexturesKey): BodyResourceDescriptionListInterface { + switch (key) { + case PlayerTexturesKey.Accessory: + return this.ACCESSORIES_RESOURCES; + case PlayerTexturesKey.Body: + return this.COLOR_RESOURCES; + case PlayerTexturesKey.Clothes: + return this.CLOTHES_RESOURCES; + case PlayerTexturesKey.Eyes: + return this.EYES_RESOURCES; + case PlayerTexturesKey.Hair: + return this.HAIR_RESOURCES; + case PlayerTexturesKey.Hat: + return this.HATS_RESOURCES; + case PlayerTexturesKey.Woka: + return this.PLAYER_RESOURCES; + } + } + + public getLayers(): BodyResourceDescriptionListInterface[] { + return this.LAYERS; + } + + private mapTexturesMetadataIntoResources(metadata: PlayerTexturesMetadata): void { + this.PLAYER_RESOURCES = this.getMappedResources(metadata.woka); + this.COLOR_RESOURCES = this.getMappedResources(metadata.body); + this.EYES_RESOURCES = this.getMappedResources(metadata.eyes); + this.HAIR_RESOURCES = this.getMappedResources(metadata.hair); + this.CLOTHES_RESOURCES = this.getMappedResources(metadata.clothes); + this.HATS_RESOURCES = this.getMappedResources(metadata.hat); + this.ACCESSORIES_RESOURCES = this.getMappedResources(metadata.accessory); + + this.LAYERS = [ + this.COLOR_RESOURCES, + this.EYES_RESOURCES, + this.HAIR_RESOURCES, + this.CLOTHES_RESOURCES, + this.HATS_RESOURCES, + this.ACCESSORIES_RESOURCES, + ]; + } + + private getMappedResources(category: PlayerTexturesCategory): BodyResourceDescriptionListInterface { + const resources: BodyResourceDescriptionListInterface = {}; + if (!category) { + return {}; + } + for (const collection of category.collections) { + for (const texture of collection.textures) { + resources[texture.id] = { id: texture.id, img: texture.url }; + } + } + return resources; + } +} export const OBJECTS: BodyResourceDescriptionInterface[] = [ - { name: "teleportation", img: "resources/objects/teleportation.png" }, + { id: "teleportation", img: "resources/objects/teleportation.png" }, ]; diff --git a/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts b/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts index 40e68427..f3c9d273 100644 --- a/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts +++ b/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts @@ -1,6 +1,6 @@ import LoaderPlugin = Phaser.Loader.LoaderPlugin; import type { CharacterTexture } from "../../Connexion/LocalUser"; -import { BodyResourceDescriptionInterface, LAYERS, PLAYER_RESOURCES } from "./PlayerTextures"; +import { BodyResourceDescriptionInterface, mapLayerToLevel, PlayerTextures, PlayerTexturesKey } from "./PlayerTextures"; import CancelablePromise from "cancelable-promise"; export interface FrameConfig { @@ -8,33 +8,37 @@ export interface FrameConfig { frameHeight: number; } -export const loadAllLayers = (load: LoaderPlugin): BodyResourceDescriptionInterface[][] => { +export const loadAllLayers = ( + load: LoaderPlugin, + playerTextures: PlayerTextures +): BodyResourceDescriptionInterface[][] => { const returnArray: BodyResourceDescriptionInterface[][] = []; - LAYERS.forEach((layer) => { + playerTextures.getLayers().forEach((layer) => { const layerArray: BodyResourceDescriptionInterface[] = []; Object.values(layer).forEach((textureDescriptor) => { layerArray.push(textureDescriptor); - load.spritesheet(textureDescriptor.name, textureDescriptor.img, { frameWidth: 32, frameHeight: 32 }); + load.spritesheet(textureDescriptor.id, textureDescriptor.img, { frameWidth: 32, frameHeight: 32 }); }); returnArray.push(layerArray); }); return returnArray; }; -export const loadAllDefaultModels = (load: LoaderPlugin): BodyResourceDescriptionInterface[] => { - const returnArray = Object.values(PLAYER_RESOURCES); +export const loadAllDefaultModels = ( + load: LoaderPlugin, + playerTextures: PlayerTextures +): BodyResourceDescriptionInterface[] => { + const returnArray = Object.values(playerTextures.getTexturesResources(PlayerTexturesKey.Woka)); returnArray.forEach((playerResource: BodyResourceDescriptionInterface) => { - load.spritesheet(playerResource.name, playerResource.img, { frameWidth: 32, frameHeight: 32 }); + load.spritesheet(playerResource.id, playerResource.img, { frameWidth: 32, frameHeight: 32 }); }); return returnArray; }; -export const loadCustomTexture = ( +export const loadWokaTexture = ( loaderPlugin: LoaderPlugin, - texture: CharacterTexture + texture: BodyResourceDescriptionInterface ): CancelablePromise => { - const name = "customCharacterTexture" + texture.id; - const playerResourceDescriptor: BodyResourceDescriptionInterface = { name, img: texture.url, level: texture.level }; - return createLoadingPromise(loaderPlugin, playerResourceDescriptor, { + return createLoadingPromise(loaderPlugin, texture, { frameWidth: 32, frameHeight: 32, }); @@ -42,16 +46,15 @@ export const loadCustomTexture = ( export const lazyLoadPlayerCharacterTextures = ( loadPlugin: LoaderPlugin, - texturekeys: Array + textures: BodyResourceDescriptionInterface[] ): CancelablePromise => { const promisesList: CancelablePromise[] = []; - texturekeys.forEach((textureKey: string | BodyResourceDescriptionInterface) => { + textures.forEach((texture) => { try { //TODO refactor - const playerResourceDescriptor = getRessourceDescriptor(textureKey); - if (playerResourceDescriptor && !loadPlugin.textureManager.exists(playerResourceDescriptor.name)) { + if (!loadPlugin.textureManager.exists(texture.id)) { promisesList.push( - createLoadingPromise(loadPlugin, playerResourceDescriptor, { + createLoadingPromise(loadPlugin, texture, { frameWidth: 32, frameHeight: 32, }) @@ -64,58 +67,41 @@ export const lazyLoadPlayerCharacterTextures = ( let returnPromise: CancelablePromise>; if (promisesList.length > 0) { loadPlugin.start(); - returnPromise = CancelablePromise.all(promisesList).then(() => texturekeys); + returnPromise = CancelablePromise.all(promisesList).then(() => textures); } else { - returnPromise = CancelablePromise.resolve(texturekeys); + returnPromise = CancelablePromise.resolve(textures); } //If the loading fail, we render the default model instead. return returnPromise.then((keys) => keys.map((key) => { - return typeof key !== "string" ? key.name : key; + return typeof key !== "string" ? key.id : key; }) ); }; -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 playerResource = PLAYER_RESOURCES[textureName]; - if (playerResource !== undefined) return playerResource; - - for (let i = 0; i < LAYERS.length; i++) { - const playerResource = LAYERS[i][textureName]; - if (playerResource !== undefined) return playerResource; - } - throw new Error("Could not find a data for texture " + textureName); -}; - export const createLoadingPromise = ( loadPlugin: LoaderPlugin, playerResourceDescriptor: BodyResourceDescriptionInterface, frameConfig: FrameConfig ) => { return new CancelablePromise((res, rej, cancel) => { - if (loadPlugin.textureManager.exists(playerResourceDescriptor.name)) { + if (loadPlugin.textureManager.exists(playerResourceDescriptor.id)) { return res(playerResourceDescriptor); } cancel(() => { loadPlugin.off("loaderror"); - loadPlugin.off("filecomplete-spritesheet-" + playerResourceDescriptor.name); + loadPlugin.off("filecomplete-spritesheet-" + playerResourceDescriptor.id); return; }); - loadPlugin.spritesheet(playerResourceDescriptor.name, playerResourceDescriptor.img, frameConfig); + loadPlugin.spritesheet(playerResourceDescriptor.id, playerResourceDescriptor.img, frameConfig); const errorCallback = (file: { src: string }) => { if (file.src !== playerResourceDescriptor.img) return; console.error("failed loading player resource: ", playerResourceDescriptor); rej(playerResourceDescriptor); - loadPlugin.off("filecomplete-spritesheet-" + playerResourceDescriptor.name, successCallback); + loadPlugin.off("filecomplete-spritesheet-" + playerResourceDescriptor.id, successCallback); loadPlugin.off("loaderror", errorCallback); }; const successCallback = () => { @@ -123,7 +109,7 @@ export const createLoadingPromise = ( res(playerResourceDescriptor); }; - loadPlugin.once("filecomplete-spritesheet-" + playerResourceDescriptor.name, successCallback); + loadPlugin.once("filecomplete-spritesheet-" + playerResourceDescriptor.id, successCallback); loadPlugin.on("loaderror", errorCallback); }); }; diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 67d9ebfe..58fe73e8 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -18,7 +18,7 @@ import { soundManager } from "./SoundManager"; import { SharedVariablesManager } from "./SharedVariablesManager"; import { EmbeddedWebsiteManager } from "./EmbeddedWebsiteManager"; -import { lazyLoadPlayerCharacterTextures, loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager"; +import { lazyLoadPlayerCharacterTextures, loadWokaTexture } from "../Entity/PlayerTexturesLoadingManager"; import { lazyLoadCompanionResource } from "../Companion/CompanionTexturesLoadingManager"; import { iframeListener } from "../../Api/IframeListener"; import { DEBUG_MODE, JITSI_URL, MAX_PER_GROUP, POSITION_DELAY } from "../../Enum/EnvironmentVariable"; @@ -97,6 +97,8 @@ import { startLayerNamesStore } from "../../Stores/StartLayerNamesStore"; import { JitsiCoWebsite } from "../../WebRtc/CoWebsite/JitsiCoWebsite"; import { SimpleCoWebsite } from "../../WebRtc/CoWebsite/SimpleCoWebsite"; import type { CoWebsite } from "../../WebRtc/CoWebsite/CoWesbite"; +import { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures"; +import CancelablePromise from "cancelable-promise"; export interface GameSceneInitInterface { initPosition: PointInterface | null; reconnecting: boolean; @@ -244,13 +246,6 @@ export class GameScene extends DirtyScene { //initialize frame event of scripting API this.listenToIframeEvents(); - const localUser = localUserStore.getLocalUser(); - const textures = localUser?.textures; - if (textures) { - for (const texture of textures) { - loadCustomTexture(this.load, texture).catch((e) => console.error(e)); - } - } this.load.image("iconTalk", "/resources/icons/icon_talking.png"); if (touchScreenManager.supportTouchScreen) { @@ -744,6 +739,14 @@ export class GameScene extends DirtyScene { .then((onConnect: OnConnectInterface) => { this.connection = onConnect.connection; + lazyLoadPlayerCharacterTextures(this.load, onConnect.room.characterLayers) + .then((layers) => { + this.currentPlayerTexturesResolve(layers); + }) + .catch((e) => { + this.currentPlayerTexturesReject(e); + }); + playersStore.connectToRoomConnection(this.connection); userIsAdminStore.set(this.connection.hasTag("admin")); @@ -1531,7 +1534,7 @@ ${escapedMessage} this.messageSubscription?.unsubscribe(); this.userInputManager.destroy(); this.pinchManager?.destroy(); - this.emoteManager.destroy(); + this.emoteManager?.destroy(); this.cameraManager.destroy(); this.peerStoreUnsubscribe(); this.emoteUnsubscribe(); @@ -1690,16 +1693,23 @@ ${escapedMessage} } } + // The promise that will resolve to the current player texture. This will be available only after connection is established. + private currentPlayerTexturesResolve!: (value: string[]) => void; + private currentPlayerTexturesReject!: (reason: unknown) => void; + private currentPlayerTexturesPromise: CancelablePromise = new CancelablePromise((resolve, reject) => { + this.currentPlayerTexturesResolve = resolve; + this.currentPlayerTexturesReject = reject; + }); + private createCurrentPlayer() { //TODO create animation moving between exit and start - const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, this.characterLayers); try { this.CurrentPlayer = new Player( this, this.startPositionCalculator.startPosition.x, this.startPositionCalculator.startPosition.y, this.playerName, - texturesPromise, + this.currentPlayerTexturesPromise, PlayerAnimationDirections.Down, false, this.companion, diff --git a/front/src/Phaser/Login/AbstractCharacterScene.ts b/front/src/Phaser/Login/AbstractCharacterScene.ts index 67e2ba3d..b31db769 100644 --- a/front/src/Phaser/Login/AbstractCharacterScene.ts +++ b/front/src/Phaser/Login/AbstractCharacterScene.ts @@ -1,41 +1,41 @@ import { ResizableScene } from "./ResizableScene"; -import { localUserStore } from "../../Connexion/LocalUserStore"; -import type { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures"; -import { loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager"; -import type { CharacterTexture } from "../../Connexion/LocalUser"; +import { BodyResourceDescriptionInterface, PlayerTexturesKey } from "../Entity/PlayerTextures"; +import { loadWokaTexture } from "../Entity/PlayerTexturesLoadingManager"; import type CancelablePromise from "cancelable-promise"; +import { PlayerTextures } from "../Entity/PlayerTextures"; export abstract class AbstractCharacterScene extends ResizableScene { + protected playerTextures: PlayerTextures; + + constructor(params: { key: string }) { + super(params); + this.playerTextures = new PlayerTextures(); + } + loadCustomSceneSelectCharacters(): Promise { - const textures = this.getTextures(); + const textures = this.playerTextures.getTexturesResources(PlayerTexturesKey.Woka); const promises: CancelablePromise[] = []; if (textures) { - for (const texture of textures) { + for (const texture of Object.values(textures)) { if (texture.level === -1) { continue; } - promises.push(loadCustomTexture(this.load, texture)); + promises.push(loadWokaTexture(this.load, texture)); } } return Promise.all(promises); } loadSelectSceneCharacters(): Promise { - const textures = this.getTextures(); const promises: CancelablePromise[] = []; - if (textures) { - for (const texture of textures) { + for (const textures of this.playerTextures.getLayers()) { + for (const texture of Object.values(textures)) { if (texture.level !== -1) { continue; } - promises.push(loadCustomTexture(this.load, texture)); + promises.push(loadWokaTexture(this.load, texture)); } } return Promise.all(promises); } - - private getTextures(): CharacterTexture[] | undefined { - const localUser = localUserStore.getLocalUser(); - return localUser?.textures; - } } diff --git a/front/src/Phaser/Login/CustomizeScene.ts b/front/src/Phaser/Login/CustomizeScene.ts index e95eab60..918cf9cf 100644 --- a/front/src/Phaser/Login/CustomizeScene.ts +++ b/front/src/Phaser/Login/CustomizeScene.ts @@ -15,6 +15,7 @@ import { CustomizedCharacter } from "../Entity/CustomizedCharacter"; import { get } from "svelte/store"; import { analyticsClient } from "../../Administration/AnalyticsClient"; import { isMediaBreakpointUp } from "../../Utils/BreakpointsUtils"; +import { PUSHER_URL } from "../../Enum/EnvironmentVariable"; export const CustomizeSceneName = "CustomizeScene"; @@ -40,27 +41,45 @@ export class CustomizeScene extends AbstractCharacterScene { } preload() { - this.loadCustomSceneSelectCharacters() - .then((bodyResourceDescriptions) => { - bodyResourceDescriptions.forEach((bodyResourceDescription) => { - if ( - bodyResourceDescription.level == undefined || - bodyResourceDescription.level < 0 || - bodyResourceDescription.level > 5 - ) { - throw new Error("Texture level is null"); - } - this.layers[bodyResourceDescription.level].unshift(bodyResourceDescription); - }); - this.lazyloadingAttempt = true; - }) - .catch((e) => console.error(e)); + const wokaMetadataKey = "woka-list"; + this.cache.json.remove(wokaMetadataKey); + // FIXME: window.location.href is wrong. We need the URL of the main room (so we need to apply any redirect before!) + this.load.json( + wokaMetadataKey, + `${PUSHER_URL}/woka/list/` + encodeURIComponent(window.location.href), + undefined, + { + responseType: "text", + headers: { + Authorization: localUserStore.getAuthToken() ?? "", + }, + withCredentials: true, + } + ); + this.load.once(`filecomplete-json-${wokaMetadataKey}`, () => { + this.playerTextures.loadPlayerTexturesMetadata(this.cache.json.get(wokaMetadataKey)); + this.loadCustomSceneSelectCharacters() + .then((bodyResourceDescriptions) => { + bodyResourceDescriptions.forEach((bodyResourceDescription) => { + if ( + bodyResourceDescription.level == undefined || + bodyResourceDescription.level < 0 || + bodyResourceDescription.level > 5 + ) { + throw new Error("Texture level is null"); + } + this.layers[bodyResourceDescription.level].unshift(bodyResourceDescription); + }); + this.lazyloadingAttempt = true; + }) + .catch((e) => console.error(e)); - this.layers = loadAllLayers(this.load); - this.lazyloadingAttempt = false; + this.layers = loadAllLayers(this.load, this.playerTextures); + this.lazyloadingAttempt = false; - //this function must stay at the end of preload function - this.loader.addLoader(); + //this function must stay at the end of preload function + this.loader.addLoader(); + }); } create() { @@ -192,13 +211,13 @@ export class CustomizeScene extends AbstractCharacterScene { const children: Array = new Array(); for (let j = 0; j <= layerNumber; j++) { if (j === layerNumber) { - children.push(this.layers[j][selectedItem].name); + children.push(this.layers[j][selectedItem].id); } else { const layer = this.selectedLayers[j]; if (layer === undefined) { continue; } - children.push(this.layers[j][layer].name); + children.push(this.layers[j][layer].id); } } return children; @@ -276,7 +295,7 @@ export class CustomizeScene extends AbstractCharacterScene { let i = 0; for (const layerItem of this.selectedLayers) { if (layerItem !== undefined) { - layers.push(this.layers[i][layerItem].name); + layers.push(this.layers[i][layerItem].id); } i++; } @@ -287,14 +306,14 @@ export class CustomizeScene extends AbstractCharacterScene { analyticsClient.validationWoka("CustomizeWoka"); gameManager.setCharacterLayers(layers); - this.scene.sleep(CustomizeSceneName); + this.scene.stop(CustomizeSceneName); waScaleManager.restoreZoom(); gameManager.tryResumingGame(EnableCameraSceneName); customCharacterSceneVisibleStore.set(false); } public backToPreviousScene() { - this.scene.sleep(CustomizeSceneName); + this.scene.stop(CustomizeSceneName); waScaleManager.restoreZoom(); this.scene.run(SelectCharacterSceneName); customCharacterSceneVisibleStore.set(false); diff --git a/front/src/Phaser/Login/EntryScene.ts b/front/src/Phaser/Login/EntryScene.ts index f3ab3a08..d86e3a2e 100644 --- a/front/src/Phaser/Login/EntryScene.ts +++ b/front/src/Phaser/Login/EntryScene.ts @@ -7,6 +7,8 @@ import { ReconnectingTextures } from "../Reconnecting/ReconnectingScene"; import LL from "../../i18n/i18n-svelte"; import { get } from "svelte/store"; import { localeDetector } from "../../i18n/locales"; +import { PlayerTextures } from "../Entity/PlayerTextures"; +import { PUSHER_URL } from "../../Enum/EnvironmentVariable"; export const EntrySceneName = "EntryScene"; @@ -15,6 +17,8 @@ export const EntrySceneName = "EntryScene"; * and to route to the next correct scene. */ export class EntryScene extends Scene { + private localeLoaded: boolean = false; + constructor() { super({ key: EntrySceneName, @@ -30,6 +34,10 @@ export class EntryScene extends Scene { } create() { + this.loadLocale(); + } + + private loadLocale(): void { localeDetector() .then(() => { gameManager diff --git a/front/src/Phaser/Login/SelectCharacterScene.ts b/front/src/Phaser/Login/SelectCharacterScene.ts index 1fceee39..f7fd3c8a 100644 --- a/front/src/Phaser/Login/SelectCharacterScene.ts +++ b/front/src/Phaser/Login/SelectCharacterScene.ts @@ -5,7 +5,7 @@ import { CustomizeSceneName } from "./CustomizeScene"; import { localUserStore } from "../../Connexion/LocalUserStore"; import { loadAllDefaultModels } from "../Entity/PlayerTexturesLoadingManager"; import { Loader } from "../Components/Loader"; -import type { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures"; +import { BodyResourceDescriptionInterface, PlayerTextures } from "../Entity/PlayerTextures"; import { AbstractCharacterScene } from "./AbstractCharacterScene"; import { areCharacterLayersValid } from "../../Connexion/LocalUser"; import { touchScreenManager } from "../../Touch/TouchScreenManager"; @@ -14,6 +14,8 @@ import { selectCharacterSceneVisibleStore } from "../../Stores/SelectCharacterSt import { waScaleManager } from "../Services/WaScaleManager"; import { analyticsClient } from "../../Administration/AnalyticsClient"; import { isMediaBreakpointUp } from "../../Utils/BreakpointsUtils"; +import { PUSHER_URL } from "../../Enum/EnvironmentVariable"; +import { customizeAvailableStore } from "../../Stores/SelectCharacterSceneStore"; //todo: put this constants in a dedicated file export const SelectCharacterSceneName = "SelectCharacterScene"; @@ -38,25 +40,46 @@ export class SelectCharacterScene extends AbstractCharacterScene { key: SelectCharacterSceneName, }); this.loader = new Loader(this); + this.playerTextures = new PlayerTextures(); } preload() { - this.loadSelectSceneCharacters() - .then((bodyResourceDescriptions) => { - bodyResourceDescriptions.forEach((bodyResourceDescription) => { - this.playerModels.push(bodyResourceDescription); - }); - this.lazyloadingAttempt = true; - }) - .catch((e) => console.error(e)); - this.playerModels = loadAllDefaultModels(this.load); - this.lazyloadingAttempt = false; + const wokaMetadataKey = "woka-list"; + this.cache.json.remove(wokaMetadataKey); - //this function must stay at the end of preload function - this.loader.addLoader(); + // FIXME: window.location.href is wrong. We need the URL of the main room (so we need to apply any redirect before!) + this.load.json( + wokaMetadataKey, + `${PUSHER_URL}/woka/list/` + encodeURIComponent(window.location.href), + undefined, + { + responseType: "text", + headers: { + Authorization: localUserStore.getAuthToken() ?? "", + }, + withCredentials: true, + } + ); + this.load.once(`filecomplete-json-${wokaMetadataKey}`, () => { + this.playerTextures.loadPlayerTexturesMetadata(this.cache.json.get(wokaMetadataKey)); + this.loadSelectSceneCharacters() + .then((bodyResourceDescriptions) => { + bodyResourceDescriptions.forEach((bodyResourceDescription) => { + this.playerModels.push(bodyResourceDescription); + }); + this.lazyloadingAttempt = true; + }) + .catch((e) => console.error(e)); + this.playerModels = loadAllDefaultModels(this.load, this.playerTextures); + this.lazyloadingAttempt = false; + + //this function must stay at the end of preload function + this.loader.addLoader(); + }); } create() { + customizeAvailableStore.set(this.isCustomizationAvailable()); selectCharacterSceneVisibleStore.set(true); this.events.addListener("wake", () => { waScaleManager.saveZoom(); @@ -130,16 +153,16 @@ export class SelectCharacterScene extends AbstractCharacterScene { const playerResource = this.playerModels[i]; //check already exist texture - if (this.players.find((c) => c.texture.key === playerResource.name)) { + if (this.players.find((c) => c.texture.key === playerResource.id)) { continue; } const [middleX, middleY] = this.getCharacterPosition(); - const player = this.physics.add.sprite(middleX, middleY, playerResource.name, 0); + const player = this.physics.add.sprite(middleX, middleY, playerResource.id, 0); this.setUpPlayer(player, i); this.anims.create({ - key: playerResource.name, - frames: this.anims.generateFrameNumbers(playerResource.name, { start: 0, end: 11 }), + key: playerResource.id, + frames: this.anims.generateFrameNumbers(playerResource.id, { start: 0, end: 11 }), frameRate: 8, repeat: -1, }); @@ -164,7 +187,7 @@ export class SelectCharacterScene extends AbstractCharacterScene { this.currentSelectUser = 0; } this.selectedPlayer = this.players[this.currentSelectUser]; - this.selectedPlayer.play(this.playerModels[this.currentSelectUser].name); + this.selectedPlayer.play(this.playerModels[this.currentSelectUser].id); } protected moveUser() { @@ -247,9 +270,9 @@ export class SelectCharacterScene extends AbstractCharacterScene { } protected updateSelectedPlayer(): void { - this.selectedPlayer?.anims.pause(this.selectedPlayer?.anims.currentAnim.frames[0]); + this.selectedPlayer?.anims?.pause(this.selectedPlayer?.anims.currentAnim.frames[0]); const player = this.players[this.currentSelectUser]; - player.play(this.playerModels[this.currentSelectUser].name); + player?.play(this.playerModels[this.currentSelectUser].id); this.selectedPlayer = player; localUserStore.setPlayerCharacterIndex(this.currentSelectUser); } @@ -274,4 +297,13 @@ export class SelectCharacterScene extends AbstractCharacterScene { //move position of user this.moveUser(); } + + private isCustomizationAvailable(): boolean { + for (const layer of this.playerTextures.getLayers()) { + if (Object.keys(layer).length > 0) { + return true; + } + } + return false; + } } diff --git a/front/src/Stores/SelectCharacterSceneStore.ts b/front/src/Stores/SelectCharacterSceneStore.ts new file mode 100644 index 00000000..654da03c --- /dev/null +++ b/front/src/Stores/SelectCharacterSceneStore.ts @@ -0,0 +1,3 @@ +import { writable } from "svelte/store"; + +export const customizeAvailableStore = writable(false); diff --git a/front/tests/Phaser/Game/PlayerTexturesLoadingTest.ts b/front/tests/Phaser/Game/PlayerTexturesLoadingTest.ts deleted file mode 100644 index 6c984a9e..00000000 --- a/front/tests/Phaser/Game/PlayerTexturesLoadingTest.ts +++ /dev/null @@ -1,28 +0,0 @@ -import "jasmine"; -import { getRessourceDescriptor } from "../../../src/Phaser/Entity/PlayerTexturesLoadingManager"; - -describe("getRessourceDescriptor()", () => { - it(", if given a valid descriptor as parameter, should return it", () => { - const desc = getRessourceDescriptor({ name: "name", img: "url" }); - expect(desc.name).toEqual("name"); - expect(desc.img).toEqual("url"); - }); - - it(", if given a string as parameter, should search through hardcoded values", () => { - const desc = getRessourceDescriptor("male1"); - expect(desc.name).toEqual("male1"); - expect(desc.img).toEqual("resources/characters/pipoya/Male 01-1.png"); - }); - - it(", if given a string as parameter, should search through hardcoded values (bis)", () => { - const desc = getRessourceDescriptor("color_2"); - expect(desc.name).toEqual("color_2"); - expect(desc.img).toEqual("resources/customisation/character_color/character_color1.png"); - }); - - it(", if given a descriptor without url as parameter, should search through hardcoded values", () => { - const desc = getRessourceDescriptor({ name: "male1", img: "" }); - expect(desc.name).toEqual("male1"); - expect(desc.img).toEqual("resources/characters/pipoya/Male 01-1.png"); - }); -}); diff --git a/messages/JsonMessages/AdminApiData.ts b/messages/JsonMessages/AdminApiData.ts index 314963be..236c6355 100644 --- a/messages/JsonMessages/AdminApiData.ts +++ b/messages/JsonMessages/AdminApiData.ts @@ -1,5 +1,4 @@ import * as tg from "generic-type-guard"; -import { isCharacterTexture } from "./CharacterTexture"; /* * WARNING! The original file is in /messages/JsonMessages. @@ -12,7 +11,6 @@ export const isAdminApiData = new tg.IsInterface() email: tg.isNullable(tg.isString), roomUrl: tg.isString, mapUrlStart: tg.isString, - textures: tg.isArray(isCharacterTexture), }) .withOptionalProperties({ messages: tg.isArray(tg.isUnknown), diff --git a/messages/JsonMessages/CharacterTexture.ts b/messages/JsonMessages/CharacterTexture.ts deleted file mode 100644 index eb2ec15e..00000000 --- a/messages/JsonMessages/CharacterTexture.ts +++ /dev/null @@ -1,16 +0,0 @@ -import * as tg from "generic-type-guard"; - -/* - * WARNING! The original file is in /messages/JsonMessages. - * All other files are automatically copied from this file on container startup / build - */ - -export const isCharacterTexture = new tg.IsInterface() - .withProperties({ - id: tg.isNumber, - level: tg.isNumber, - url: tg.isString, - rights: tg.isString, - }) - .get(); -export type CharacterTexture = tg.GuardedType; diff --git a/messages/JsonMessages/MapDetailsData.ts b/messages/JsonMessages/MapDetailsData.ts index 39866eee..09500b80 100644 --- a/messages/JsonMessages/MapDetailsData.ts +++ b/messages/JsonMessages/MapDetailsData.ts @@ -1,5 +1,4 @@ import * as tg from "generic-type-guard"; -import { isCharacterTexture } from "./CharacterTexture"; import { isNumber } from "generic-type-guard"; /* @@ -12,7 +11,6 @@ export const isMapDetailsData = new tg.IsInterface() mapUrl: tg.isString, policy_type: isNumber, //isNumericEnum(GameRoomPolicyTypes), tags: tg.isArray(tg.isString), - textures: tg.isArray(isCharacterTexture), authenticationMandatory: tg.isUnion(tg.isNullable(tg.isBoolean), tg.isUndefined), roomSlug: tg.isNullable(tg.isString), // deprecated contactPage: tg.isNullable(tg.isString), diff --git a/messages/JsonMessages/RegisterData.ts b/messages/JsonMessages/RegisterData.ts index 473ee592..de1b2ca7 100644 --- a/messages/JsonMessages/RegisterData.ts +++ b/messages/JsonMessages/RegisterData.ts @@ -1,5 +1,5 @@ import * as tg from "generic-type-guard"; -import { isCharacterTexture } from "./CharacterTexture"; +//import { isCharacterTexture } from "./CharacterTexture"; /* * WARNING! The original file is in /messages/JsonMessages. @@ -13,7 +13,6 @@ export const isRegisterData = new tg.IsInterface() organizationMemberToken: tg.isNullable(tg.isString), mapUrlStart: tg.isString, userUuid: tg.isString, - textures: tg.isArray(isCharacterTexture), authToken: tg.isString, }) .withOptionalProperties({ diff --git a/messages/package.json b/messages/package.json index be11d915..524f9996 100644 --- a/messages/package.json +++ b/messages/package.json @@ -9,9 +9,10 @@ "copy-to-front-ts-proto": "sed 's/import { Observable } from \"rxjs\";/import type { Observable } from \"rxjs\";/g' ts-proto-generated/protos/messages.ts > ../front/src/Messages/ts-proto-generated/messages.ts", "copy-to-pusher": "rm -rf ../pusher/src/Messages/generated && cp -rf generated/ ../pusher/src/Messages/generated", "json-copy-to-pusher": "rm -rf ../pusher/src/Messages/JsonMessages/* && cp -rf JsonMessages/* ../pusher/src/Messages/JsonMessages/", + "json-copy-to-back": "rm -rf ../back/src/Messages/JsonMessages/* && cp -rf JsonMessages/* ../back/src/Messages/JsonMessages/", "json-copy-to-front": "rm -rf ../front/src/Messages/JsonMessages/* && cp -rf JsonMessages/* ../front/src/Messages/JsonMessages/", "precommit": "lint-staged", - "proto-all": "yarn run proto && yarn run ts-proto && yarn run copy-to-back && yarn run copy-to-front-ts-proto && yarn run copy-to-pusher && yarn run json-copy-to-pusher && yarn run json-copy-to-front", + "proto-all": "yarn run proto && yarn run ts-proto && yarn run copy-to-back && yarn run copy-to-front-ts-proto && yarn run copy-to-pusher && yarn run json-copy-to-pusher && yarn run json-copy-to-back && yarn run json-copy-to-front", "proto:watch": "yarn run proto-all; inotifywait -q -m -e close_write protos/messages.proto JsonMessages/ | while read -r filename event; do yarn run proto-all; done", "pretty": "yarn prettier --write 'JsonMessages/**/*.ts'", "pretty-check": "yarn prettier --check 'JsonMessages/**/*.ts'" diff --git a/messages/protos/messages.proto b/messages/protos/messages.proto index 8ac7bbf0..d0768480 100644 --- a/messages/protos/messages.proto +++ b/messages/protos/messages.proto @@ -34,6 +34,7 @@ message SilentMessage { message CharacterLayerMessage { string url = 1; string name = 2; + string layer = 3; } message CompanionMessage { @@ -223,6 +224,8 @@ message RoomJoinedMessage { repeated string tag = 5; repeated VariableMessage variable = 6; string userRoomToken = 7; + // We send the current skin of the current player. + repeated CharacterLayerMessage characterLayer = 8; } message WebRtcStartMessage { @@ -274,6 +277,8 @@ message WorldFullMessage{ } message TokenExpiredMessage{ } +message InvalidTextureMessage{ +} message WorldConnexionMessage{ string message = 2; @@ -310,6 +315,7 @@ message ServerToClientMessage { FollowRequestMessage followRequestMessage = 21; FollowConfirmationMessage followConfirmationMessage = 22; FollowAbortMessage followAbortMessage = 23; + InvalidTextureMessage invalidTextureMessage = 24; } } diff --git a/pusher/data/woka.json b/pusher/data/woka.json new file mode 100644 index 00000000..04cc9cf7 --- /dev/null +++ b/pusher/data/woka.json @@ -0,0 +1,1851 @@ +{ + "woka": { + "collections": [ + { + "name": "default", + "position": 0, + "textures": [ + { + "id": "male1", + "name": "male1", + "url": "resources/characters/pipoya/Male 01-1.png", + "position": 0 + }, + { + "id": "male2", + "name": "male2", + "url": "resources/characters/pipoya/Male 02-2.png", + "position": 1 + }, + { + "id": "male3", + "name": "male3", + "url": "resources/characters/pipoya/Male 03-4.png", + "position": 2 + }, + { + "id": "male4", + "name": "male4", + "url": "resources/characters/pipoya/Male 09-1.png", + "position": 3 + }, + { + "id": "male5", + "name": "male5", + "url": "resources/characters/pipoya/Male 10-3.png", + "position": 4 + }, + { + "id": "male6", + "name": "male6", + "url": "resources/characters/pipoya/Male 17-2.png", + "position": 5 + }, + { + "id": "male7", + "name": "male7", + "url": "resources/characters/pipoya/Male 18-1.png", + "position": 6 + }, + { + "id": "male8", + "name": "male8", + "url": "resources/characters/pipoya/Male 16-4.png", + "position": 7 + }, + { + "id": "male9", + "name": "male9", + "url": "resources/characters/pipoya/Male 07-2.png", + "position": 8 + }, + { + "id": "male10", + "name": "male10", + "url": "resources/characters/pipoya/Male 05-3.png", + "position": 9 + }, + { + "id": "male11", + "name": "male11", + "url": "resources/characters/pipoya/Teacher male 02.png", + "position": 10 + }, + { + "id": "male12", + "name": "male12", + "url": "resources/characters/pipoya/su4 Student male 12.png", + "position": 11 + }, + { + "id": "female1", + "name": "female1", + "url": "resources/characters/pipoya/Female 01-1.png", + "position": 12 + }, + { + "id": "female2", + "name": "female2", + "url": "resources/characters/pipoya/Female 02-2.png", + "position": 13 + }, + { + "id": "female3", + "name": "female3", + "url": "resources/characters/pipoya/Female 03-4.png", + "position": 14 + }, + { + "id": "female4", + "name": "female4", + "url": "resources/characters/pipoya/Female 09-1.png", + "position": 15 + }, + { + "id": "female5", + "name": "female5", + "url": "resources/characters/pipoya/Female 10-3.png", + "position": 16 + }, + { + "id": "female6", + "name": "female6", + "url": "resources/characters/pipoya/Female 17-2.png", + "position": 17 + }, + { + "id": "female7", + "name": "female7", + "url": "resources/characters/pipoya/Female 18-1.png", + "position": 18 + }, + { + "id": "female8", + "name": "female8", + "url": "resources/characters/pipoya/Female 16-4.png", + "position": 19 + }, + { + "id": "female9", + "name": "female9", + "url": "resources/characters/pipoya/Female 07-2.png", + "position": 20 + }, + { + "id": "female10", + "name": "female10", + "url": "resources/characters/pipoya/Female 05-3.png", + "position": 21 + }, + { + "id": "female11", + "name": "female11", + "url": "resources/characters/pipoya/Teacher fmale 02.png", + "position": 22 + }, + { + "id": "female12", + "name": "female12", + "url": "resources/characters/pipoya/su4 Student fmale 12.png", + "position": 23 + } + ] + } + ] + }, + "body": { + "required": true, + "collections": [ + { + "name": "default", + "position": 0, + "textures": [ + { + "id": "body1", + "name": "body1", + "url": "resources/customisation/character_color/character_color0.png", + "position": 0 + }, + { + "id": "body2", + "name": "body2", + "url": "resources/customisation/character_color/character_color1.png", + "position": 1 + }, + { + "id": "body3", + "name": "body3", + "url": "resources/customisation/character_color/character_color2.png", + "position": 2 + }, + { + "id": "body4", + "name": "body4", + "url": "resources/customisation/character_color/character_color3.png", + "position": 3 + }, + { + "id": "body5", + "name": "body5", + "url": "resources/customisation/character_color/character_color4.png", + "position": 4 + }, + { + "id": "body6", + "name": "body6", + "url": "resources/customisation/character_color/character_color5.png", + "position": 5 + }, + { + "id": "body7", + "name": "body7", + "url": "resources/customisation/character_color/character_color6.png", + "position": 6 + }, + { + "id": "body8", + "name": "body8", + "url": "resources/customisation/character_color/character_color7.png", + "position": 7 + }, + { + "id": "body9", + "name": "body9", + "url": "resources/customisation/character_color/character_color8.png", + "position": 8 + }, + { + "id": "body10", + "name": "body10", + "url": "resources/customisation/character_color/character_color9.png", + "position": 9 + }, + { + "id": "body11", + "name": "body11", + "url": "resources/customisation/character_color/character_color10.png", + "position": 10 + }, + { + "id": "body12", + "name": "body12", + "url": "resources/customisation/character_color/character_color11.png", + "position": 11 + }, + { + "id": "body13", + "name": "body13", + "url": "resources/customisation/character_color/character_color12.png", + "position": 12 + }, + { + "id": "body14", + "name": "body14", + "url": "resources/customisation/character_color/character_color13.png", + "position": 13 + }, + { + "id": "body15", + "name": "body15", + "url": "resources/customisation/character_color/character_color14.png", + "position": 14 + }, + { + "id": "body16", + "name": "body16", + "url": "resources/customisation/character_color/character_color15.png", + "position": 15 + }, + { + "id": "body17", + "name": "body17", + "url": "resources/customisation/character_color/character_color16.png", + "position": 16 + }, + { + "id": "body18", + "name": "body18", + "url": "resources/customisation/character_color/character_color17.png", + "position": 17 + }, + { + "id": "body19", + "name": "body19", + "url": "resources/customisation/character_color/character_color18.png", + "position": 18 + }, + { + "id": "body20", + "name": "body20", + "url": "resources/customisation/character_color/character_color19.png", + "position": 19 + }, + { + "id": "body21", + "name": "body21", + "url": "resources/customisation/character_color/character_color20.png", + "position": 20 + }, + { + "id": "body22", + "name": "body22", + "url": "resources/customisation/character_color/character_color21.png", + "position": 21 + }, + { + "id": "body23", + "name": "body23", + "url": "resources/customisation/character_color/character_color22.png", + "position": 22 + }, + { + "id": "body24", + "name": "body24", + "url": "resources/customisation/character_color/character_color23.png", + "position": 23 + }, + { + "id": "body25", + "name": "body25", + "url": "resources/customisation/character_color/character_color24.png", + "position": 24 + }, + { + "id": "body26", + "name": "body26", + "url": "resources/customisation/character_color/character_color25.png", + "position": 25 + }, + { + "id": "body27", + "name": "body27", + "url": "resources/customisation/character_color/character_color26.png", + "position": 26 + }, + { + "id": "body28", + "name": "body28", + "url": "resources/customisation/character_color/character_color27.png", + "position": 27 + }, + { + "id": "body29", + "name": "body29", + "url": "resources/customisation/character_color/character_color28.png", + "position": 28 + }, + { + "id": "body30", + "name": "body30", + "url": "resources/customisation/character_color/character_color29.png", + "position": 29 + }, + { + "id": "body31", + "name": "body31", + "url": "resources/customisation/character_color/character_color30.png", + "position": 30 + }, + { + "id": "body32", + "name": "body32", + "url": "resources/customisation/character_color/character_color31.png", + "position": 31 + }, + { + "id": "body33", + "name": "body33", + "url": "resources/customisation/character_color/character_color32.png", + "position": 32 + } + ] + } + ] + }, + "eyes": { + "required": true, + "collections": [ + { + "name": "default", + "position": 0, + "textures": [ + { + "id": "eyes1", + "name": "eyes1", + "url": "resources/customisation/character_eyes/character_eyes1.png", + "position": 0 + }, + { + "id": "eyes2", + "name": "eyes2", + "url": "resources/customisation/character_eyes/character_eyes2.png", + "position": 1 + }, + { + "id": "eyes3", + "name": "eyes3", + "url": "resources/customisation/character_eyes/character_eyes3.png", + "position": 2 + }, + { + "id": "eyes4", + "name": "eyes4", + "url": "resources/customisation/character_eyes/character_eyes4.png", + "position": 3 + }, + { + "id": "eyes5", + "name": "eyes5", + "url": "resources/customisation/character_eyes/character_eyes5.png", + "position": 4 + }, + { + "id": "eyes6", + "name": "eyes6", + "url": "resources/customisation/character_eyes/character_eyes6.png", + "position": 5 + }, + { + "id": "eyes7", + "name": "eyes7", + "url": "resources/customisation/character_eyes/character_eyes7.png", + "position": 6 + }, + { + "id": "eyes8", + "name": "eyes8", + "url": "resources/customisation/character_eyes/character_eyes8.png", + "position": 7 + }, + { + "id": "eyes9", + "name": "eyes9", + "url": "resources/customisation/character_eyes/character_eyes9.png", + "position": 8 + }, + { + "id": "eyes10", + "name": "eyes10", + "url": "resources/customisation/character_eyes/character_eyes10.png", + "position": 9 + }, + { + "id": "eyes11", + "name": "eyes11", + "url": "resources/customisation/character_eyes/character_eyes11.png", + "position": 10 + }, + { + "id": "eyes12", + "name": "eyes12", + "url": "resources/customisation/character_eyes/character_eyes12.png", + "position": 11 + }, + { + "id": "eyes13", + "name": "eyes13", + "url": "resources/customisation/character_eyes/character_eyes13.png", + "position": 12 + }, + { + "id": "eyes14", + "name": "eyes14", + "url": "resources/customisation/character_eyes/character_eyes14.png", + "position": 13 + }, + { + "id": "eyes15", + "name": "eyes15", + "url": "resources/customisation/character_eyes/character_eyes15.png", + "position": 14 + }, + { + "id": "eyes16", + "name": "eyes16", + "url": "resources/customisation/character_eyes/character_eyes16.png", + "position": 15 + }, + { + "id": "eyes17", + "name": "eyes17", + "url": "resources/customisation/character_eyes/character_eyes17.png", + "position": 16 + }, + { + "id": "eyes18", + "name": "eyes18", + "url": "resources/customisation/character_eyes/character_eyes18.png", + "position": 17 + }, + { + "id": "eyes19", + "name": "eyes19", + "url": "resources/customisation/character_eyes/character_eyes19.png", + "position": 18 + }, + { + "id": "eyes20", + "name": "eyes20", + "url": "resources/customisation/character_eyes/character_eyes20.png", + "position": 19 + }, + { + "id": "eyes21", + "name": "eyes21", + "url": "resources/customisation/character_eyes/character_eyes21.png", + "position": 20 + }, + { + "id": "eyes22", + "name": "eyes22", + "url": "resources/customisation/character_eyes/character_eyes22.png", + "position": 21 + }, + { + "id": "eyes23", + "name": "eyes23", + "url": "resources/customisation/character_eyes/character_eyes23.png", + "position": 22 + }, + { + "id": "eyes24", + "name": "eyes24", + "url": "resources/customisation/character_eyes/character_eyes24.png", + "position": 23 + }, + { + "id": "eyes25", + "name": "eyes25", + "url": "resources/customisation/character_eyes/character_eyes25.png", + "position": 24 + }, + { + "id": "eyes26", + "name": "eyes26", + "url": "resources/customisation/character_eyes/character_eyes26.png", + "position": 25 + }, + { + "id": "eyes27", + "name": "eyes27", + "url": "resources/customisation/character_eyes/character_eyes27.png", + "position": 26 + }, + { + "id": "eyes28", + "name": "eyes28", + "url": "resources/customisation/character_eyes/character_eyes28.png", + "position": 27 + }, + { + "id": "eyes29", + "name": "eyes29", + "url": "resources/customisation/character_eyes/character_eyes29.png", + "position": 28 + }, + { + "id": "eyes30", + "name": "eyes30", + "url": "resources/customisation/character_eyes/character_eyes30.png", + "position": 29 + } + ] + } + ] + }, + "hair": { + "collections": [ + { + "name": "default", + "position": 0, + "textures": [ + { + "id": "hair1", + "name": "hair1", + "url": "resources/customisation/character_hairs/character_hairs0.png", + "position": 0 + }, + { + "id": "hair2", + "name": "hair2", + "url": "resources/customisation/character_hairs/character_hairs1.png", + "position": 1 + }, + { + "id": "hair3", + "name": "hair3", + "url": "resources/customisation/character_hairs/character_hairs2.png", + "position": 2 + }, + { + "id": "hair4", + "name": "hair4", + "url": "resources/customisation/character_hairs/character_hairs3.png", + "position": 3 + }, + { + "id": "hair5", + "name": "hair5", + "url": "resources/customisation/character_hairs/character_hairs4.png", + "position": 4 + }, + { + "id": "hair6", + "name": "hair6", + "url": "resources/customisation/character_hairs/character_hairs5.png", + "position": 5 + }, + { + "id": "hair7", + "name": "hair7", + "url": "resources/customisation/character_hairs/character_hairs6.png", + "position": 6 + }, + { + "id": "hair8", + "name": "hair8", + "url": "resources/customisation/character_hairs/character_hairs7.png", + "position": 7 + }, + { + "id": "hair9", + "name": "hair9", + "url": "resources/customisation/character_hairs/character_hairs8.png", + "position": 8 + }, + { + "id": "hair10", + "name": "hair10", + "url": "resources/customisation/character_hairs/character_hairs9.png", + "position": 9 + }, + { + "id": "hair11", + "name": "hair11", + "url": "resources/customisation/character_hairs/character_hairs10.png", + "position": 10 + }, + { + "id": "hair12", + "name": "hair12", + "url": "resources/customisation/character_hairs/character_hairs11.png", + "position": 11 + }, + { + "id": "hair13", + "name": "hair13", + "url": "resources/customisation/character_hairs/character_hairs12.png", + "position": 12 + }, + { + "id": "hair14", + "name": "hair14", + "url": "resources/customisation/character_hairs/character_hairs13.png", + "position": 13 + }, + { + "id": "hair15", + "name": "hair15", + "url": "resources/customisation/character_hairs/character_hairs14.png", + "position": 14 + }, + { + "id": "hair16", + "name": "hair16", + "url": "resources/customisation/character_hairs/character_hairs15.png", + "position": 15 + }, + { + "id": "hair17", + "name": "hair17", + "url": "resources/customisation/character_hairs/character_hairs16.png", + "position": 16 + }, + { + "id": "hair18", + "name": "hair18", + "url": "resources/customisation/character_hairs/character_hairs17.png", + "position": 17 + }, + { + "id": "hair19", + "name": "hair19", + "url": "resources/customisation/character_hairs/character_hairs18.png", + "position": 18 + }, + { + "id": "hair20", + "name": "hair20", + "url": "resources/customisation/character_hairs/character_hairs19.png", + "position": 19 + }, + { + "id": "hair21", + "name": "hair21", + "url": "resources/customisation/character_hairs/character_hairs20.png", + "position": 20 + }, + { + "id": "hair22", + "name": "hair22", + "url": "resources/customisation/character_hairs/character_hairs21.png", + "position": 21 + }, + { + "id": "hair23", + "name": "hair23", + "url": "resources/customisation/character_hairs/character_hairs22.png", + "position": 22 + }, + { + "id": "hair24", + "name": "hair24", + "url": "resources/customisation/character_hairs/character_hairs23.png", + "position": 23 + }, + { + "id": "hair25", + "name": "hair25", + "url": "resources/customisation/character_hairs/character_hairs24.png", + "position": 24 + }, + { + "id": "hair26", + "name": "hair26", + "url": "resources/customisation/character_hairs/character_hairs25.png", + "position": 25 + }, + { + "id": "hair27", + "name": "hair27", + "url": "resources/customisation/character_hairs/character_hairs26.png", + "position": 26 + }, + { + "id": "hair28", + "name": "hair28", + "url": "resources/customisation/character_hairs/character_hairs27.png", + "position": 27 + }, + { + "id": "hair29", + "name": "hair29", + "url": "resources/customisation/character_hairs/character_hairs28.png", + "position": 28 + }, + { + "id": "hair30", + "name": "hair30", + "url": "resources/customisation/character_hairs/character_hairs29.png", + "position": 29 + }, + { + "id": "hair31", + "name": "hair31", + "url": "resources/customisation/character_hairs/character_hairs30.png", + "position": 30 + }, + { + "id": "hair32", + "name": "hair32", + "url": "resources/customisation/character_hairs/character_hairs31.png", + "position": 31 + }, + { + "id": "hair33", + "name": "hair33", + "url": "resources/customisation/character_hairs/character_hairs32.png", + "position": 32 + }, + { + "id": "hair34", + "name": "hair34", + "url": "resources/customisation/character_hairs/character_hairs33.png", + "position": 33 + }, + { + "id": "hair35", + "name": "hair35", + "url": "resources/customisation/character_hairs/character_hairs34.png", + "position": 34 + }, + { + "id": "hair36", + "name": "hair36", + "url": "resources/customisation/character_hairs/character_hairs35.png", + "position": 35 + }, + { + "id": "hair37", + "name": "hair37", + "url": "resources/customisation/character_hairs/character_hairs36.png", + "position": 36 + }, + { + "id": "hair38", + "name": "hair38", + "url": "resources/customisation/character_hairs/character_hairs37.png", + "position": 37 + }, + { + "id": "hair39", + "name": "hair39", + "url": "resources/customisation/character_hairs/character_hairs38.png", + "position": 38 + }, + { + "id": "hair40", + "name": "hair40", + "url": "resources/customisation/character_hairs/character_hairs39.png", + "position": 39 + }, + { + "id": "hair41", + "name": "hair41", + "url": "resources/customisation/character_hairs/character_hairs40.png", + "position": 40 + }, + { + "id": "hair42", + "name": "hair42", + "url": "resources/customisation/character_hairs/character_hairs41.png", + "position": 41 + }, + { + "id": "hair43", + "name": "hair43", + "url": "resources/customisation/character_hairs/character_hairs42.png", + "position": 42 + }, + { + "id": "hair44", + "name": "hair44", + "url": "resources/customisation/character_hairs/character_hairs43.png", + "position": 43 + }, + { + "id": "hair45", + "name": "hair45", + "url": "resources/customisation/character_hairs/character_hairs44.png", + "position": 44 + }, + { + "id": "hair46", + "name": "hair46", + "url": "resources/customisation/character_hairs/character_hairs45.png", + "position": 45 + }, + { + "id": "hair47", + "name": "hair47", + "url": "resources/customisation/character_hairs/character_hairs46.png", + "position": 46 + }, + { + "id": "hair48", + "name": "hair48", + "url": "resources/customisation/character_hairs/character_hairs47.png", + "position": 47 + }, + { + "id": "hair49", + "name": "hair49", + "url": "resources/customisation/character_hairs/character_hairs48.png", + "position": 48 + }, + { + "id": "hair50", + "name": "hair50", + "url": "resources/customisation/character_hairs/character_hairs49.png", + "position": 49 + }, + { + "id": "hair51", + "name": "hair51", + "url": "resources/customisation/character_hairs/character_hairs50.png", + "position": 50 + }, + { + "id": "hair52", + "name": "hair52", + "url": "resources/customisation/character_hairs/character_hairs51.png", + "position": 51 + }, + { + "id": "hair53", + "name": "hair53", + "url": "resources/customisation/character_hairs/character_hairs52.png", + "position": 52 + }, + { + "id": "hair54", + "name": "hair54", + "url": "resources/customisation/character_hairs/character_hairs53.png", + "position": 53 + }, + { + "id": "hair55", + "name": "hair55", + "url": "resources/customisation/character_hairs/character_hairs54.png", + "position": 54 + }, + { + "id": "hair56", + "name": "hair56", + "url": "resources/customisation/character_hairs/character_hairs55.png", + "position": 55 + }, + { + "id": "hair57", + "name": "hair57", + "url": "resources/customisation/character_hairs/character_hairs56.png", + "position": 56 + }, + { + "id": "hair58", + "name": "hair58", + "url": "resources/customisation/character_hairs/character_hairs57.png", + "position": 57 + }, + { + "id": "hair59", + "name": "hair59", + "url": "resources/customisation/character_hairs/character_hairs58.png", + "position": 58 + }, + { + "id": "hair60", + "name": "hair60", + "url": "resources/customisation/character_hairs/character_hairs59.png", + "position": 59 + }, + { + "id": "hair61", + "name": "hair61", + "url": "resources/customisation/character_hairs/character_hairs60.png", + "position": 60 + }, + { + "id": "hair62", + "name": "hair62", + "url": "resources/customisation/character_hairs/character_hairs61.png", + "position": 61 + }, + { + "id": "hair63", + "name": "hair63", + "url": "resources/customisation/character_hairs/character_hairs62.png", + "position": 62 + }, + { + "id": "hair64", + "name": "hair64", + "url": "resources/customisation/character_hairs/character_hairs63.png", + "position": 63 + }, + { + "id": "hair65", + "name": "hair65", + "url": "resources/customisation/character_hairs/character_hairs64.png", + "position": 64 + }, + { + "id": "hair66", + "name": "hair66", + "url": "resources/customisation/character_hairs/character_hairs65.png", + "position": 65 + }, + { + "id": "hair67", + "name": "hair67", + "url": "resources/customisation/character_hairs/character_hairs66.png", + "position": 66 + }, + { + "id": "hair68", + "name": "hair68", + "url": "resources/customisation/character_hairs/character_hairs67.png", + "position": 67 + }, + { + "id": "hair69", + "name": "hair69", + "url": "resources/customisation/character_hairs/character_hairs68.png", + "position": 68 + }, + { + "id": "hair70", + "name": "hair70", + "url": "resources/customisation/character_hairs/character_hairs69.png", + "position": 69 + }, + { + "id": "hair71", + "name": "hair71", + "url": "resources/customisation/character_hairs/character_hairs70.png", + "position": 70 + }, + { + "id": "hair72", + "name": "hair72", + "url": "resources/customisation/character_hairs/character_hairs71.png", + "position": 71 + }, + { + "id": "hair73", + "name": "hair73", + "url": "resources/customisation/character_hairs/character_hairs72.png", + "position": 72 + }, + { + "id": "hair74", + "name": "hair74", + "url": "resources/customisation/character_hairs/character_hairs73.png", + "position": 73 + } + ] + } + ] + }, + "clothes": { + "collections": [ + { + "name": "default", + "position": 0, + "textures": [ + { + "id": "clothes1", + "name": "clothes1", + "url": "resources/customisation/character_clothes/character_clothes0.png", + "position": 0 + }, + { + "id": "clothes2", + "name": "clothes2", + "url": "resources/customisation/character_clothes/character_clothes1.png", + "position": 1 + }, + { + "id": "clothes3", + "name": "clothes3", + "url": "resources/customisation/character_clothes/character_clothes2.png", + "position": 2 + }, + { + "id": "clothes4", + "name": "clothes4", + "url": "resources/customisation/character_clothes/character_clothes3.png", + "position": 3 + }, + { + "id": "clothes5", + "name": "clothes5", + "url": "resources/customisation/character_clothes/character_clothes4.png", + "position": 4 + }, + { + "id": "clothes6", + "name": "clothes6", + "url": "resources/customisation/character_clothes/character_clothes5.png", + "position": 5 + }, + { + "id": "clothes7", + "name": "clothes7", + "url": "resources/customisation/character_clothes/character_clothes6.png", + "position": 6 + }, + { + "id": "clothes8", + "name": "clothes8", + "url": "resources/customisation/character_clothes/character_clothes7.png", + "position": 7 + }, + { + "id": "clothes9", + "name": "clothes9", + "url": "resources/customisation/character_clothes/character_clothes8.png", + "position": 8 + }, + { + "id": "clothes10", + "name": "clothes10", + "url": "resources/customisation/character_clothes/character_clothes9.png", + "position": 9 + }, + { + "id": "clothes11", + "name": "clothes11", + "url": "resources/customisation/character_clothes/character_clothes10.png", + "position": 10 + }, + { + "id": "clothes12", + "name": "clothes12", + "url": "resources/customisation/character_clothes/character_clothes11.png", + "position": 11 + }, + { + "id": "clothes13", + "name": "clothes13", + "url": "resources/customisation/character_clothes/character_clothes12.png", + "position": 12 + }, + { + "id": "clothes14", + "name": "clothes14", + "url": "resources/customisation/character_clothes/character_clothes13.png", + "position": 13 + }, + { + "id": "clothes15", + "name": "clothes15", + "url": "resources/customisation/character_clothes/character_clothes14.png", + "position": 14 + }, + { + "id": "clothes16", + "name": "clothes16", + "url": "resources/customisation/character_clothes/character_clothes15.png", + "position": 15 + }, + { + "id": "clothes17", + "name": "clothes17", + "url": "resources/customisation/character_clothes/character_clothes16.png", + "position": 16 + }, + { + "id": "clothes18", + "name": "clothes18", + "url": "resources/customisation/character_clothes/character_clothes17.png", + "position": 17 + }, + { + "id": "clothes19", + "name": "clothes19", + "url": "resources/customisation/character_clothes/character_clothes18.png", + "position": 18 + }, + { + "id": "clothes20", + "name": "clothes20", + "url": "resources/customisation/character_clothes/character_clothes19.png", + "position": 19 + }, + { + "id": "clothes21", + "name": "clothes21", + "url": "resources/customisation/character_clothes/character_clothes20.png", + "position": 20 + }, + { + "id": "clothes22", + "name": "clothes22", + "url": "resources/customisation/character_clothes/character_clothes21.png", + "position": 21 + }, + { + "id": "clothes23", + "name": "clothes23", + "url": "resources/customisation/character_clothes/character_clothes22.png", + "position": 22 + }, + { + "id": "clothes24", + "name": "clothes24", + "url": "resources/customisation/character_clothes/character_clothes23.png", + "position": 23 + }, + { + "id": "clothes25", + "name": "clothes25", + "url": "resources/customisation/character_clothes/character_clothes24.png", + "position": 24 + }, + { + "id": "clothes26", + "name": "clothes26", + "url": "resources/customisation/character_clothes/character_clothes25.png", + "position": 25 + }, + { + "id": "clothes27", + "name": "clothes27", + "url": "resources/customisation/character_clothes/character_clothes26.png", + "position": 26 + }, + { + "id": "clothes28", + "name": "clothes28", + "url": "resources/customisation/character_clothes/character_clothes27.png", + "position": 27 + }, + { + "id": "clothes29", + "name": "clothes29", + "url": "resources/customisation/character_clothes/character_clothes28.png", + "position": 28 + }, + { + "id": "clothes30", + "name": "clothes30", + "url": "resources/customisation/character_clothes/character_clothes29.png", + "position": 29 + }, + { + "id": "clothes31", + "name": "clothes31", + "url": "resources/customisation/character_clothes/character_clothes30.png", + "position": 30 + }, + { + "id": "clothes32", + "name": "clothes32", + "url": "resources/customisation/character_clothes/character_clothes31.png", + "position": 31 + }, + { + "id": "clothes33", + "name": "clothes33", + "url": "resources/customisation/character_clothes/character_clothes32.png", + "position": 32 + }, + { + "id": "clothes34", + "name": "clothes34", + "url": "resources/customisation/character_clothes/character_clothes33.png", + "position": 33 + }, + { + "id": "clothes35", + "name": "clothes35", + "url": "resources/customisation/character_clothes/character_clothes34.png", + "position": 34 + }, + { + "id": "clothes36", + "name": "clothes36", + "url": "resources/customisation/character_clothes/character_clothes35.png", + "position": 35 + }, + { + "id": "clothes37", + "name": "clothes37", + "url": "resources/customisation/character_clothes/character_clothes36.png", + "position": 36 + }, + { + "id": "clothes38", + "name": "clothes38", + "url": "resources/customisation/character_clothes/character_clothes37.png", + "position": 37 + }, + { + "id": "clothes39", + "name": "clothes39", + "url": "resources/customisation/character_clothes/character_clothes38.png", + "position": 38 + }, + { + "id": "clothes40", + "name": "clothes40", + "url": "resources/customisation/character_clothes/character_clothes39.png", + "position": 39 + }, + { + "id": "clothes41", + "name": "clothes41", + "url": "resources/customisation/character_clothes/character_clothes40.png", + "position": 40 + }, + { + "id": "clothes42", + "name": "clothes42", + "url": "resources/customisation/character_clothes/character_clothes41.png", + "position": 41 + }, + { + "id": "clothes43", + "name": "clothes43", + "url": "resources/customisation/character_clothes/character_clothes42.png", + "position": 42 + }, + { + "id": "clothes44", + "name": "clothes44", + "url": "resources/customisation/character_clothes/character_clothes43.png", + "position": 43 + }, + { + "id": "clothes45", + "name": "clothes45", + "url": "resources/customisation/character_clothes/character_clothes44.png", + "position": 44 + }, + { + "id": "clothes46", + "name": "clothes46", + "url": "resources/customisation/character_clothes/character_clothes45.png", + "position": 45 + }, + { + "id": "clothes47", + "name": "clothes47", + "url": "resources/customisation/character_clothes/character_clothes46.png", + "position": 46 + }, + { + "id": "clothes48", + "name": "clothes48", + "url": "resources/customisation/character_clothes/character_clothes47.png", + "position": 47 + }, + { + "id": "clothes49", + "name": "clothes49", + "url": "resources/customisation/character_clothes/character_clothes48.png", + "position": 48 + }, + { + "id": "clothes50", + "name": "clothes50", + "url": "resources/customisation/character_clothes/character_clothes49.png", + "position": 49 + }, + { + "id": "clothes51", + "name": "clothes51", + "url": "resources/customisation/character_clothes/character_clothes50.png", + "position": 50 + }, + { + "id": "clothes52", + "name": "clothes52", + "url": "resources/customisation/character_clothes/character_clothes51.png", + "position": 51 + }, + { + "id": "clothes53", + "name": "clothes53", + "url": "resources/customisation/character_clothes/character_clothes52.png", + "position": 52 + }, + { + "id": "clothes54", + "name": "clothes54", + "url": "resources/customisation/character_clothes/character_clothes53.png", + "position": 53 + }, + { + "id": "clothes55", + "name": "clothes55", + "url": "resources/customisation/character_clothes/character_clothes54.png", + "position": 54 + }, + { + "id": "clothes56", + "name": "clothes56", + "url": "resources/customisation/character_clothes/character_clothes55.png", + "position": 55 + }, + { + "id": "clothes57", + "name": "clothes57", + "url": "resources/customisation/character_clothes/character_clothes56.png", + "position": 56 + }, + { + "id": "clothes58", + "name": "clothes58", + "url": "resources/customisation/character_clothes/character_clothes57.png", + "position": 57 + }, + { + "id": "clothes59", + "name": "clothes59", + "url": "resources/customisation/character_clothes/character_clothes58.png", + "position": 58 + }, + { + "id": "clothes60", + "name": "clothes60", + "url": "resources/customisation/character_clothes/character_clothes59.png", + "position": 59 + }, + { + "id": "clothes61", + "name": "clothes61", + "url": "resources/customisation/character_clothes/character_clothes60.png", + "position": 60 + }, + { + "id": "clothes62", + "name": "clothes62", + "url": "resources/customisation/character_clothes/character_clothes61.png", + "position": 61 + }, + { + "id": "clothes63", + "name": "clothes63", + "url": "resources/customisation/character_clothes/character_clothes62.png", + "position": 62 + }, + { + "id": "clothes64", + "name": "clothes64", + "url": "resources/customisation/character_clothes/character_clothes63.png", + "position": 63 + }, + { + "id": "clothes65", + "name": "clothes65", + "url": "resources/customisation/character_clothes/character_clothes64.png", + "position": 64 + }, + { + "id": "clothes66", + "name": "clothes66", + "url": "resources/customisation/character_clothes/character_clothes65.png", + "position": 65 + }, + { + "id": "clothes67", + "name": "clothes67", + "url": "resources/customisation/character_clothes/character_clothes66.png", + "position": 66 + }, + { + "id": "clothes68", + "name": "clothes68", + "url": "resources/customisation/character_clothes/character_clothes67.png", + "position": 67 + }, + { + "id": "clothes69", + "name": "clothes69", + "url": "resources/customisation/character_clothes/character_clothes68.png", + "position": 68 + }, + { + "id": "clothes70", + "name": "clothes70", + "url": "resources/customisation/character_clothes/character_clothes69.png", + "position": 69 + }, + { + "id": "clothes_pride_shirt", + "name": "clothes_pride_shirt", + "url": "resources/customisation/character_clothes/pride_shirt.png", + "position": 70 + }, + { + "id": "clothes_black_hoodie", + "name": "clothes_black_hoodie", + "url": "resources/customisation/character_clothes/black_hoodie.png", + "position": 71 + }, + { + "id": "clothes_white_hoodie", + "name": "clothes_white_hoodie", + "url": "resources/customisation/character_clothes/white_hoodie.png", + "position": 72 + }, + { + "id": "clothes_engelbert", + "name": "clothes_engelbert", + "url": "resources/customisation/character_clothes/engelbert.png", + "position": 73 + } + ] + } + ] + }, + "hat": { + "collections": [ + { + "name": "default", + "position": 0, + "textures": [ + { + "id": "hat1", + "name": "hat1", + "url": "resources/customisation/character_hats/character_hats1.png", + "position": 0 + }, + { + "id": "hat2", + "name": "hat2", + "url": "resources/customisation/character_hats/character_hats2.png", + "position": 1 + }, + { + "id": "hat3", + "name": "hat3", + "url": "resources/customisation/character_hats/character_hats3.png", + "position": 2 + }, + { + "id": "hat4", + "name": "hat4", + "url": "resources/customisation/character_hats/character_hats4.png", + "position": 3 + }, + { + "id": "hat5", + "name": "hat5", + "url": "resources/customisation/character_hats/character_hats5.png", + "position": 4 + }, + { + "id": "hat6", + "name": "hat6", + "url": "resources/customisation/character_hats/character_hats6.png", + "position": 5 + }, + { + "id": "hat7", + "name": "hat7", + "url": "resources/customisation/character_hats/character_hats7.png", + "position": 6 + }, + { + "id": "hat8", + "name": "hat8", + "url": "resources/customisation/character_hats/character_hats8.png", + "position": 7 + }, + { + "id": "hat9", + "name": "hat9", + "url": "resources/customisation/character_hats/character_hats9.png", + "position": 8 + }, + { + "id": "hat10", + "name": "hat10", + "url": "resources/customisation/character_hats/character_hats10.png", + "position": 9 + }, + { + "id": "hat11", + "name": "hat11", + "url": "resources/customisation/character_hats/character_hats11.png", + "position": 10 + }, + { + "id": "hat12", + "name": "hat12", + "url": "resources/customisation/character_hats/character_hats12.png", + "position": 11 + }, + { + "id": "hat13", + "name": "hat13", + "url": "resources/customisation/character_hats/character_hats13.png", + "position": 12 + }, + { + "id": "hat14", + "name": "hat14", + "url": "resources/customisation/character_hats/character_hats14.png", + "position": 13 + }, + { + "id": "hat15", + "name": "hat15", + "url": "resources/customisation/character_hats/character_hats15.png", + "position": 14 + }, + { + "id": "hat16", + "name": "hat16", + "url": "resources/customisation/character_hats/character_hats16.png", + "position": 15 + }, + { + "id": "hat17", + "name": "hat17", + "url": "resources/customisation/character_hats/character_hats17.png", + "position": 16 + }, + { + "id": "hat18", + "name": "hat18", + "url": "resources/customisation/character_hats/character_hats18.png", + "position": 17 + }, + { + "id": "hat19", + "name": "hat19", + "url": "resources/customisation/character_hats/character_hats19.png", + "position": 18 + }, + { + "id": "hat20", + "name": "hat20", + "url": "resources/customisation/character_hats/character_hats20.png", + "position": 19 + }, + { + "id": "hat21", + "name": "hat21", + "url": "resources/customisation/character_hats/character_hats21.png", + "position": 20 + }, + { + "id": "hat22", + "name": "hat22", + "url": "resources/customisation/character_hats/character_hats22.png", + "position": 21 + }, + { + "id": "hat23", + "name": "hat23", + "url": "resources/customisation/character_hats/character_hats23.png", + "position": 22 + }, + { + "id": "hat24", + "name": "hat24", + "url": "resources/customisation/character_hats/character_hats24.png", + "position": 23 + }, + { + "id": "hat25", + "name": "hat25", + "url": "resources/customisation/character_hats/character_hats25.png", + "position": 24 + }, + { + "id": "hat26", + "name": "hat26", + "url": "resources/customisation/character_hats/character_hats26.png", + "position": 25 + }, + { + "id": "tinfoil_hat1", + "name": "tinfoil_hat1", + "url": "resources/customisation/character_hats/tinfoil_hat1.png", + "position": 26 + } + ] + } + ] + }, + "accessory": { + "required": true, + "collections": [ + { + "name": "default", + "position": 0, + "textures": [ + { + "id": "accessory1", + "name": "accessory1", + "url": "resources/customisation/character_accessories/character_accessories1.png", + "position": 0 + }, + { + "id": "accessory2", + "name": "accessory2", + "url": "resources/customisation/character_accessories/character_accessories2.png", + "position": 1 + }, + { + "id": "accessory3", + "name": "accessory3", + "url": "resources/customisation/character_accessories/character_accessories3.png", + "position": 2 + }, + { + "id": "accessory4", + "name": "accessory4", + "url": "resources/customisation/character_accessories/character_accessories4.png", + "position": 3 + }, + { + "id": "accessory5", + "name": "accessory5", + "url": "resources/customisation/character_accessories/character_accessories5.png", + "position": 4 + }, + { + "id": "accessory6", + "name": "accessory6", + "url": "resources/customisation/character_accessories/character_accessories6.png", + "position": 5 + }, + { + "id": "accessory7", + "name": "accessory7", + "url": "resources/customisation/character_accessories/character_accessories7.png", + "position": 6 + }, + { + "id": "accessory8", + "name": "accessory8", + "url": "resources/customisation/character_accessories/character_accessories8.png", + "position": 7 + }, + { + "id": "accessory9", + "name": "accessory9", + "url": "resources/customisation/character_accessories/character_accessories9.png", + "position": 8 + }, + { + "id": "accessory10", + "name": "accessory10", + "url": "resources/customisation/character_accessories/character_accessories10.png", + "position": 9 + }, + { + "id": "accessory11", + "name": "accessory11", + "url": "resources/customisation/character_accessories/character_accessories11.png", + "position": 10 + }, + { + "id": "accessory12", + "name": "accessory12", + "url": "resources/customisation/character_accessories/character_accessories12.png", + "position": 11 + }, + { + "id": "accessory13", + "name": "accessory13", + "url": "resources/customisation/character_accessories/character_accessories13.png", + "position": 12 + }, + { + "id": "accessory14", + "name": "accessory14", + "url": "resources/customisation/character_accessories/character_accessories14.png", + "position": 13 + }, + { + "id": "accessory15", + "name": "accessory15", + "url": "resources/customisation/character_accessories/character_accessories15.png", + "position": 14 + }, + { + "id": "accessory16", + "name": "accessory16", + "url": "resources/customisation/character_accessories/character_accessories16.png", + "position": 15 + }, + { + "id": "accessory17", + "name": "accessory17", + "url": "resources/customisation/character_accessories/character_accessories17.png", + "position": 16 + }, + { + "id": "accessory18", + "name": "accessory18", + "url": "resources/customisation/character_accessories/character_accessories18.png", + "position": 17 + }, + { + "id": "accessory19", + "name": "accessory19", + "url": "resources/customisation/character_accessories/character_accessories19.png", + "position": 18 + }, + { + "id": "accessory20", + "name": "accessory20", + "url": "resources/customisation/character_accessories/character_accessories20.png", + "position": 19 + }, + { + "id": "accessory21", + "name": "accessory21", + "url": "resources/customisation/character_accessories/character_accessories21.png", + "position": 20 + }, + { + "id": "accessory22", + "name": "accessory22", + "url": "resources/customisation/character_accessories/character_accessories22.png", + "position": 21 + }, + { + "id": "accessory23", + "name": "accessory23", + "url": "resources/customisation/character_accessories/character_accessories23.png", + "position": 22 + }, + { + "id": "accessory24", + "name": "accessory24", + "url": "resources/customisation/character_accessories/character_accessories24.png", + "position": 23 + }, + { + "id": "accessory25", + "name": "accessory25", + "url": "resources/customisation/character_accessories/character_accessories25.png", + "position": 24 + }, + { + "id": "accessory26", + "name": "accessory26", + "url": "resources/customisation/character_accessories/character_accessories26.png", + "position": 25 + }, + { + "id": "accessory27", + "name": "accessory27", + "url": "resources/customisation/character_accessories/character_accessories27.png", + "position": 26 + }, + { + "id": "accessory28", + "name": "accessory28", + "url": "resources/customisation/character_accessories/character_accessories28.png", + "position": 27 + }, + { + "id": "accessory29", + "name": "accessory29", + "url": "resources/customisation/character_accessories/character_accessories29.png", + "position": 28 + }, + { + "id": "accessory30", + "name": "accessory30", + "url": "resources/customisation/character_accessories/character_accessories30.png", + "position": 29 + }, + { + "id": "accessory31", + "name": "accessory31", + "url": "resources/customisation/character_accessories/character_accessories31.png", + "position": 30 + }, + { + "id": "accessory32", + "name": "accessory32", + "url": "resources/customisation/character_accessories/character_accessories32.png", + "position": 31 + }, + { + "id": "accessory_mate_bottle", + "name": "accessory_mate_bottle", + "url": "resources/customisation/character_accessories/mate_bottle1.png", + "position": 32 + }, + { + "id": "accessory_mask", + "name": "accessory_mask", + "url": "resources/customisation/character_accessories/mask.png", + "position": 33 + } + ] + } + ] + } +} \ No newline at end of file diff --git a/pusher/package.json b/pusher/package.json index bbef80fa..4de55915 100644 --- a/pusher/package.json +++ b/pusher/package.json @@ -4,9 +4,9 @@ "description": "", "main": "index.js", "scripts": { - "tsc": "tsc", + "tsc": "tsc && cp -rf ./data ./dist/", "dev": "ts-node-dev --respawn ./server.ts", - "prod": "tsc && node --max-old-space-size=4096 ./dist/server.js", + "prod": "tsc && cp -rf ./data ./dist/ && node --max-old-space-size=4096 ./dist/server.js", "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", @@ -41,22 +41,22 @@ "homepage": "https://github.com/thecodingmachine/workadventure#readme", "dependencies": { "axios": "^0.21.2", - "busboy": "^0.3.1", "circular-json": "^0.5.9", "debug": "^4.3.1", "generic-type-guard": "^3.2.0", "google-protobuf": "^3.13.0", "grpc": "^1.24.4", + "hyper-express": "^5.8.1", "jsonwebtoken": "^8.5.1", "mkdirp": "^1.0.4", "openid-client": "^4.7.4", "prom-client": "^12.0.0", + "qs": "^6.10.3", "query-string": "^6.13.3", - "uWebSockets.js": "uNetworking/uWebSockets.js#v20.4.0", - "uuidv4": "^6.0.7" + "uuidv4": "^6.0.7", + "zod": "^3.12.0" }, "devDependencies": { - "@types/busboy": "^0.2.3", "@types/circular-json": "^0.4.0", "@types/debug": "^4.1.5", "@types/google-protobuf": "^3.7.3", @@ -64,13 +64,17 @@ "@types/jasmine": "^3.5.10", "@types/jsonwebtoken": "^8.3.8", "@types/mkdirp": "^1.0.1", + "@types/swagger-jsdoc": "^6.0.1", "@types/uuidv4": "^5.0.0", "@typescript-eslint/eslint-plugin": "^2.26.0", "@typescript-eslint/parser": "^2.26.0", "eslint": "^6.8.0", "jasmine": "^3.5.0", "lint-staged": "^11.0.0", + "live-directory": "^2.3.2", "prettier": "^2.3.1", + "swagger-jsdoc": "^6.1.0", + "swagger-ui-dist": "^4.5.1", "ts-node-dev": "^1.1.8", "typescript": "^4.5.2" }, diff --git a/pusher/src/App.ts b/pusher/src/App.ts index 327d493c..f63bca1e 100644 --- a/pusher/src/App.ts +++ b/pusher/src/App.ts @@ -4,31 +4,38 @@ import { AuthenticateController } from "./Controller/AuthenticateController"; // import { MapController } from "./Controller/MapController"; import { PrometheusController } from "./Controller/PrometheusController"; import { DebugController } from "./Controller/DebugController"; -import { App as uwsApp } from "./Server/sifrr.server"; import { AdminController } from "./Controller/AdminController"; import { OpenIdProfileController } from "./Controller/OpenIdProfileController"; +import { WokaListController } from "./Controller/WokaListController"; +import { SwaggerController } from "./Controller/SwaggerController"; +import HyperExpress from "hyper-express"; +import { cors } from "./Middleware/Cors"; +import { ENABLE_OPENAPI_ENDPOINT } from "./Enum/EnvironmentVariable"; class App { - public app: uwsApp; - public ioSocketController: IoSocketController; - public authenticateController: AuthenticateController; - public mapController: MapController; - public prometheusController: PrometheusController; - private debugController: DebugController; - private adminController: AdminController; - private openIdProfileController: OpenIdProfileController; + public app: HyperExpress.compressors.TemplatedApp; constructor() { - this.app = new uwsApp(); + const webserver = new HyperExpress.Server(); + this.app = webserver.uws_instance; - //create socket controllers - this.ioSocketController = new IoSocketController(this.app); - this.authenticateController = new AuthenticateController(this.app); - this.mapController = new MapController(this.app); - this.prometheusController = new PrometheusController(this.app); - this.debugController = new DebugController(this.app); - this.adminController = new AdminController(this.app); - this.openIdProfileController = new OpenIdProfileController(this.app); + // Global middlewares + webserver.use(cors); + + // Socket controllers + new IoSocketController(this.app); + + // Http controllers + new AuthenticateController(webserver); + new MapController(webserver); + new PrometheusController(webserver); + new DebugController(webserver); + new AdminController(webserver); + new OpenIdProfileController(webserver); + new WokaListController(webserver); + if (ENABLE_OPENAPI_ENDPOINT) { + new SwaggerController(webserver); + } } } diff --git a/pusher/src/Controller/AdminController.ts b/pusher/src/Controller/AdminController.ts index a8e3b593..c579f95b 100644 --- a/pusher/src/Controller/AdminController.ts +++ b/pusher/src/Controller/AdminController.ts @@ -1,45 +1,43 @@ -import { BaseController } from "./BaseController"; -import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js"; -import { ADMIN_API_TOKEN } from "../Enum/EnvironmentVariable"; import { apiClientRepository } from "../Services/ApiClientRepository"; import { AdminRoomMessage, WorldFullWarningToRoomMessage, RefreshRoomPromptMessage, } from "../Messages/generated/messages_pb"; +import { adminToken } from "../Middleware/AdminToken"; +import { BaseHttpController } from "./BaseHttpController"; -export class AdminController extends BaseController { - constructor(private App: TemplatedApp) { - super(); - this.App = App; +export class AdminController extends BaseHttpController { + routes() { this.receiveGlobalMessagePrompt(); this.receiveRoomEditionPrompt(); } + /** + * @openapi + * /room/refresh: + * post: + * description: Forces anyone out of the room. The request must be authenticated with the "admin-token" header. + * parameters: + * - name: "admin-token" + * in: "header" + * required: true + * type: "string" + * description: TODO - move this to a classic "Authorization" header! + * - name: "roomId" + * in: "body" + * description: "The ID (full URL) to the room" + * required: true + * type: "string" + * responses: + * 200: + * description: Will always return "ok". + * example: "ok" + */ receiveRoomEditionPrompt() { - this.App.options("/room/refresh", (res: HttpResponse, req: HttpRequest) => { - this.addCorsHeaders(res); - res.end(); - }); - // eslint-disable-next-line @typescript-eslint/no-misused-promises - this.App.post("/room/refresh", async (res: HttpResponse, req: HttpRequest) => { - res.onAborted(() => { - console.warn("/message request was aborted"); - }); - - const token = req.getHeader("admin-token"); - const body = await res.json(); - - if (ADMIN_API_TOKEN === "") { - res.writeStatus("401 Unauthorized").end("No token configured!"); - return; - } - if (token !== ADMIN_API_TOKEN) { - console.error("Admin access refused for token: " + token); - res.writeStatus("401 Unauthorized").end("Incorrect token"); - return; - } + this.app.post("/room/refresh", { middlewares: [adminToken] }, async (req, res) => { + const body = await req.json(); try { if (typeof body.roomId !== "string") { @@ -58,41 +56,53 @@ export class AdminController extends BaseController { }); }); } catch (err) { - this.errorToResponse(err, res); + this.castErrorToResponse(err, res); return; } - res.writeStatus("200"); - res.end("ok"); + res.send("ok"); + return; }); } + /** + * @openapi + * /message: + * post: + * description: Sends a message (or a world full message) to a number of rooms. + * parameters: + * - name: "admin-token" + * in: "header" + * required: true + * type: "string" + * description: TODO - move this to a classic "Authorization" header! + * - name: "text" + * in: "body" + * description: "The text of the message" + * required: true + * type: "string" + * - name: "type" + * in: "body" + * description: Either "capacity" or "message + * required: true + * type: "string" + * - name: "targets" + * in: "body" + * description: The list of room IDs to target + * required: true + * type: array + * items: + * type: string + * example: "https://play.workadventu.re/@/foo/bar/baz" + * responses: + * 200: + * description: Will always return "ok". + * example: "ok" + */ receiveGlobalMessagePrompt() { - this.App.options("/message", (res: HttpResponse, req: HttpRequest) => { - this.addCorsHeaders(res); - res.end(); - }); - // eslint-disable-next-line @typescript-eslint/no-misused-promises - this.App.post("/message", async (res: HttpResponse, req: HttpRequest) => { - res.onAborted(() => { - console.warn("/message request was aborted"); - }); - - const token = req.getHeader("admin-token"); - const body = await res.json(); - - if (ADMIN_API_TOKEN === "") { - res.writeStatus("401 Unauthorized").end("No token configured!"); - res.end(); - return; - } - if (token !== ADMIN_API_TOKEN) { - console.error("Admin access refused for token: " + token); - res.writeStatus("401 Unauthorized").end("Incorrect token"); - res.end(); - return; - } + this.app.post("/message", { middlewares: [adminToken] }, async (req, res) => { + const body = await req.json(); try { if (typeof body.text !== "string") { @@ -133,13 +143,11 @@ export class AdminController extends BaseController { }) ); } catch (err) { - this.errorToResponse(err, res); + this.castErrorToResponse(err, res); return; } - res.writeStatus("200"); - this.addCorsHeaders(res); - res.end("ok"); + res.send("ok"); }); } } diff --git a/pusher/src/Controller/AuthenticateController.ts b/pusher/src/Controller/AuthenticateController.ts index 6abb94f8..82760e62 100644 --- a/pusher/src/Controller/AuthenticateController.ts +++ b/pusher/src/Controller/AuthenticateController.ts @@ -1,20 +1,18 @@ import { v4 } from "uuid"; -import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js"; -import { BaseController } from "./BaseController"; +import { BaseHttpController } from "./BaseHttpController"; import { adminApi, FetchMemberDataByUuidResponse } from "../Services/AdminApi"; import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager"; import { parse } from "query-string"; import { openIDClient } from "../Services/OpenIDClient"; -import { DISABLE_ANONYMOUS, FRONT_URL } from "../Enum/EnvironmentVariable"; +import { DISABLE_ANONYMOUS } from "../Enum/EnvironmentVariable"; import { RegisterData } from "../Messages/JsonMessages/RegisterData"; export interface TokenInterface { userUuid: string; } -export class AuthenticateController extends BaseController { - constructor(private App: TemplatedApp) { - super(); +export class AuthenticateController extends BaseHttpController { + routes() { this.openIDLogin(); this.openIDCallback(); this.register(); @@ -23,14 +21,41 @@ export class AuthenticateController extends BaseController { } openIDLogin() { + /** + * @openapi + * /login-screen: + * get: + * description: Redirects the user to the OpenID login screen + * parameters: + * - name: "nonce" + * in: "query" + * description: "todo" + * required: true + * type: "string" + * - name: "state" + * in: "query" + * description: "todo" + * required: true + * type: "string" + * - name: "playUri" + * in: "query" + * description: "todo" + * required: false + * type: "string" + * - name: "redirect" + * in: "query" + * description: "todo" + * required: false + * type: "string" + * responses: + * 302: + * description: Redirects the user to the OpenID login screen + * + */ //eslint-disable-next-line @typescript-eslint/no-misused-promises - this.App.get("/login-screen", async (res: HttpResponse, req: HttpRequest) => { - res.onAborted(() => { - console.warn("/message request was aborted"); - }); - + this.app.get("/login-screen", async (req, res) => { try { - const { nonce, state, playUri, redirect } = parse(req.getQuery()); + const { nonce, state, playUri, redirect } = parse(req.path_query); if (!state || !nonce) { throw new Error("missing state and nonce URL parameters"); } @@ -41,24 +66,53 @@ export class AuthenticateController extends BaseController { playUri as string | undefined, redirect as string | undefined ); - res.writeStatus("302"); - res.writeHeader("Location", loginUri); - return res.end(); + res.status(302); + res.setHeader("Location", loginUri); + return res.send(""); } catch (e) { console.error("openIDLogin => e", e); - return this.errorToResponse(e, res); + this.castErrorToResponse(e, res); + return; } }); } openIDCallback() { + /** + * @openapi + * /login-callback: + * get: + * description: TODO + * parameters: + * - name: "nonce" + * in: "query" + * description: "todo" + * required: true + * type: "string" + * - name: "state" + * in: "query" + * description: "todo" + * required: true + * type: "string" + * - name: "playUri" + * in: "query" + * description: "todo" + * required: false + * type: "string" + * - name: "redirect" + * in: "query" + * description: "todo" + * required: false + * type: "string" + * responses: + * 200: + * description: TODO + * + */ //eslint-disable-next-line @typescript-eslint/no-misused-promises - this.App.get("/login-callback", async (res: HttpResponse, req: HttpRequest) => { - res.onAborted(() => { - console.warn("/message request was aborted"); - }); - const IPAddress = req.getHeader("x-forwarded-for"); - const { code, nonce, token, playUri } = parse(req.getQuery()); + this.app.get("/login-callback", async (req, res) => { + const IPAddress = req.header("x-forwarded-for"); + const { code, nonce, token, playUri } = parse(req.path_query); try { //verify connected by token if (token != undefined) { @@ -77,31 +131,22 @@ export class AuthenticateController extends BaseController { //if not nonce and code, user connected in anonymous //get data with identifier and return token if (!code && !nonce) { - res.writeStatus("200"); - this.addCorsHeaders(res); - res.writeHeader("Content-Type", "application/json"); - return res.end(JSON.stringify({ ...resUserData, authToken: token })); + return res.json({ ...resUserData, authToken: token }); } console.error("Token cannot to be check on OpenId provider"); - res.writeStatus("500"); - res.writeHeader("Access-Control-Allow-Origin", FRONT_URL); - res.end("User cannot to be connected on openid provider"); + res.status(500); + res.send("User cannot to be connected on openid provider"); return; } const resCheckTokenAuth = await openIDClient.checkTokenAuth(authTokenData.accessToken); - res.writeStatus("200"); - this.addCorsHeaders(res); - res.writeHeader("Content-Type", "application/json"); - return res.end( - JSON.stringify({ - ...resCheckTokenAuth, - ...resUserData, - authToken: token, - username: authTokenData?.username, - locale: authTokenData?.locale, - }) - ); + return res.json({ + ...resCheckTokenAuth, + ...resUserData, + authToken: token, + username: authTokenData?.username, + locale: authTokenData?.locale, + }); } catch (err) { console.info("User was not connected", err); } @@ -114,9 +159,8 @@ export class AuthenticateController extends BaseController { } catch (err) { //if no access on openid provider, return error console.error("User cannot to be connected on OpenId provider => ", err); - res.writeStatus("500"); - res.writeHeader("Access-Control-Allow-Origin", FRONT_URL); - res.end("User cannot to be connected on openid provider"); + res.status(500); + res.send("User cannot to be connected on openid provider"); return; } const email = userInfo.email || userInfo.sub; @@ -134,25 +178,32 @@ export class AuthenticateController extends BaseController { //This is very important to create User Local in LocalStorage in WorkAdventure const data = await this.getUserByUserIdentifier(email, playUri as string, IPAddress); - res.writeStatus("200"); - this.addCorsHeaders(res); - res.writeHeader("Content-Type", "application/json"); - return res.end( - JSON.stringify({ ...data, authToken, username: userInfo?.username, locale: userInfo?.locale }) - ); + return res.json({ ...data, authToken, username: userInfo?.username, locale: userInfo?.locale }); } catch (e) { console.error("openIDCallback => ERROR", e); - return this.errorToResponse(e, res); + return this.castErrorToResponse(e, res); } }); + /** + * @openapi + * /logout-callback: + * get: + * description: TODO + * parameters: + * - name: "token" + * in: "query" + * description: "todo" + * required: false + * type: "string" + * responses: + * 200: + * description: TODO + * + */ // eslint-disable-next-line @typescript-eslint/no-misused-promises - this.App.get("/logout-callback", async (res: HttpResponse, req: HttpRequest) => { - res.onAborted(() => { - console.warn("/message request was aborted"); - }); - - const { token } = parse(req.getQuery()); + this.app.get("/logout-callback", async (req, res) => { + const { token } = parse(req.path_query); try { const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false); @@ -162,29 +213,65 @@ export class AuthenticateController extends BaseController { await openIDClient.logoutUser(authTokenData.accessToken); } catch (error) { console.error("openIDCallback => logout-callback", error); - } finally { - res.writeStatus("200"); - this.addCorsHeaders(res); - // eslint-disable-next-line no-unsafe-finally - return res.end(); } + + return res.status(200).send(""); }); } - //Try to login with an admin token + /** + * @openapi + * /register: + * post: + * description: Try to login with an admin token + * parameters: + * - name: "organizationMemberToken" + * in: "body" + * description: "A token allowing a user to connect to a given world" + * required: true + * type: "string" + * responses: + * 200: + * description: The details of the logged user + * content: + * application/json: + * schema: + * type: object + * properties: + * authToken: + * type: string + * description: A unique identification JWT token + * userUuid: + * type: string + * description: Unique user ID + * email: + * type: string|null + * description: The email of the user + * example: john.doe@example.com + * roomUrl: + * type: string + * description: The room URL to connect to + * example: https://play.workadventu.re/@/foo/bar/baz + * organizationMemberToken: + * type: string|null + * description: TODO- unclear. It seems to be sent back from the request? + * example: ??? + * mapUrlStart: + * type: string + * description: TODO- unclear. I cannot find any use of this + * example: ??? + * messages: + * type: array + * description: The list of messages to be displayed when the user logs? + * example: ??? + */ private register() { - this.App.options("/register", (res: HttpResponse, req: HttpRequest) => { - this.addCorsHeaders(res); - - res.end(); + this.app.options("/register", {}, (req, res) => { + res.status(200).send(""); }); - - this.App.post("/register", (res: HttpResponse, req: HttpRequest) => { + this.app.post("/register", (req, res) => { (async () => { - res.onAborted(() => { - console.warn("Login request was aborted"); - }); - const param = await res.json(); + const param = await req.json(); //todo: what to do if the organizationMemberToken is already used? const organizationMemberToken: string | null = param.organizationMemberToken; @@ -196,71 +283,81 @@ export class AuthenticateController extends BaseController { const email = data.email; const roomUrl = data.roomUrl; const mapUrlStart = data.mapUrlStart; - const textures = data.textures; const authToken = jwtTokenManager.createAuthToken(email || userUuid); - res.writeStatus("200 OK"); - this.addCorsHeaders(res); - res.writeHeader("Content-Type", "application/json"); - res.end( - JSON.stringify({ - authToken, - userUuid, - email, - roomUrl, - mapUrlStart, - organizationMemberToken, - textures, - } as RegisterData) - ); + res.json({ + authToken, + userUuid, + email, + roomUrl, + mapUrlStart, + organizationMemberToken, + } as RegisterData); } catch (e) { console.error("register => ERROR", e); - this.errorToResponse(e, res); + this.castErrorToResponse(e, res); } })(); }); } - //permit to login on application. Return token to connect on Websocket IO. + /** + * @openapi + * /anonymLogin: + * post: + * description: Generates an "anonymous" JWT token allowing to connect to WorkAdventure anonymously. + * responses: + * 200: + * description: The details of the logged user + * content: + * application/json: + * schema: + * type: object + * properties: + * authToken: + * type: string + * description: A unique identification JWT token + * userUuid: + * type: string + * description: Unique user ID + * 403: + * description: Anonymous login is disabled at the configuration level (environment variable DISABLE_ANONYMOUS = true) + */ private anonymLogin() { - this.App.options("/anonymLogin", (res: HttpResponse, req: HttpRequest) => { - this.addCorsHeaders(res); - res.end(); - }); - - this.App.post("/anonymLogin", (res: HttpResponse, req: HttpRequest) => { - res.onAborted(() => { - console.warn("Login request was aborted"); - }); - + this.app.post("/anonymLogin", (req, res) => { if (DISABLE_ANONYMOUS) { - res.writeStatus("403 FORBIDDEN"); - res.end(); + res.status(403); + return res; } else { const userUuid = v4(); const authToken = jwtTokenManager.createAuthToken(userUuid); - res.writeStatus("200 OK"); - this.addCorsHeaders(res); - res.writeHeader("Content-Type", "application/json"); - res.end( - JSON.stringify({ - authToken, - userUuid, - }) - ); + return res.json({ + authToken, + userUuid, + }); } }); } + /** + * @openapi + * /profile-callback: + * get: + * description: ??? + * parameters: + * - name: "token" + * in: "query" + * description: "A JWT authentication token ???" + * required: true + * type: "string" + * responses: + * 302: + * description: Redirects the user to the profile screen of the admin + */ profileCallback() { - //eslint-disable-next-line @typescript-eslint/no-misused-promises - // @ts-ignore // eslint-disable-next-line @typescript-eslint/no-misused-promises - this.App.get("/profile-callback", async (res: HttpResponse, req: HttpRequest) => { - res.onAborted(() => { - console.warn("/message request was aborted"); - }); - const { token } = parse(req.getQuery()); + this.app.get("/profile-callback", async (req, res) => { + const { token } = parse(req.path_query); try { //verify connected by token if (token != undefined) { @@ -272,18 +369,18 @@ export class AuthenticateController extends BaseController { await openIDClient.checkTokenAuth(authTokenData.accessToken); //get login profile - res.writeStatus("302"); - res.writeHeader("Location", adminApi.getProfileUrl(authTokenData.accessToken)); - this.addCorsHeaders(res); - // eslint-disable-next-line no-unsafe-finally - return res.end(); + res.status(302); + res.setHeader("Location", adminApi.getProfileUrl(authTokenData.accessToken)); + res.send(""); + return; } catch (error) { - return this.errorToResponse(error, res); + this.castErrorToResponse(error, res); + return; } } } catch (error) { console.error("profileCallback => ERROR", error); - this.errorToResponse(error, res); + this.castErrorToResponse(error, res); } }); } @@ -311,7 +408,7 @@ export class AuthenticateController extends BaseController { userRoomToken: undefined, }; try { - data = await adminApi.fetchMemberDataByUuid(email, playUri, IPAddress); + data = await adminApi.fetchMemberDataByUuid(email, playUri, IPAddress, []); } catch (err) { console.error("openIDCallback => fetchMemberDataByUuid", err); } diff --git a/pusher/src/Controller/BaseController.ts b/pusher/src/Controller/BaseController.ts deleted file mode 100644 index f5465e1b..00000000 --- a/pusher/src/Controller/BaseController.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { HttpResponse } from "uWebSockets.js"; -import { FRONT_URL } from "../Enum/EnvironmentVariable"; - -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", FRONT_URL); - } - - /** - * Turns any exception into a HTTP response (and logs the error) - */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - protected errorToResponse(e: any, res: HttpResponse): void { - if (e && e.message) { - let url = e?.config?.url; - if (url !== undefined) { - url = " for URL: " + url; - } else { - url = ""; - } - console.error("ERROR: " + e.message + url); - } else if (typeof e === "string") { - console.error(e); - } - if (e.stack) { - console.error(e.stack); - } - if (e.response) { - res.writeStatus(e.response.status + " " + e.response.statusText); - this.addCorsHeaders(res); - res.end( - "An error occurred: " + - e.response.status + - " " + - (e.response.data && e.response.data.message ? e.response.data.message : e.response.statusText) - ); - } else { - res.writeStatus("500 Internal Server Error"); - this.addCorsHeaders(res); - res.end("An error occurred"); - } - } -} diff --git a/pusher/src/Controller/BaseHttpController.ts b/pusher/src/Controller/BaseHttpController.ts new file mode 100644 index 00000000..a15f7529 --- /dev/null +++ b/pusher/src/Controller/BaseHttpController.ts @@ -0,0 +1,47 @@ +import { Server } from "hyper-express"; +import Response from "hyper-express/types/components/http/Response"; +import axios from "axios"; + +export class BaseHttpController { + constructor(protected app: Server) { + this.routes(); + } + + protected routes() { + /* Define routes on children */ + } + + protected castErrorToResponse(e: unknown, res: Response): void { + if (e instanceof Error) { + let url: string | undefined; + if (axios.isAxiosError(e)) { + url = e.config.url; + if (url !== undefined) { + url = " for URL: " + url; + } else { + url = ""; + } + } + + console.error("ERROR: " + e.message + url); + console.error(e.stack); + } else if (typeof e === "string") { + console.error(e); + } + + if (axios.isAxiosError(e) && e.response) { + res.status(e.response.status); + res.send( + "An error occurred: " + + e.response.status + + " " + + (e.response.data && e.response.data.message ? e.response.data.message : e.response.statusText) + ); + return; + } else { + res.status(500); + res.send("An error occurred"); + return; + } + } +} diff --git a/pusher/src/Controller/DebugController.ts b/pusher/src/Controller/DebugController.ts index 26b229b6..c8bc6430 100644 --- a/pusher/src/Controller/DebugController.ts +++ b/pusher/src/Controller/DebugController.ts @@ -1,51 +1,42 @@ import { ADMIN_API_TOKEN } from "../Enum/EnvironmentVariable"; -import { IoSocketController } from "_Controller/IoSocketController"; 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 { BaseHttpController } from "./BaseHttpController"; -export class DebugController { - constructor(private App: App) { - this.getDump(); - } - - getDump() { - this.App.get("/dump", (res: HttpResponse, req: HttpRequest) => { - const query = parse(req.getQuery()); +export class DebugController extends BaseHttpController { + routes() { + this.app.get("/dump", (req, res) => { + const query = parse(req.path_query); if (ADMIN_API_TOKEN === "") { - return res.writeStatus("401 Unauthorized").end("No token configured!"); + return res.status(401).send("No token configured!"); } if (query.token !== ADMIN_API_TOKEN) { - return res.writeStatus("401 Unauthorized").end("Invalid token sent!"); + return res.status(401).send("Invalid token sent!"); } const worlds = Object.fromEntries(socketManager.getWorlds().entries()); - return res - .writeStatus("200 OK") - .writeHeader("Content-Type", "application/json") - .end( - stringify(worlds, (key: unknown, value: unknown) => { - 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; + return res.json( + stringify(worlds, (key: unknown, value: unknown) => { + 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; + } + }) + ); }); } } diff --git a/pusher/src/Controller/IoSocketController.ts b/pusher/src/Controller/IoSocketController.ts index 6db53403..f07ec6b8 100644 --- a/pusher/src/Controller/IoSocketController.ts +++ b/pusher/src/Controller/IoSocketController.ts @@ -1,5 +1,5 @@ -import { CharacterLayer, ExSocketInterface } from "../Model/Websocket/ExSocketInterface"; //TODO fix import by "_Model/.." -import { GameRoomPolicyTypes, PusherRoom } from "../Model/PusherRoom"; +import { ExSocketInterface } from "../Model/Websocket/ExSocketInterface"; //TODO fix import by "_Model/.." +import { GameRoomPolicyTypes } from "../Model/PusherRoom"; import { PointInterface } from "../Model/Websocket/PointInterface"; import { SetPlayerDetailsMessage, @@ -23,7 +23,6 @@ import { VariableMessage, } from "../Messages/generated/messages_pb"; import { UserMovesMessage } from "../Messages/generated/messages_pb"; -import { TemplatedApp } from "uWebSockets.js"; import { parse } from "query-string"; import { AdminSocketTokenData, jwtTokenManager, tokenInvalidException } from "../Services/JWTTokenManager"; import { adminApi, FetchMemberDataByUuidResponse } from "../Services/AdminApi"; @@ -32,15 +31,51 @@ import { emitInBatch } from "../Services/IoSocketHelpers"; import { ADMIN_API_URL, ADMIN_SOCKETS_TOKEN, DISABLE_ANONYMOUS, SOCKET_IDLE_TIMER } from "../Enum/EnvironmentVariable"; import { Zone } from "_Model/Zone"; import { ExAdminSocketInterface } from "_Model/Websocket/ExAdminSocketInterface"; -import { CharacterTexture } from "../Messages/JsonMessages/CharacterTexture"; import { isAdminMessageInterface } from "../Model/Websocket/Admin/AdminMessages"; import Axios from "axios"; import { InvalidTokenError } from "../Controller/InvalidTokenError"; +import HyperExpress from "hyper-express"; +import { localWokaService } from "../Services/LocalWokaService"; +import { WebSocket } from "uWebSockets.js"; +import { WokaDetail } from "../Enum/PlayerTextures"; + +/** + * The object passed between the "open" and the "upgrade" methods when opening a websocket + */ +interface UpgradeData { + // Data passed here is accessible on the "websocket" socket object. + rejected: false; + token: string; + userUuid: string; + IPAddress: string; + roomId: string; + name: string; + companion: CompanionMessage | undefined; + characterLayers: WokaDetail[]; + messages: unknown[]; + tags: string[]; + visitCardUrl: string | null; + userRoomToken: string | undefined; + position: PointInterface; + viewport: { + top: number; + right: number; + bottom: number; + left: number; + }; +} + +interface UpgradeFailedData { + rejected: true; + reason: "tokenInvalid" | "textureInvalid" | null; + message: string; + roomId: string; +} export class IoSocketController { private nextUserId: number = 1; - constructor(private readonly app: TemplatedApp) { + constructor(private readonly app: HyperExpress.compressors.TemplatedApp) { this.ioConnection(); if (ADMIN_SOCKETS_TOKEN) { this.adminRoomSocket(); @@ -244,7 +279,7 @@ export class IoSocketController { let memberVisitCardUrl: string | null = null; let memberMessages: unknown; let memberUserRoomToken: string | undefined; - let memberTextures: CharacterTexture[] = []; + let memberTextures: WokaDetail[] = []; const room = await socketManager.getOrCreateRoom(roomId); let userData: FetchMemberDataByUuidResponse = { email: userIdentifier, @@ -256,10 +291,18 @@ export class IoSocketController { anonymous: true, userRoomToken: undefined, }; + + let characterLayerObjs: WokaDetail[]; + if (ADMIN_API_URL) { try { try { - userData = await adminApi.fetchMemberDataByUuid(userIdentifier, roomId, IPAddress); + userData = await adminApi.fetchMemberDataByUuid( + userIdentifier, + roomId, + IPAddress, + characterLayers + ); } catch (err) { if (Axios.isAxiosError(err)) { if (err?.response?.status == 404) { @@ -308,6 +351,8 @@ export class IoSocketController { ) { throw new Error("Use the login URL to connect"); } + + characterLayerObjs = memberTextures; } catch (e) { console.log( "access not granted for user " + @@ -318,11 +363,31 @@ export class IoSocketController { console.error(e); throw new Error("User cannot access this world"); } + } else { + const fetchedTextures = await localWokaService.fetchWokaDetails(characterLayers); + if (fetchedTextures === undefined) { + // The textures we want to use do not exist! + // We need to go in error. + res.upgrade( + { + rejected: true, + reason: "textureInvalid", + message: "", + roomId, + } as UpgradeFailedData, + websocketKey, + websocketProtocol, + websocketExtensions, + context + ); + return; + } + characterLayerObjs = fetchedTextures; } // Generate characterLayers objects from characterLayers string[] - const characterLayerObjs: CharacterLayer[] = - SocketManager.mergeCharacterLayersAndCustomTextures(characterLayers, memberTextures); + /*const characterLayerObjs: CharacterLayer[] = + SocketManager.mergeCharacterLayersAndCustomTextures(characterLayers, memberTextures);*/ if (upgradeAborted.aborted) { console.log("Ouch! Client disconnected before we could upgrade it!"); @@ -334,7 +399,7 @@ export class IoSocketController { res.upgrade( { // Data passed here is accessible on the "websocket" socket object. - url, + rejected: false, token, userUuid: userData.userUuid, IPAddress, @@ -346,7 +411,6 @@ export class IoSocketController { tags: memberTags, visitCardUrl: memberVisitCardUrl, userRoomToken: memberUserRoomToken, - textures: memberTextures, position: { x: x, y: y, @@ -359,7 +423,7 @@ export class IoSocketController { bottom, left, }, - }, + } as UpgradeData, /* Spell these correctly */ websocketKey, websocketProtocol, @@ -374,7 +438,7 @@ export class IoSocketController { reason: e instanceof InvalidTokenError ? tokenInvalidException : null, message: e.message, roomId, - }, + } as UpgradeFailedData, websocketKey, websocketProtocol, websocketExtensions, @@ -387,7 +451,7 @@ export class IoSocketController { reason: null, message: "500 Internal Server Error", roomId, - }, + } as UpgradeFailedData, websocketKey, websocketProtocol, websocketExtensions, @@ -398,20 +462,23 @@ export class IoSocketController { })(); }, /* Handlers */ - open: (ws) => { + open: (_ws: WebSocket) => { + const ws = _ws as WebSocket & (UpgradeData | UpgradeFailedData); if (ws.rejected === true) { // If there is a room in the error, let's check if we need to clean it. if (ws.roomId) { - socketManager.deleteRoomIfEmptyFromId(ws.roomId as string); + socketManager.deleteRoomIfEmptyFromId(ws.roomId); } //FIX ME to use status code if (ws.reason === tokenInvalidException) { socketManager.emitTokenExpiredMessage(ws); + } else if (ws.reason === "textureInvalid") { + socketManager.emitInvalidTextureMessage(ws); } else if (ws.message === "World is full") { socketManager.emitWorldFullMessage(ws); } else { - socketManager.emitConnexionErrorMessage(ws, ws.message as string); + socketManager.emitConnexionErrorMessage(ws, ws.message); } setTimeout(() => ws.close(), 0); return; @@ -535,7 +602,6 @@ export class IoSocketController { client.name = ws.name; client.tags = ws.tags; client.visitCardUrl = ws.visitCardUrl; - client.textures = ws.textures; client.characterLayers = ws.characterLayers; client.companion = ws.companion; client.roomId = ws.roomId; diff --git a/pusher/src/Controller/MapController.ts b/pusher/src/Controller/MapController.ts index eae205f9..c6243713 100644 --- a/pusher/src/Controller/MapController.ts +++ b/pusher/src/Controller/MapController.ts @@ -1,41 +1,101 @@ -import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js"; -import { BaseController } from "./BaseController"; -import { parse } from "query-string"; import { adminApi } from "../Services/AdminApi"; -import { ADMIN_API_URL, DISABLE_ANONYMOUS, FRONT_URL } from "../Enum/EnvironmentVariable"; +import { ADMIN_API_URL, DISABLE_ANONYMOUS } from "../Enum/EnvironmentVariable"; import { GameRoomPolicyTypes } from "../Model/PusherRoom"; import { isMapDetailsData, MapDetailsData } from "../Messages/JsonMessages/MapDetailsData"; -import { socketManager } from "../Services/SocketManager"; import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager"; -import { v4 } from "uuid"; import { InvalidTokenError } from "./InvalidTokenError"; +import { parse } from "query-string"; +import { BaseHttpController } from "./BaseHttpController"; -export class MapController extends BaseController { - constructor(private App: TemplatedApp) { - super(); - this.App = App; - this.getMapUrl(); - } - +export class MapController extends BaseHttpController { // Returns a map mapping map name to file name of the map - getMapUrl() { - this.App.options("/map", (res: HttpResponse, req: HttpRequest) => { - this.addCorsHeaders(res); - res.end(); - }); - - this.App.get("/map", (res: HttpResponse, req: HttpRequest) => { - res.onAborted(() => { - console.warn("/map request was aborted"); - }); - - const query = parse(req.getQuery()); - + routes() { + /** + * @openapi + * /map: + * get: + * description: Returns a map mapping map name to file name of the map + * produces: + * - "application/json" + * parameters: + * - name: "playUri" + * in: "query" + * description: "The full URL of WorkAdventure to load this map" + * required: true + * type: "string" + * - name: "authToken" + * in: "query" + * description: "The authentication token" + * required: true + * type: "string" + * responses: + * 200: + * description: The details of the map + * content: + * application/json: + * schema: + * type: object + * required: + * - mapUrl + * - policy_type + * - tags + * - textures + * - authenticationMandatory + * - roomSlug + * - contactPage + * - group + * properties: + * mapUrl: + * type: string + * description: The full URL to the JSON map file + * example: https://myuser.github.io/myrepo/map.json + * policy_type: + * type: integer + * description: ANONYMOUS_POLICY = 1, MEMBERS_ONLY_POLICY = 2, USE_TAGS_POLICY= 3 + * example: 1 + * tags: + * type: array + * description: The list of tags required to enter this room + * items: + * type: string + * example: speaker + * authenticationMandatory: + * type: boolean|null + * description: Whether the authentication is mandatory or not for this map. + * example: true + * roomSlug: + * type: string + * description: The slug of the room + * deprecated: true + * example: foo + * contactPage: + * type: string|null + * description: The URL to the contact page + * example: https://mycompany.com/contact-us + * group: + * type: string|null + * description: The group this room is part of (maps the notion of "world" in WorkAdventure SAAS) + * example: myorg/myworld + * iframeAuthentication: + * type: string|null + * description: The URL of the authentication Iframe + * example: https://mycompany.com/authc + * expireOn: + * type: string|undefined + * description: The date (in ISO 8601 format) at which the room will expire + * example: 2022-11-05T08:15:30-05:00 + * canReport: + * type: boolean|undefined + * description: Whether the "report" feature is enabled or not on this room + * example: true + * + */ + this.app.get("/map", (req, res) => { + const query = parse(req.path_query); if (typeof query.playUri !== "string") { console.error("Expected playUri parameter in /map endpoint"); - res.writeStatus("400 Bad request"); - this.addCorsHeaders(res); - res.end("Expected playUri parameter"); + res.status(400); + res.send("Expected playUri parameter"); return; } @@ -45,30 +105,22 @@ export class MapController extends BaseController { const match = /\/_\/[^/]+\/(.+)/.exec(roomUrl.pathname); if (!match) { - res.writeStatus("404 Not Found"); - this.addCorsHeaders(res); - res.writeHeader("Content-Type", "application/json"); - res.end(JSON.stringify({})); + res.status(404); + res.json({}); return; } const mapUrl = roomUrl.protocol + "//" + match[1]; - res.writeStatus("200 OK"); - this.addCorsHeaders(res); - res.writeHeader("Content-Type", "application/json"); - res.end( - JSON.stringify({ - mapUrl, - policy_type: GameRoomPolicyTypes.ANONYMOUS_POLICY, - roomSlug: null, // Deprecated - group: null, - tags: [], - textures: [], - contactPage: null, - authenticationMandatory: DISABLE_ANONYMOUS, - } as MapDetailsData) - ); + res.json({ + mapUrl, + policy_type: GameRoomPolicyTypes.ANONYMOUS_POLICY, + roomSlug: null, // Deprecated + group: null, + tags: [], + contactPage: null, + authenticationMandatory: DISABLE_ANONYMOUS, + } as MapDetailsData); return; } @@ -90,12 +142,12 @@ export class MapController extends BaseController { } catch (e) { if (e instanceof InvalidTokenError) { // The token was not good, redirect user on login page - res.writeStatus("401 Unauthorized"); - res.writeHeader("Access-Control-Allow-Origin", FRONT_URL); - res.end("Token decrypted error"); + res.status(401); + res.send("Token decrypted error"); return; } else { - return this.errorToResponse(e, res); + this.castErrorToResponse(e, res); + return; } } } @@ -106,12 +158,10 @@ export class MapController extends BaseController { mapDetails.authenticationMandatory = true; } - res.writeStatus("200 OK"); - this.addCorsHeaders(res); - res.writeHeader("Content-Type", "application/json"); - res.end(JSON.stringify(mapDetails)); + res.json(mapDetails); + return; } catch (e) { - this.errorToResponse(e, res); + this.castErrorToResponse(e, res); } })(); }); diff --git a/pusher/src/Controller/OpenIdProfileController.ts b/pusher/src/Controller/OpenIdProfileController.ts index 8c7b4a4b..3ff4c948 100644 --- a/pusher/src/Controller/OpenIdProfileController.ts +++ b/pusher/src/Controller/OpenIdProfileController.ts @@ -1,26 +1,13 @@ -import { BaseController } from "./BaseController"; -import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js"; import { parse } from "query-string"; import { openIDClient } from "../Services/OpenIDClient"; -import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager"; -import { adminApi } from "../Services/AdminApi"; import { OPID_CLIENT_ISSUER } from "../Enum/EnvironmentVariable"; -import { IntrospectionResponse } from "openid-client"; +import { BaseHttpController } from "./BaseHttpController"; -export class OpenIdProfileController extends BaseController { - constructor(private App: TemplatedApp) { - super(); - this.profileOpenId(); - } - - profileOpenId() { +export class OpenIdProfileController extends BaseHttpController { + routes() { //eslint-disable-next-line @typescript-eslint/no-misused-promises - this.App.get("/profile", async (res: HttpResponse, req: HttpRequest) => { - res.onAborted(() => { - console.warn("/message request was aborted"); - }); - - const { accessToken } = parse(req.getQuery()); + this.app.get("/profile", async (req, res) => { + const { accessToken } = parse(req.path_query); if (!accessToken) { throw Error("Access token expected cannot to be check on Hydra"); } @@ -29,16 +16,17 @@ export class OpenIdProfileController extends BaseController { if (!resCheckTokenAuth.email) { throw new Error("Email was not found"); } - res.end( + res.send( this.buildHtml( OPID_CLIENT_ISSUER, resCheckTokenAuth.email as string, resCheckTokenAuth.picture as string | undefined ) ); + return; } catch (error) { console.error("profileCallback => ERROR", error); - this.errorToResponse(error, res); + this.castErrorToResponse(error, res); } }); } @@ -64,13 +52,13 @@ export class OpenIdProfileController extends BaseController {
- +
Profile validated by domain: ${domain} -
+
- Your email: ${email} + Your email: ${email}
diff --git a/pusher/src/Controller/PrometheusController.ts b/pusher/src/Controller/PrometheusController.ts index 7fff3981..9ee851d5 100644 --- a/pusher/src/Controller/PrometheusController.ts +++ b/pusher/src/Controller/PrometheusController.ts @@ -1,18 +1,23 @@ -import { App } from "../Server/sifrr.server"; -import { HttpRequest, HttpResponse } from "uWebSockets.js"; import { register, collectDefaultMetrics } from "prom-client"; +import { Server } from "hyper-express"; +import { BaseHttpController } from "./BaseHttpController"; +import Request from "hyper-express/types/components/http/Request"; +import Response from "hyper-express/types/components/http/Response"; -export class PrometheusController { - constructor(private App: App) { +export class PrometheusController extends BaseHttpController { + constructor(app: Server) { + super(app); collectDefaultMetrics({ gcDurationBuckets: [0.001, 0.01, 0.1, 1, 2, 5], // These are the default buckets. }); - - this.App.get("/metrics", this.metrics.bind(this)); } - private metrics(res: HttpResponse, req: HttpRequest): void { - res.writeHeader("Content-Type", register.contentType); + routes() { + this.app.get("/metrics", this.metrics.bind(this)); + } + + private metrics(req: Request, res: Response): void { + res.setHeader("Content-Type", register.contentType); res.end(register.metrics()); } } diff --git a/pusher/src/Controller/SwaggerController.ts b/pusher/src/Controller/SwaggerController.ts new file mode 100644 index 00000000..618b5266 --- /dev/null +++ b/pusher/src/Controller/SwaggerController.ts @@ -0,0 +1,64 @@ +import { BaseHttpController } from "./BaseHttpController"; +import * as fs from "fs"; + +export class SwaggerController extends BaseHttpController { + routes() { + this.app.get("/openapi", (req, res) => { + // Let's load the module dynamically (it may not exist in prod because part of the -dev packages) + const swaggerJsdoc = require("swagger-jsdoc"); + const options = { + swaggerDefinition: { + openapi: "3.0.0", + info: { + title: "WorkAdventure Pusher", + version: "1.0.0", + }, + }, + apis: ["./src/Controller/*.ts"], + }; + + res.json(swaggerJsdoc(options)); + }); + + // Create a LiveDirectory instance to virtualize directory with our assets + // @ts-ignore + const LiveDirectory = require("live-directory"); + const LiveAssets = new LiveDirectory({ + path: __dirname + "/../../node_modules/swagger-ui-dist", // We want to provide the system path to the folder. Avoid using relative paths. + keep: { + extensions: [".css", ".js", ".json", ".png", ".jpg", ".jpeg", ".html"], // We only want to serve files with these extensions + }, + ignore: (path: string) => { + return path.startsWith("."); // We want to ignore dotfiles for safety + }, + }); + + // Create static serve route to serve index.html + this.app.get("/swagger-ui/", (request, response) => { + fs.readFile(__dirname + "/../../node_modules/swagger-ui-dist/index.html", "utf8", function (err, data) { + if (err) { + return response.status(500).send(err.message); + } + const result = data.replace(/https:\/\/petstore\.swagger\.io\/v2\/swagger.json/g, "/openapi"); + + response.send(result); + + return; + }); + }); + + // Create static serve route to serve frontend assets + this.app.get("/swagger-ui/*", (request, response) => { + // Strip away '/assets' from the request path to get asset relative path + // Lookup LiveFile instance from our LiveDirectory instance. + const path = request.path.replace("/swagger-ui", ""); + const file = LiveAssets.get(path); + + // Return a 404 if no asset/file exists on the derived path + if (file === undefined) return response.status(404).send(""); + + // Set appropriate mime-type and serve file buffer as response body + return response.type(file.extension).send(file.buffer); + }); + } +} diff --git a/pusher/src/Controller/WokaListController.ts b/pusher/src/Controller/WokaListController.ts new file mode 100644 index 00000000..56300e90 --- /dev/null +++ b/pusher/src/Controller/WokaListController.ts @@ -0,0 +1,52 @@ +import { BaseHttpController } from "./BaseHttpController"; +import { wokaService } from "../Services/WokaService"; +import * as tg from "generic-type-guard"; +import { jwtTokenManager } from "../Services/JWTTokenManager"; + +export class WokaListController extends BaseHttpController { + routes() { + this.app.options("/woka/list/:roomUrl", {}, (req, res) => { + res.status(200).send(""); + return; + }); + + // eslint-disable-next-line @typescript-eslint/no-misused-promises + this.app.get("/woka/list/:roomUrl", {}, async (req, res) => { + const token = req.header("Authorization"); + + if (!token) { + res.status(401).send("Undefined authorization header"); + return; + } + + try { + const jwtData = jwtTokenManager.verifyJWTToken(token); + // Let's set the "uuid" param + req.params["uuid"] = jwtData.identifier; + } catch (e) { + console.error("Connection refused for token: " + token, e); + res.status(401).send("Invalid token sent"); + return; + } + + const isParameters = new tg.IsInterface() + .withProperties({ + roomUrl: tg.isString, + }) + .get(); + + if (!isParameters(req.path_parameters)) { + return res.status(400).send("Unknown parameters"); + } + + const roomUrl = decodeURIComponent(req.path_parameters.roomUrl); + const wokaList = await wokaService.getWokaList(roomUrl, req.params["uuid"]); + + if (!wokaList) { + return res.status(500).send("Error on getting woka list"); + } + + return res.status(200).json(wokaList); + }); + } +} diff --git a/pusher/src/Enum/EnvironmentVariable.ts b/pusher/src/Enum/EnvironmentVariable.ts index c0d33894..56f2e52a 100644 --- a/pusher/src/Enum/EnvironmentVariable.ts +++ b/pusher/src/Enum/EnvironmentVariable.ts @@ -23,6 +23,9 @@ export const OPID_USERNAME_CLAIM = process.env.OPID_USERNAME_CLAIM || "username" export const OPID_LOCALE_CLAIM = process.env.OPID_LOCALE_CLAIM || "locale"; export const DISABLE_ANONYMOUS: boolean = process.env.DISABLE_ANONYMOUS === "true"; +// If set to the string "true", the /openapi route will return the OpenAPI definition and the swagger-ui/ route will display the documentation +export const ENABLE_OPENAPI_ENDPOINT = process.env.ENABLE_OPENAPI_ENDPOINT === "true"; + export { SECRET_KEY, API_URL, diff --git a/pusher/src/Enum/PlayerTextures.ts b/pusher/src/Enum/PlayerTextures.ts new file mode 100644 index 00000000..8c7407f9 --- /dev/null +++ b/pusher/src/Enum/PlayerTextures.ts @@ -0,0 +1,48 @@ +import * as tg from "generic-type-guard"; +import { z } from "zod"; + +//The list of all the player textures, both the default models and the partial textures used for customization + +const wokaTexture = z.object({ + id: z.string(), + name: z.string(), + url: z.string(), + tags: z.array(z.string()).optional(), + tintable: z.boolean().optional(), +}); + +export type WokaTexture = z.infer; + +const wokaTextureCollection = z.object({ + name: z.string(), + textures: z.array(wokaTexture), +}); + +export type WokaTextureCollection = z.infer; + +const wokaPartType = z.object({ + collections: z.array(wokaTextureCollection), + required: z.boolean().optional(), +}); + +export type WokaPartType = z.infer; + +export const wokaList = z.record(wokaPartType); + +export type WokaList = z.infer; + +export const wokaPartNames = ["woka", "body", "eyes", "hair", "clothes", "hat", "accessory"]; + +export const isWokaDetail = new tg.IsInterface() + .withProperties({ + id: tg.isString, + }) + .withOptionalProperties({ + url: tg.isString, + layer: tg.isString, + }) + .get(); + +export type WokaDetail = tg.GuardedType; + +export type WokaDetailsResult = WokaDetail[]; diff --git a/pusher/src/Middleware/AdminToken.ts b/pusher/src/Middleware/AdminToken.ts new file mode 100644 index 00000000..6f5505ef --- /dev/null +++ b/pusher/src/Middleware/AdminToken.ts @@ -0,0 +1,22 @@ +import Request from "hyper-express/types/components/http/Request"; +import Response from "hyper-express/types/components/http/Response"; +import { MiddlewareNext, MiddlewarePromise } from "hyper-express/types/components/router/Router"; +import { ADMIN_API_TOKEN } from "../Enum/EnvironmentVariable"; + +export function adminToken(req: Request, res: Response, next?: MiddlewareNext): MiddlewarePromise { + const token = req.header("admin-token"); + + if (ADMIN_API_TOKEN === "") { + res.status(401).end("No token configured!"); + return; + } + if (token !== ADMIN_API_TOKEN) { + console.error("Admin access refused for token: " + token); + res.status(401).end("Incorrect token"); + return; + } + + if (next) { + next(); + } +} diff --git a/pusher/src/Middleware/Cors.ts b/pusher/src/Middleware/Cors.ts new file mode 100644 index 00000000..9353d7a8 --- /dev/null +++ b/pusher/src/Middleware/Cors.ts @@ -0,0 +1,18 @@ +import Request from "hyper-express/types/components/http/Request"; +import Response from "hyper-express/types/components/http/Response"; +import { MiddlewareNext, MiddlewarePromise } from "hyper-express/types/components/router/Router"; +import { FRONT_URL } from "../Enum/EnvironmentVariable"; + +export function cors(req: Request, res: Response, next?: MiddlewareNext): MiddlewarePromise { + res.setHeader( + "access-control-allow-headers", + "Origin, X-Requested-With, Content-Type, Accept, Authorization, Pragma, Cache-Control" + ); + res.setHeader("access-control-allow-methods", "GET, POST, OPTIONS, PUT, PATCH, DELETE"); + res.setHeader("access-control-allow-origin", FRONT_URL); + res.setHeader("access-control-allow-credentials", "true"); + + if (next) { + next(); + } +} diff --git a/pusher/src/Middleware/HasToken.ts b/pusher/src/Middleware/HasToken.ts new file mode 100644 index 00000000..4b06419c --- /dev/null +++ b/pusher/src/Middleware/HasToken.ts @@ -0,0 +1,16 @@ +import Request from "hyper-express/types/components/http/Request"; +import Response from "hyper-express/types/components/http/Response"; +import { MiddlewareNext, MiddlewarePromise } from "hyper-express/types/components/router/Router"; + +export function hasToken(req: Request, res: Response, next?: MiddlewareNext): MiddlewarePromise { + const authorizationHeader = req.header("Authorization"); + + if (!authorizationHeader) { + res.status(401).send("Undefined authorization header"); + return; + } + + if (next) { + next(); + } +} diff --git a/pusher/src/Model/Websocket/ExAdminSocketInterface.ts b/pusher/src/Model/Websocket/ExAdminSocketInterface.ts index 572bd0fe..663953ef 100644 --- a/pusher/src/Model/Websocket/ExAdminSocketInterface.ts +++ b/pusher/src/Model/Websocket/ExAdminSocketInterface.ts @@ -9,13 +9,13 @@ import { ServerToClientMessage, SubMessage, } from "../../Messages/generated/messages_pb"; -import { WebSocket } from "uWebSockets.js"; +import { compressors } from "hyper-express"; import { ClientDuplexStream } from "grpc"; import { Zone } from "_Model/Zone"; export type AdminConnection = ClientDuplexStream; -export interface ExAdminSocketInterface extends WebSocket { +export interface ExAdminSocketInterface extends compressors.WebSocket { adminConnection: AdminConnection; disconnecting: boolean; } diff --git a/pusher/src/Model/Websocket/ExSocketInterface.ts b/pusher/src/Model/Websocket/ExSocketInterface.ts index 47eba2dd..2b5d6225 100644 --- a/pusher/src/Model/Websocket/ExSocketInterface.ts +++ b/pusher/src/Model/Websocket/ExSocketInterface.ts @@ -8,26 +8,21 @@ import { ServerToClientMessage, SubMessage, } from "../../Messages/generated/messages_pb"; -import { WebSocket } from "uWebSockets.js"; import { ClientDuplexStream } from "grpc"; import { Zone } from "_Model/Zone"; -import { CharacterTexture } from "../../Messages/JsonMessages/CharacterTexture"; +import { compressors } from "hyper-express"; +import { WokaDetail } from "_Enum/PlayerTextures"; export type BackConnection = ClientDuplexStream; -export interface CharacterLayer { - name: string; - url: string | undefined; -} - -export interface ExSocketInterface extends WebSocket, Identificable { +export interface ExSocketInterface extends compressors.WebSocket, Identificable { token: string; roomId: string; //userId: number; // A temporary (autoincremented) identifier for this user userUuid: string; // A unique identifier for this user IPAddress: string; // IP address name: string; - characterLayers: CharacterLayer[]; + characterLayers: WokaDetail[]; position: PointInterface; viewport: ViewportInterface; companion?: CompanionMessage; @@ -41,7 +36,6 @@ export interface ExSocketInterface extends WebSocket, Identificable { messages: unknown; tags: string[]; visitCardUrl: string | null; - textures: CharacterTexture[]; backConnection: BackConnection; listenedZones: Set; userRoomToken: string | undefined; diff --git a/pusher/src/Model/Websocket/ProtobufUtils.ts b/pusher/src/Model/Websocket/ProtobufUtils.ts index bd9cb9c2..daf2aeb8 100644 --- a/pusher/src/Model/Websocket/ProtobufUtils.ts +++ b/pusher/src/Model/Websocket/ProtobufUtils.ts @@ -5,10 +5,11 @@ import { PointMessage, PositionMessage, } from "../../Messages/generated/messages_pb"; -import { CharacterLayer, ExSocketInterface } from "_Model/Websocket/ExSocketInterface"; +import { ExSocketInterface } from "_Model/Websocket/ExSocketInterface"; import Direction = PositionMessage.Direction; import { ItemEventMessageInterface } from "_Model/Websocket/ItemEventMessage"; import { PositionInterface } from "_Model/PositionInterface"; +import { WokaDetail } from "_Enum/PlayerTextures"; export class ProtobufUtils { public static toPositionMessage(point: PointInterface): PositionMessage { @@ -94,13 +95,16 @@ export class ProtobufUtils { return itemEventMessage; } - public static toCharacterLayerMessages(characterLayers: CharacterLayer[]): CharacterLayerMessage[] { + public static toCharacterLayerMessages(characterLayers: WokaDetail[]): CharacterLayerMessage[] { return characterLayers.map(function (characterLayer): CharacterLayerMessage { const message = new CharacterLayerMessage(); - message.setName(characterLayer.name); + message.setName(characterLayer.id); if (characterLayer.url) { message.setUrl(characterLayer.url); } + if (characterLayer.layer) { + message.setLayer(characterLayer.layer); + } return message; }); } diff --git a/pusher/src/Server/server/app.ts b/pusher/src/Server/server/app.ts deleted file mode 100644 index 4c422d5c..00000000 --- a/pusher/src/Server/server/app.ts +++ /dev/null @@ -1,13 +0,0 @@ -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()); - } -} - -export default App; diff --git a/pusher/src/Server/server/baseapp.ts b/pusher/src/Server/server/baseapp.ts deleted file mode 100644 index 6d973ac7..00000000 --- a/pusher/src/Server/server/baseapp.ts +++ /dev/null @@ -1,109 +0,0 @@ -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"; - -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"); - - 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); - } - }); - - return stream; - }; - - 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); -}; - -class BaseApp { - _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; - } - - 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; - } - - 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; - } - - 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)"); - } - - 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(); - } - return this; - } -} - -export default BaseApp; diff --git a/pusher/src/Server/server/formdata.ts b/pusher/src/Server/server/formdata.ts deleted file mode 100644 index 66e51db4..00000000 --- a/pusher/src/Server/server/formdata.ts +++ /dev/null @@ -1,99 +0,0 @@ -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; - } = {} -) { - console.log("Enter form data"); - options.headers = { - "content-type": contType, - }; - - return new Promise((resolve, reject) => { - const busb = new Busboy(options); - const ret = {}; - - this.bodyStream().pipe(busb); - - 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); - }); -} - -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 -) { - if (fieldname.endsWith("[]")) { - fieldname = fieldname.slice(0, fieldname.length - 2); - if (Array.isArray(ret[fieldname])) { - ret[fieldname].push(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/pusher/src/Server/server/sslapp.ts b/pusher/src/Server/server/sslapp.ts deleted file mode 100644 index 80df0e4a..00000000 --- a/pusher/src/Server/server/sslapp.ts +++ /dev/null @@ -1,13 +0,0 @@ -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()); - } -} - -export default SSLApp; diff --git a/pusher/src/Server/server/types.ts b/pusher/src/Server/server/types.ts deleted file mode 100644 index afc21d17..00000000 --- a/pusher/src/Server/server/types.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { AppOptions, TemplatedApp, HttpResponse, HttpRequest } from "uWebSockets.js"; - -export type UwsApp = { - (options: AppOptions): TemplatedApp; - new (options: AppOptions): TemplatedApp; - prototype: TemplatedApp; -}; - -export type Handler = (res: HttpResponse, req: HttpRequest) => void; - -export {}; diff --git a/pusher/src/Server/server/utils.ts b/pusher/src/Server/server/utils.ts deleted file mode 100644 index dc813064..00000000 --- a/pusher/src/Server/server/utils.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { ReadStream } from "fs"; - -// 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)); - - 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/pusher/src/Server/sifrr.server.ts b/pusher/src/Server/sifrr.server.ts deleted file mode 100644 index 4ef03721..00000000 --- a/pusher/src/Server/sifrr.server.ts +++ /dev/null @@ -1,19 +0,0 @@ -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()); -}; - -export { App, SSLApp, getQuery }; -export * from "./server/types"; - -export default { - App, - SSLApp, - getQuery, - ...types, -}; diff --git a/pusher/src/Services/AdminApi.ts b/pusher/src/Services/AdminApi.ts index c72a6ba8..9ea84b2d 100644 --- a/pusher/src/Services/AdminApi.ts +++ b/pusher/src/Services/AdminApi.ts @@ -1,26 +1,34 @@ import { ADMIN_API_TOKEN, ADMIN_API_URL, ADMIN_URL, OPID_PROFILE_SCREEN_PROVIDER } from "../Enum/EnvironmentVariable"; -import Axios from "axios"; -import { GameRoomPolicyTypes } from "_Model/PusherRoom"; -import { CharacterTexture } from "../Messages/JsonMessages/CharacterTexture"; +import Axios, { AxiosResponse } from "axios"; import { MapDetailsData } from "../Messages/JsonMessages/MapDetailsData"; import { RoomRedirect } from "../Messages/JsonMessages/RoomRedirect"; import { AdminApiData, isAdminApiData } from "../Messages/JsonMessages/AdminApiData"; +import * as tg from "generic-type-guard"; +import { isNumber } from "generic-type-guard"; +import { isWokaDetail } from "../Enum/PlayerTextures"; +import qs from "qs"; export interface AdminBannedData { is_banned: boolean; message: string; } -export interface FetchMemberDataByUuidResponse { - email: string; - userUuid: string; - tags: string[]; - visitCardUrl: string | null; - textures: CharacterTexture[]; - messages: unknown[]; - anonymous?: boolean; - userRoomToken: string | undefined; -} +const isFetchMemberDataByUuidResponse = new tg.IsInterface() + .withProperties({ + email: tg.isString, + userUuid: tg.isString, + tags: tg.isArray(tg.isString), + visitCardUrl: tg.isNullable(tg.isString), + textures: tg.isArray(isWokaDetail), + messages: tg.isArray(tg.isUnknown), + }) + .withOptionalProperties({ + anonymous: tg.isBoolean, + userRoomToken: tg.isString, + }) + .get(); + +export type FetchMemberDataByUuidResponse = tg.GuardedType; class AdminApi { /** @@ -48,15 +56,25 @@ class AdminApi { async fetchMemberDataByUuid( userIdentifier: string | null, roomId: string, - ipAddress: string + ipAddress: string, + characterLayers: string[] ): Promise { if (!ADMIN_API_URL) { return Promise.reject(new Error("No admin backoffice set!")); } - const res = await Axios.get(ADMIN_API_URL + "/api/room/access", { - params: { userIdentifier, roomId, ipAddress }, + const res = await Axios.get>(ADMIN_API_URL + "/api/room/access", { + params: { userIdentifier, roomId, ipAddress, characterLayers }, headers: { Authorization: `${ADMIN_API_TOKEN}` }, + paramsSerializer: (p) => { + return qs.stringify(p, { arrayFormat: "brackets" }); + }, }); + if (!isFetchMemberDataByUuidResponse(res.data)) { + throw new Error( + "Invalid answer received from the admin for the /api/room/access endpoint. Received: " + + JSON.stringify(res.data) + ); + } return res.data; } diff --git a/pusher/src/Services/AdminWokaService.ts b/pusher/src/Services/AdminWokaService.ts new file mode 100644 index 00000000..52261412 --- /dev/null +++ b/pusher/src/Services/AdminWokaService.ts @@ -0,0 +1,29 @@ +import axios, { AxiosResponse } from "axios"; +import { ADMIN_API_TOKEN, ADMIN_API_URL } from "../Enum/EnvironmentVariable"; +import { wokaList, WokaList } from "../Enum/PlayerTextures"; +import { WokaServiceInterface } from "./WokaServiceInterface"; + +class AdminWokaService implements WokaServiceInterface { + /** + * Returns the list of all available Wokas for the current user. + */ + getWokaList(roomUrl: string, token: string): Promise { + return axios + .get>(`${ADMIN_API_URL}/api/woka/list`, { + headers: { Authorization: `${ADMIN_API_TOKEN}` }, + params: { + roomUrl, + uuid: token, + }, + }) + .then((res) => { + return wokaList.parse(res.data); + }) + .catch((err) => { + console.error(`Cannot get woka list from admin API with token: ${token}`, err); + return undefined; + }); + } +} + +export const adminWokaService = new AdminWokaService(); diff --git a/pusher/src/Services/LocalWokaService.ts b/pusher/src/Services/LocalWokaService.ts new file mode 100644 index 00000000..931ff4b3 --- /dev/null +++ b/pusher/src/Services/LocalWokaService.ts @@ -0,0 +1,74 @@ +import { WokaDetail, WokaDetailsResult, WokaList, wokaPartNames } from "../Enum/PlayerTextures"; +import { WokaServiceInterface } from "./WokaServiceInterface"; + +class LocalWokaService implements WokaServiceInterface { + /** + * Returns the list of all available Wokas & Woka Parts for the current user. + */ + async getWokaList(roomId: string, token: string): Promise { + const wokaData: WokaList = await require("../../data/woka.json"); + if (!wokaData) { + return undefined; + } + return wokaData; + } + + /** + * Returns the URL of all the images for the given texture ids. + * + * Key: texture id + * Value: URL + * + * If one of the textures cannot be found, undefined is returned (and the user should be redirected to Woka choice page!) + */ + async fetchWokaDetails(textureIds: string[]): Promise { + const wokaData: WokaList = await require("../../data/woka.json"); + const textures = new Map< + string, + { + url: string; + layer: string; + } + >(); + const searchIds = new Set(textureIds); + + for (const part of wokaPartNames) { + const wokaPartType = wokaData[part]; + if (!wokaPartType) { + continue; + } + + for (const collection of wokaPartType.collections) { + for (const id of searchIds) { + const texture = collection.textures.find((texture) => texture.id === id); + + if (texture) { + textures.set(id, { + url: texture.url, + layer: part, + }); + searchIds.delete(id); + } + } + } + } + + if (textureIds.length !== textures.size) { + return undefined; + } + + const details: WokaDetail[] = []; + + textures.forEach((value, key) => { + details.push({ + id: key, + url: value.url, + layer: value.layer, + }); + }); + + return details; + } +} + +export const localWokaService = new LocalWokaService(); diff --git a/pusher/src/Services/SocketManager.ts b/pusher/src/Services/SocketManager.ts index 30fe761f..981c580a 100644 --- a/pusher/src/Services/SocketManager.ts +++ b/pusher/src/Services/SocketManager.ts @@ -1,5 +1,5 @@ import { PusherRoom } from "../Model/PusherRoom"; -import { CharacterLayer, ExSocketInterface } from "../Model/Websocket/ExSocketInterface"; +import { ExSocketInterface } from "../Model/Websocket/ExSocketInterface"; import { AdminMessage, AdminPusherToBackMessage, @@ -38,6 +38,7 @@ import { ErrorMessage, WorldFullMessage, PlayerDetailsUpdatedMessage, + InvalidTextureMessage, } from "../Messages/generated/messages_pb"; import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils"; import { ADMIN_API_URL, JITSI_ISS, JITSI_URL, SECRET_JITSI_KEY } from "../Enum/EnvironmentVariable"; @@ -52,7 +53,8 @@ import Debug from "debug"; import { ExAdminSocketInterface } from "_Model/Websocket/ExAdminSocketInterface"; import { WebSocket } from "uWebSockets.js"; import { isRoomRedirect } from "../Messages/JsonMessages/RoomRedirect"; -import { CharacterTexture } from "../Messages/JsonMessages/CharacterTexture"; +//import { CharacterTexture } from "../Messages/JsonMessages/CharacterTexture"; +import { compressors } from "hyper-express"; const debug = Debug("socket"); @@ -174,10 +176,13 @@ export class SocketManager implements ZoneEventListener { for (const characterLayer of client.characterLayers) { const characterLayerMessage = new CharacterLayerMessage(); - characterLayerMessage.setName(characterLayer.name); + characterLayerMessage.setName(characterLayer.id); if (characterLayer.url !== undefined) { characterLayerMessage.setUrl(characterLayer.url); } + if (characterLayer.layer !== undefined) { + characterLayerMessage.setLayer(characterLayer.layer); + } joinRoomMessage.addCharacterlayer(characterLayerMessage); } @@ -544,36 +549,6 @@ export class SocketManager implements ZoneEventListener { }); } - /** - * Merges the characterLayers received from the front (as an array of string) with the custom textures from the back. - */ - static mergeCharacterLayersAndCustomTextures( - characterLayers: string[], - memberTextures: CharacterTexture[] - ): CharacterLayer[] { - const characterLayerObjs: CharacterLayer[] = []; - for (const characterLayer of characterLayers) { - if (characterLayer.startsWith("customCharacterTexture")) { - const customCharacterLayerId: number = +characterLayer.substr(22); - for (const memberTexture of memberTextures) { - if (memberTexture.id == customCharacterLayerId) { - characterLayerObjs.push({ - name: characterLayer, - url: memberTexture.url, - }); - break; - } - } - } else { - characterLayerObjs.push({ - name: characterLayer, - url: undefined, - }); - } - } - return characterLayerObjs; - } - public onUserEnters(user: UserDescriptor, listener: ExSocketInterface): void { const subMessage = new SubMessage(); subMessage.setUserjoinedmessage(user.toUserJoinedMessage()); @@ -619,7 +594,7 @@ export class SocketManager implements ZoneEventListener { emitInBatch(listener, subMessage); } - public emitWorldFullMessage(client: WebSocket) { + public emitWorldFullMessage(client: compressors.WebSocket) { const errorMessage = new WorldFullMessage(); const serverToClientMessage = new ServerToClientMessage(); @@ -630,7 +605,7 @@ export class SocketManager implements ZoneEventListener { } } - public emitTokenExpiredMessage(client: WebSocket) { + public emitTokenExpiredMessage(client: compressors.WebSocket) { const errorMessage = new TokenExpiredMessage(); const serverToClientMessage = new ServerToClientMessage(); @@ -641,7 +616,18 @@ export class SocketManager implements ZoneEventListener { } } - public emitConnexionErrorMessage(client: WebSocket, message: string) { + public emitInvalidTextureMessage(client: compressors.WebSocket) { + const errorMessage = new InvalidTextureMessage(); + + const serverToClientMessage = new ServerToClientMessage(); + serverToClientMessage.setInvalidtexturemessage(errorMessage); + + if (!client.disconnecting) { + client.send(serverToClientMessage.serializeBinary().buffer, true); + } + } + + public emitConnexionErrorMessage(client: compressors.WebSocket, message: string) { const errorMessage = new WorldConnexionMessage(); errorMessage.setMessage(message); diff --git a/pusher/src/Services/WokaService.ts b/pusher/src/Services/WokaService.ts new file mode 100644 index 00000000..1944ab69 --- /dev/null +++ b/pusher/src/Services/WokaService.ts @@ -0,0 +1,5 @@ +import { ADMIN_API_URL } from "../Enum/EnvironmentVariable"; +import { adminWokaService } from "./AdminWokaService"; +import { localWokaService } from "./LocalWokaService"; + +export const wokaService = ADMIN_API_URL ? adminWokaService : localWokaService; diff --git a/pusher/src/Services/WokaServiceInterface.ts b/pusher/src/Services/WokaServiceInterface.ts new file mode 100644 index 00000000..47d3b8bd --- /dev/null +++ b/pusher/src/Services/WokaServiceInterface.ts @@ -0,0 +1,8 @@ +import { WokaDetailsResult, WokaList } from "../Enum/PlayerTextures"; + +export interface WokaServiceInterface { + /** + * Returns the list of all available Wokas for the current user. + */ + getWokaList(roomId: string, token: string): Promise; +} diff --git a/pusher/yarn.lock b/pusher/yarn.lock index 8992a72c..7243f352 100644 --- a/pusher/yarn.lock +++ b/pusher/yarn.lock @@ -2,41 +2,78 @@ # yarn lockfile v1 +"@apidevtools/json-schema-ref-parser@^9.0.6": + version "9.0.9" + resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz#d720f9256e3609621280584f2b47ae165359268b" + integrity sha512-GBD2Le9w2+lVFoc4vswGI/TjkNIZSVp7+9xPf+X3uidBfWnAeUWmquteSyt0+VCrhNMWj/FTABISQrD3Z/YA+w== + dependencies: + "@jsdevtools/ono" "^7.1.3" + "@types/json-schema" "^7.0.6" + call-me-maybe "^1.0.1" + js-yaml "^4.1.0" + +"@apidevtools/openapi-schemas@^2.0.4": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz#9fa08017fb59d80538812f03fc7cac5992caaa17" + integrity sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ== + +"@apidevtools/swagger-methods@^3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz#b789a362e055b0340d04712eafe7027ddc1ac267" + integrity sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg== + +"@apidevtools/swagger-parser@10.0.2": + version "10.0.2" + resolved "https://registry.yarnpkg.com/@apidevtools/swagger-parser/-/swagger-parser-10.0.2.tgz#f4145afb7c3a3bafe0376f003b5c3bdeae17a952" + integrity sha512-JFxcEyp8RlNHgBCE98nwuTkZT6eNFPc1aosWV6wPcQph72TSEEu1k3baJD4/x1qznU+JiDdz8F5pTwabZh+Dhg== + dependencies: + "@apidevtools/json-schema-ref-parser" "^9.0.6" + "@apidevtools/openapi-schemas" "^2.0.4" + "@apidevtools/swagger-methods" "^3.0.2" + "@jsdevtools/ono" "^7.1.3" + call-me-maybe "^1.0.1" + z-schema "^4.2.3" + "@babel/code-frame@^7.0.0": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.14.5.tgz#23b08d740e83f49c5e59945fbf1b43e80bbf4edb" - integrity sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw== + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789" + integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg== dependencies: - "@babel/highlight" "^7.14.5" + "@babel/highlight" "^7.16.7" -"@babel/helper-validator-identifier@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz#d0f0e277c512e0c938277faa85a3968c9a44c0e8" - integrity sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg== +"@babel/helper-validator-identifier@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad" + integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== -"@babel/highlight@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.14.5.tgz#6861a52f03966405001f6aa534a01a24d99e8cd9" - integrity sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg== +"@babel/highlight@^7.16.7": + version "7.16.10" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.10.tgz#744f2eb81579d6eea753c227b0f570ad785aba88" + integrity sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw== dependencies: - "@babel/helper-validator-identifier" "^7.14.5" + "@babel/helper-validator-identifier" "^7.16.7" chalk "^2.0.0" js-tokens "^4.0.0" +"@jsdevtools/ono@^7.1.3": + version "7.1.3" + resolved "https://registry.yarnpkg.com/@jsdevtools/ono/-/ono-7.1.3.tgz#9df03bbd7c696a5c58885c34aa06da41c8543796" + integrity sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg== + "@mapbox/node-pre-gyp@^1.0.4": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.5.tgz#2a0b32fcb416fb3f2250fd24cb2a81421a4f5950" - integrity sha512-4srsKPXWlIxp5Vbqz5uLfBN+du2fJChBoYn/f2h991WLdk7jUvcSk/McVLSv/X+xQIPI8eGD5GjrnygdyHnhPA== + version "1.0.8" + resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.8.tgz#32abc8a5c624bc4e46c43d84dfb8b26d33a96f58" + integrity sha512-CMGKi28CF+qlbXh26hDe6NxCd7amqeAzEqnS6IHeO6LoaKyM/n+Xw3HT1COdq8cuioOdlKdqn/hCmqPUOMOywg== dependencies: detect-libc "^1.0.3" https-proxy-agent "^5.0.0" make-dir "^3.1.0" - node-fetch "^2.6.1" + node-fetch "^2.6.5" nopt "^5.0.0" - npmlog "^4.1.2" + npmlog "^5.0.1" rimraf "^3.0.2" - semver "^7.3.4" - tar "^6.1.0" + semver "^7.3.5" + tar "^6.1.11" "@panva/asn1.js@^1.0.0": version "1.0.0" @@ -44,9 +81,9 @@ integrity sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw== "@sindresorhus/is@^4.0.0": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.0.1.tgz#d26729db850fa327b7cacc5522252194404226f5" - integrity sha512-Qm9hBEBu18wt1PO2flE7LPb30BHMQt1eQgbV76YntdNk73XZGpn3izvGTYxbGgzXKgbCjiia0uxTd3aTNQrY/g== + version "4.4.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.4.0.tgz#e277e5bdbdf7cb1e20d320f02f5e2ed113cd3185" + integrity sha512-QppPM/8l3Mawvh4rn9CNEYIU9bxpXUCRMaX9yUpvBk1nMKusLKpfXGDEKExKaPhLzcn3lzil7pR6rnJ11HgeRQ== "@szmarczak/http-timer@^4.0.5": version "4.0.6" @@ -55,17 +92,25 @@ dependencies: defer-to-connect "^2.0.0" -"@types/busboy@^0.2.3": - version "0.2.4" - resolved "https://registry.yarnpkg.com/@types/busboy/-/busboy-0.2.4.tgz#19922f8c7076ad6d47b2565da8c0a94c88776315" - integrity sha512-f+ZCVjlcN8JW/zf3iR0GqO4gjOUlltMTtZjn+YR1mlK+MVu6esTiIecO0/GQlmYQPQLdBnc7+5vG3Xb+SkvFLw== +"@types/body-parser@*": + version "1.19.2" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" + integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/busboy@^0.3.1": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@types/busboy/-/busboy-0.3.2.tgz#2f29b017513415399c42632ae6a7cfcb1409b79c" + integrity sha512-iEvdm9Z9KdSs/ozuh1Z7ZsXrOl8F4M/CLMXPZHr3QuJ4d6Bjn+HBMC5EMKpwpAo8oi8iK9GZfFoHaIMrrZgwVw== dependencies: "@types/node" "*" "@types/bytebuffer@^5.0.40": - version "5.0.42" - resolved "https://registry.yarnpkg.com/@types/bytebuffer/-/bytebuffer-5.0.42.tgz#1c602a77942d34c5c0879ad75c58d5d8c07dfb3b" - integrity sha512-lEgKojWUAc/MG2t649oZS5AfYFP2xRNPoDuwDBlBMjHXd8MaGPgFgtCXUK7inZdBOygmVf10qxc1Us8GXC96aw== + version "5.0.43" + resolved "https://registry.yarnpkg.com/@types/bytebuffer/-/bytebuffer-5.0.43.tgz#b5259fca1412106bcee0cabfbf7c104846d06738" + integrity sha512-vQnTYvy4LpSojHjKdmg4nXFI1BAiYPvZ/k3ouczZAQnbDprk1xqxJiFmFHyy8y6MuUq3slz5erNMtn6n87uVKw== dependencies: "@types/long" "*" "@types/node" "*" @@ -85,20 +130,48 @@ resolved "https://registry.yarnpkg.com/@types/circular-json/-/circular-json-0.4.0.tgz#7401f7e218cfe87ad4c43690da5658b9acaf51be" integrity sha512-7+kYB7x5a7nFWW1YPBh3KxhwKfiaI4PbZ1RvzBU91LZy7lWJO822CI+pqzSre/DZ7KsCuMKdHnLHHFu8AyXbQg== +"@types/connect@*": + version "3.4.35" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" + integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== + dependencies: + "@types/node" "*" + "@types/debug@^4.1.5": - version "4.1.6" - resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.6.tgz#0b7018723084918a865eff99249c490505df2163" - integrity sha512-7fDOJFA/x8B+sO1901BmHlf5dE1cxBU8mRXj8QOEDnn16hhGJv/IHxJtZhvsabZsIMn0eLIyeOKAeqSNJJYTpA== + version "4.1.7" + resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.7.tgz#7cc0ea761509124709b8b2d1090d8f6c17aadb82" + integrity sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg== + dependencies: + "@types/ms" "*" "@types/eslint-visitor-keys@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== +"@types/express-serve-static-core@^4.17.18": + version "4.17.28" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz#c47def9f34ec81dc6328d0b1b5303d1ec98d86b8" + integrity sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + +"@types/express@^4.17.13": + version "4.17.13" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034" + integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.18" + "@types/qs" "*" + "@types/serve-static" "*" + "@types/google-protobuf@^3.7.3": - version "3.15.3" - resolved "https://registry.yarnpkg.com/@types/google-protobuf/-/google-protobuf-3.15.3.tgz#054fb37aecb34d7dec826e1ce2b40cc27ec3d06a" - integrity sha512-MDpu7lit927cdLtBzTPUFjXGANFUnu5ThPqjygY8XmCyI/oDlIA0jAi4sffGOxYaLK2CCxAuU9wGxsgAQbA6FQ== + version "3.15.5" + resolved "https://registry.yarnpkg.com/@types/google-protobuf/-/google-protobuf-3.15.5.tgz#644b2be0f5613b1f822c70c73c6b0e0b5b5fa2ad" + integrity sha512-6bgv24B+A2bo9AfzReeg5StdiijKzwwnRflA8RLd1V4Yv995LeTmo0z69/MPbBDFSiZWdZHQygLo/ccXhMEDgw== "@types/http-cache-semantics@*": version "4.0.1" @@ -113,26 +186,26 @@ http-status-codes "*" "@types/jasmine@^3.5.10": - version "3.8.1" - resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-3.8.1.tgz#8feebf4035d1e4c6a6ed4d27f3bbd285d8d0da91" - integrity sha512-ioRNoJvv0eXL1c9BZKpnywZWb5YflhaSiF3IOp9deyoh30MOwkB3bNuzi4UW76EFEhcmqpoEpdWhcUAAilomTw== + version "3.10.3" + resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-3.10.3.tgz#a89798b3d5a8bd23ca56e855a9aee3e5a93bdaaa" + integrity sha512-SWyMrjgdAUHNQmutvDcKablrJhkDLy4wunTme8oYLjKp41GnHGxMRXr2MQMvy/qy8H3LdzwQk9gH4hZ6T++H8g== -"@types/json-schema@^7.0.3": - version "7.0.8" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.8.tgz#edf1bf1dbf4e04413ca8e5b17b3b7d7d54b59818" - integrity sha512-YSBPTLTVm2e2OoQIDYx8HaeWJ5tTToLH67kXR7zYNGupXMEHa2++G8k+DczX2cFVgalypqtyZIcU19AFcmOpmg== +"@types/json-schema@^7.0.3", "@types/json-schema@^7.0.6": + version "7.0.9" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" + integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== "@types/jsonwebtoken@^8.3.8": - version "8.5.4" - resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-8.5.4.tgz#50ccaf0aa6f5d7b9956e70fe323b76e582991913" - integrity sha512-4L8msWK31oXwdtC81RmRBAULd0ShnAHjBuKT9MRQpjP0piNrZdXyTRcKY9/UIfhGeKIT4PvF5amOOUbbT/9Wpg== + version "8.5.8" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-8.5.8.tgz#01b39711eb844777b7af1d1f2b4cf22fda1c0c44" + integrity sha512-zm6xBQpFDIDM6o9r6HSgDeIcLy82TKWctCXEPbJJcXb5AKmi5BNNdLXneixK4lplX3PqIVcwLBCGE/kAGnlD4A== dependencies: "@types/node" "*" "@types/keyv@*": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.2.tgz#5d97bb65526c20b6e0845f6b0d2ade4f28604ee5" - integrity sha512-/FvAK2p4jQOaJ6CGDHJTqZcUtbZe820qIeTg7o0Shg7drB4JHeL+V/dhSaly7NXx6u8eSee+r7coT+yuJEvDLg== + version "3.1.3" + resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.3.tgz#1c9aae32872ec1f20dcdaee89a9f3ba88f465e41" + integrity sha512-FXCJgyyN3ivVgRoml4h94G/p3kY+u/B86La+QptcqJaWtBWtmc6TtkNfS40n9bIvyLteHh7zXOtgbobORKPbDg== dependencies: "@types/node" "*" @@ -141,6 +214,11 @@ resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w== +"@types/mime@^1": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" + integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== + "@types/mkdirp@^1.0.1": version "1.0.2" resolved "https://registry.yarnpkg.com/@types/mkdirp/-/mkdirp-1.0.2.tgz#8d0bad7aa793abe551860be1f7ae7f3198c16666" @@ -148,16 +226,36 @@ dependencies: "@types/node" "*" +"@types/ms@*": + version "0.7.31" + resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197" + integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== + "@types/node@*": - version "16.3.3" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.3.3.tgz#0c30adff37bbbc7a50eb9b58fae2a504d0d88038" - integrity sha512-8h7k1YgQKxKXWckzFCMfsIwn0Y61UK6tlD6y2lOb3hTOIMlK3t9/QwHOhc81TwU+RMf0As5fj7NPjroERCnejQ== + version "17.0.18" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.18.tgz#3b4fed5cfb58010e3a2be4b6e74615e4847f1074" + integrity sha512-eKj4f/BsN/qcculZiRSujogjvp5O/k4lOW5m35NopjZM/QwLOR075a8pJW5hD+Rtdm2DaCVPENS6KtSQnUD6BA== + +"@types/node@^16.11.6": + version "16.11.25" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.25.tgz#bb812b58bacbd060ce85921250d8b4ca553cd4a2" + integrity sha512-NrTwfD7L1RTc2qrHQD4RTTy4p0CO2LatKBEKEds3CaVuhoM/+DJzmWZl5f+ikR8cm8F5mfJxK+9rQq07gRiSjQ== "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== +"@types/qs@*": + version "6.9.7" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" + integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== + +"@types/range-parser@*": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" + integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== + "@types/responselike@*", "@types/responselike@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29" @@ -165,6 +263,14 @@ dependencies: "@types/node" "*" +"@types/serve-static@*": + version "1.13.10" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.10.tgz#f5e0ce8797d2d7cc5ebeda48a52c96c4fa47a8d9" + integrity sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + "@types/strip-bom@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/strip-bom/-/strip-bom-3.0.0.tgz#14a8ec3956c2e81edb7520790aecf21c290aebd2" @@ -175,6 +281,11 @@ resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1" integrity sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ== +"@types/swagger-jsdoc@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@types/swagger-jsdoc/-/swagger-jsdoc-6.0.1.tgz#94a99aca0356cb64ad2a6eb903ed034703453801" + integrity sha512-+MUpcbyxD528dECUBCEVm6abNuORdbuGjbrUdHDeAQ+rkPuo2a+L4N02WJHF3bonSSE6SJ3dUJwF2V6+cHnf0w== + "@types/uuid@8.3.1": version "8.3.1" resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.1.tgz#1a32969cf8f0364b3d8c8af9cc3555b7805df14f" @@ -235,6 +346,14 @@ abbrev@1: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== +accepts@^1.3.7: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + acorn-jsx@^5.2.0: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" @@ -287,20 +406,15 @@ ansi-regex@^2.0.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" - integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= - ansi-regex@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== -ansi-regex@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" - integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== ansi-styles@^3.2.0, ansi-styles@^3.2.1: version "3.2.1" @@ -324,18 +438,18 @@ anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" -aproba@^1.0.3: - version "1.2.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" - integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== +"aproba@^1.0.3 || ^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" + integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== -are-we-there-yet@~1.1.2: - version "1.1.5" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" - integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== +are-we-there-yet@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c" + integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw== dependencies: delegates "^1.0.0" - readable-stream "^2.0.6" + readable-stream "^3.6.0" arg@^4.1.0: version "4.1.3" @@ -349,6 +463,11 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + ascli@~1: version "1.0.1" resolved "https://registry.yarnpkg.com/ascli/-/ascli-1.0.1.tgz#bcfa5974a62f18e81cabaeb49732ab4a88f906bc" @@ -368,9 +487,9 @@ astral-regex@^2.0.0: integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== axios@^0.21.2: - version "0.21.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.2.tgz#21297d5084b2aeeb422f5d38e7be4fbb82239017" - integrity sha512-87otirqUw3e8CzHTMO+/9kh/FSgXt/eVDvipijwDtEuwbkySWZ9SBm6VEubmJ/kLKEoLQV/POhxXFb66bfekfg== + version "0.21.4" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" + integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== dependencies: follow-redirects "^1.14.0" @@ -410,16 +529,16 @@ buffer-equal-constant-time@1.0.1: integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= buffer-from@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" - integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -busboy@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.3.1.tgz#170899274c5bf38aae27d5c62b71268cd585fd1b" - integrity sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw== +busboy@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.4.0.tgz#7bb6ea83e672516ab62b7c3c418b5942f88b45bb" + integrity sha512-TytIELfX6IPn1OClqcBz0NFE6+JT9e3iW0ZpgnEl7ffsfDxvRZGHfPaSHGbrI443nSV3GutCDWuqLB6yHY92Ew== dependencies: - dicer "0.3.0" + streamsearch "^1.1.0" bytebuffer@~5: version "5.0.1" @@ -433,7 +552,7 @@ cacheable-lookup@^5.0.3: resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA== -cacheable-request@^7.0.1: +cacheable-request@^7.0.2: version "7.0.2" resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.2.tgz#ea0d0b889364a25854757301ca12b2da77f91d27" integrity sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew== @@ -446,6 +565,19 @@ cacheable-request@^7.0.1: normalize-url "^6.0.1" responselike "^2.0.0" +call-bind@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +call-me-maybe@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" + integrity sha1-JtII6onje1y95gJQoV8DHBak1ms= + callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -465,10 +597,10 @@ chalk@^2.0.0, chalk@^2.1.0: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.1.0, chalk@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad" - integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg== +chalk@^4.1.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== dependencies: ansi-styles "^4.1.0" supports-color "^7.1.0" @@ -478,10 +610,10 @@ chardet@^0.7.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== -chokidar@^3.5.1: - version "3.5.2" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75" - integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ== +chokidar@^3.5.1, chokidar@^3.5.2: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== dependencies: anymatch "~3.1.2" braces "~3.0.2" @@ -515,7 +647,7 @@ cli-cursor@^3.1.0: dependencies: restore-cursor "^3.1.0" -cli-truncate@^2.1.0: +cli-truncate@2.1.0, cli-truncate@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== @@ -573,40 +705,65 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -colorette@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" - integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== +color-support@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" + integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== + +colorette@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.4.0.tgz#5190fbb87276259a86ad700bff2c6d6faa3fca40" + integrity sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g== + +colorette@^2.0.16: + version "2.0.16" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.16.tgz#713b9af84fdb000139f04546bd4a93f62a5085da" + integrity sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g== colour@~0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/colour/-/colour-0.7.1.tgz#9cb169917ec5d12c0736d3e8685746df1cadf778" integrity sha1-nLFpkX7F0SwHNtPoaFdG3xyt93g= -commander@^7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" - integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== +commander@6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.0.tgz#b990bfb8ac030aedc6d11bc04d1488ffef56db75" + integrity sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q== + +commander@^2.7.1: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +commander@^8.2.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" + integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -console-control-strings@^1.0.0, console-control-strings@~1.1.0: +console-control-strings@^1.0.0, console-control-strings@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= -core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= +cookie-signature@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.1.0.tgz#cc94974f91fb9a9c1bb485e95fc2b7f4b120aff2" + integrity sha512-Alvs19Vgq07eunykd3Xy2jF0/qSNv2u7KDbAek9H5liV1UMijbqFs5cycZvv5dVsvseT/U4H8/7/w8Koh35C4A== -cosmiconfig@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.0.tgz#ef9b44d773959cae63ddecd122de23853b60f8d3" - integrity sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA== +cookie@^0.4.1: + version "0.4.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" + integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== + +cosmiconfig@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d" + integrity sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ== dependencies: "@types/parse-json" "^4.0.0" import-fresh "^3.2.1" @@ -639,10 +796,10 @@ cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" -debug@4, debug@^4.0.1, debug@^4.1.1, debug@^4.3.1: - version "4.3.2" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" - integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== +debug@4, debug@^4.0.1, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2: + version "4.3.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" + integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== dependencies: ms "2.1.2" @@ -663,15 +820,10 @@ decompress-response@^6.0.0: dependencies: mimic-response "^3.1.0" -dedent@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" - integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= - deep-is@~0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" - integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== defer-to-connect@^2.0.0: version "2.0.1" @@ -688,19 +840,12 @@ detect-libc@^1.0.3: resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= -dicer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.3.0.tgz#eacd98b3bfbf92e8ab5c2fdb71aaac44bb06b872" - integrity sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA== - dependencies: - streamsearch "0.1.2" - diff@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== -doctrine@^3.0.0: +doctrine@3.0.0, doctrine@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== @@ -861,16 +1006,21 @@ estraverse@^4.1.1: integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== estraverse@^5.1.0, estraverse@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" - integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -execa@^5.0.0: +etag@^1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= + +execa@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== @@ -981,24 +1131,34 @@ functional-red-black-tree@^1.0.1: resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= -gauge@~2.7.3: - version "2.7.4" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" - integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= +gauge@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.2.tgz#03bf4441c044383908bcfa0656ad91803259b395" + integrity sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q== dependencies: - aproba "^1.0.3" + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.2" console-control-strings "^1.0.0" - has-unicode "^2.0.0" - object-assign "^4.1.0" + has-unicode "^2.0.1" + object-assign "^4.1.1" signal-exit "^3.0.0" - string-width "^1.0.1" - strip-ansi "^3.0.1" - wide-align "^1.1.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.2" generic-type-guard@^3.2.0: - version "3.4.1" - resolved "https://registry.yarnpkg.com/generic-type-guard/-/generic-type-guard-3.4.1.tgz#0896dc018de915c890562a34763858076e4676da" - integrity sha512-sXce0Lz3Wfy2rR1W8O8kUemgEriTeG1x8shqSJeWGb0FwJu2qBEkB1M2qXbdSLmpgDnHcIXo0Dj/1VLNJkK/QA== + version "3.5.0" + resolved "https://registry.yarnpkg.com/generic-type-guard/-/generic-type-guard-3.5.0.tgz#39de9f8fceee65d79e7540959f0e7b23210c07b6" + integrity sha512-OpgXv/sbRobhFboaSyN/Tsh97Sxt5pcfLLxCiYZgYIIWFFp+kn2EzAXiaQZKEVRlq1rOE/zh8cYhJXEwplbJiQ== + +get-intrinsic@^1.0.2: + version "1.1.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" + integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" get-own-enumerable-property-symbols@^3.0.0: version "3.0.2" @@ -1024,10 +1184,22 @@ glob-parent@^5.0.0, glob-parent@~5.1.2: dependencies: is-glob "^4.0.1" +glob@7.1.6: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + 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@^7.0.5, glob@^7.1.3, glob@^7.1.6: - version "7.1.7" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" - integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== + version "7.2.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" + integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" @@ -1044,21 +1216,21 @@ globals@^12.1.0: type-fest "^0.8.1" google-protobuf@^3.13.0: - version "3.17.3" - resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.17.3.tgz#f87595073545a77946c8f0b67c302c5f7646d700" - integrity sha512-OVPzcSWIAJ+d5yiHyeaLrdufQtrvaBrF4JQg+z8ynTkbO3uFcujqXszTumqg1cGsAsjkWnI+M5B1xZ19yR4Wyg== + version "3.19.4" + resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.19.4.tgz#8d32c3e34be9250956f28c0fb90955d13f311888" + integrity sha512-OIPNCxsG2lkIvf+P5FNfJ/Km95CsXOBecS9ZcAU6m2Rq3svc0Apl9nB3GMDNKfQ9asNv4KjyAqGwPQFrVle3Yg== got@^11.8.0: - version "11.8.2" - resolved "https://registry.yarnpkg.com/got/-/got-11.8.2.tgz#7abb3959ea28c31f3576f1576c1effce23f33599" - integrity sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ== + version "11.8.3" + resolved "https://registry.yarnpkg.com/got/-/got-11.8.3.tgz#f496c8fdda5d729a90b4905d2b07dbd148170770" + integrity sha512-7gtQ5KiPh1RtGS9/Jbv1ofDpBFuq42gyfEib+ejaRBJuj/3tQFeR5+gw57e4ipaU8c/rCjvX6fkQz2lyDlGAOg== dependencies: "@sindresorhus/is" "^4.0.0" "@szmarczak/http-timer" "^4.0.5" "@types/cacheable-request" "^6.0.1" "@types/responselike" "^1.0.0" cacheable-lookup "^5.0.3" - cacheable-request "^7.0.1" + cacheable-request "^7.0.2" decompress-response "^6.0.0" http2-wrapper "^1.0.0-beta.5.2" lowercase-keys "^2.0.0" @@ -1066,9 +1238,9 @@ got@^11.8.0: responselike "^2.0.0" grpc@^1.24.4: - version "1.24.10" - resolved "https://registry.yarnpkg.com/grpc/-/grpc-1.24.10.tgz#4cafa5f366e6d64440c3c46e134add47b205a293" - integrity sha512-mTR+P5IL3WO3oCgNwxKFE5ksXEJfCYP+dk0aIbjB494f7OnHTmssU5r9vznsSq3+cdLcxAzGFskOj5CaPwi8KA== + version "1.24.11" + resolved "https://registry.yarnpkg.com/grpc/-/grpc-1.24.11.tgz#7039da9f6f22ce35168535a6d5dda618398a5966" + integrity sha512-8/AQdFCzCeCDWW3SoaMNp6ccbRvTQEH1O1u1uFtt29eWsg5gSZCJ3m6fbkduEIh3smY7WAPP+LgVJ5n3nZRxcA== dependencies: "@mapbox/node-pre-gyp" "^1.0.4" "@types/bytebuffer" "^5.0.40" @@ -1087,7 +1259,12 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-unicode@^2.0.0: +has-symbols@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" + integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== + +has-unicode@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= @@ -1105,9 +1282,9 @@ http-cache-semantics@^4.0.0: integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== http-status-codes@*: - version "2.1.4" - resolved "https://registry.yarnpkg.com/http-status-codes/-/http-status-codes-2.1.4.tgz#453d99b4bd9424254c4f6a9a3a03715923052798" - integrity sha512-MZVIsLKGVOVE1KEnldppe6Ij+vmemMuApDfjhVSLzyYP+td0bREEYyAoIw9yFePoBXManCuBqmiNP5FqJS5Xkg== + version "2.2.0" + resolved "https://registry.yarnpkg.com/http-status-codes/-/http-status-codes-2.2.0.tgz#bb2efe63d941dfc2be18e15f703da525169622be" + integrity sha512-feERVo9iWxvnejp3SEfm/+oNG517npqL2/PIA8ORjyOZjGC7TwCRQsZylciLS64i6pJ0wRYz3rkXLRwbtFa8Ng== http2-wrapper@^1.0.0-beta.5.2: version "1.0.3" @@ -1130,6 +1307,25 @@ human-signals@^2.1.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== +hyper-express@^5.8.1: + version "5.8.1" + resolved "https://registry.yarnpkg.com/hyper-express/-/hyper-express-5.8.1.tgz#1ca8568852bac4148e1e512df7fccad11cabaeee" + integrity sha512-e/B0/IvKK6SKquz6Tbh50GzyTeNd3DazNHktu0pX4UblJCPsD9TWxh+np/3AIvSqpm3SSi0ohIcCfiVWxgxOfg== + dependencies: + "@types/busboy" "^0.3.1" + "@types/express" "^4.17.13" + "@types/node" "^16.11.6" + accepts "^1.3.7" + busboy "^1.0.0" + cookie "^0.4.1" + cookie-signature "^1.1.0" + mime-types "^2.1.33" + query-string "^7.0.1" + range-parser "^1.2.1" + type-is "^1.6.18" + uWebSockets.js "github:uNetworking/uWebSockets.js#v20.6.0" + uid-safe "^2.1.5" + iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -1168,7 +1364,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@~2.0.3: +inherits@2, inherits@^2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -1209,10 +1405,10 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" -is-core-module@^2.2.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.5.0.tgz#f754843617c70bfd29b7bd87327400cda5c18491" - integrity sha512-TXCMSDsEHMEEZ6eCA8rwRDbLu55MRGmrctljsBX/2v1d9/GzqHOxW5c5oPSgrUt2vBFXebu9rGqckXGPWOlYpg== +is-core-module@^2.8.1: + version "2.8.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211" + integrity sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA== dependencies: has "^1.0.3" @@ -1239,9 +1435,9 @@ is-fullwidth-code-point@^3.0.0: integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" - integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== dependencies: is-extglob "^2.1.1" @@ -1261,37 +1457,27 @@ is-regexp@^1.0.0: integrity sha1-/S2INUXEa6xaYz57mgnof6LLUGk= is-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" - integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== - -is-unicode-supported@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" - integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== - -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= -jasmine-core@~3.8.0: - version "3.8.0" - resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.8.0.tgz#815399aae5aa5d9beeb1262805f981b99ffc9bf0" - integrity sha512-zl0nZWDrmbCiKns0NcjkFGYkVTGCPUgoHypTaj+G2AzaWus7QGoXARSlYsSle2VRpSdfJmM+hzmFKzQNhF2kHg== +jasmine-core@~3.99.0: + version "3.99.0" + resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.99.0.tgz#99a3da0d38ba2de82614d9198b7b1bc1c32a5960" + integrity sha512-+ZDaJlEfRopINQqgE+hvzRyDIQDeKfqqTvF8RzXsvU1yE3pBDRud2+Qfh9WvGgRpuzqxyQJVI6Amy5XQ11r/3w== jasmine@^3.5.0: - version "3.8.0" - resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-3.8.0.tgz#4497bc797eede7ca9de18179aedd4cf50245d8dc" - integrity sha512-kdQ3SfcNpMbbMdgJPLyFe9IksixdnrgYaCJapP9sS0aLgdWdIZADNXEr+11Zafxm1VDfRSC5ZL4fzXT0bexzXw== + version "3.99.0" + resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-3.99.0.tgz#7cc7aeda7ade2d57694fc818a374f778cbb4ea62" + integrity sha512-YIThBuHzaIIcjxeuLmPD40SjxkEcc8i//sGMDKCgkRMVgIwRJf5qyExtlJpQeh7pkeoBSOe6lQEdg+/9uKg9mw== dependencies: glob "^7.1.6" - jasmine-core "~3.8.0" + jasmine-core "~3.99.0" jose@^2.0.5: version "2.0.5" @@ -1313,6 +1499,13 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + json-buffer@3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" @@ -1367,9 +1560,9 @@ jws@^3.2.2: safe-buffer "^5.0.1" keyv@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.3.tgz#4f3aa98de254803cafcd2896734108daa35e4254" - integrity sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA== + version "4.1.1" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.1.1.tgz#02c538bfdbd2a9308cc932d4096f05ae42bfa06a" + integrity sha512-tGv1yP6snQVDSM4X6yxrv2zzq/EvpW+oYiUz6aueW1u9CtS8RzUQYxxmFwgZlO2jSgCxQbchhxaqXXp2hnKGpQ== dependencies: json-buffer "3.0.1" @@ -1389,44 +1582,52 @@ levn@^0.3.0, levn@~0.3.0: type-check "~0.3.2" lines-and-columns@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" - integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== lint-staged@^11.0.0: - version "11.0.1" - resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-11.0.1.tgz#1b8ae8ed5a52ed87252db95fe008c2618c85f55a" - integrity sha512-RkTA1ulE6jAGFskxpGAwxfVRXjHp7D9gFg/+KMARUWMPiVFP0t28Em2u0gL8sA0w3/ck3TC57F2v2RNeQ5XPnw== + version "11.2.6" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-11.2.6.tgz#f477b1af0294db054e5937f171679df63baa4c43" + integrity sha512-Vti55pUnpvPE0J9936lKl0ngVeTdSZpEdTNhASbkaWX7J5R9OEifo1INBGQuGW4zmy6OG+TcWPJ3m5yuy5Q8Tg== dependencies: - 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" + cli-truncate "2.1.0" + colorette "^1.4.0" + commander "^8.2.0" + cosmiconfig "^7.0.1" + debug "^4.3.2" enquirer "^2.3.6" - execa "^5.0.0" - listr2 "^3.8.2" - log-symbols "^4.1.0" + execa "^5.1.1" + listr2 "^3.12.2" 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" + stringify-object "3.3.0" + supports-color "8.1.1" -listr2@^3.8.2: - version "3.10.0" - resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.10.0.tgz#58105a53ed7fa1430d1b738c6055ef7bb006160f" - integrity sha512-eP40ZHihu70sSmqFNbNy2NL1YwImmlMmPh9WO5sLmPDleurMHt3n+SwEWNu2kzKScexZnkyFtc1VI0z/TGlmpw== +listr2@^3.12.2: + version "3.14.0" + resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.14.0.tgz#23101cc62e1375fd5836b248276d1d2b51fdbe9e" + integrity sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g== dependencies: cli-truncate "^2.1.0" - colorette "^1.2.2" + colorette "^2.0.16" log-update "^4.0.0" p-map "^4.0.0" - rxjs "^6.6.7" + rfdc "^1.3.0" + rxjs "^7.5.1" through "^2.3.8" wrap-ansi "^7.0.0" +live-directory@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/live-directory/-/live-directory-2.3.2.tgz#17945a1386ed439af24228c802f6546c0fa85a9b" + integrity sha512-s/QBuRkngjzUU8kVkrklqT/2/je4GYE45HiVZ8WwFNTvswXknlsC5vdgv4ycOrL/76CBMjrG7rySFpX8nX80gg== + dependencies: + chokidar "^3.5.2" + etag "^1.8.1" + lodash.camelcase@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" @@ -1437,6 +1638,11 @@ lodash.clone@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.clone/-/lodash.clone-4.5.0.tgz#195870450f5a13192478df4bc3d23d2dea1907b6" integrity sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y= +lodash.get@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= + lodash.includes@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" @@ -1447,6 +1653,11 @@ lodash.isboolean@^3.0.3: resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY= +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= + lodash.isinteger@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" @@ -1467,6 +1678,11 @@ lodash.isstring@^4.0.1: resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= +lodash.mergewith@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz#617121f89ac55f59047c7aec1ccd6654c6590f55" + integrity sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ== + lodash.once@^4.0.0: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" @@ -1477,14 +1693,6 @@ lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -log-symbols@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" - integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== - dependencies: - chalk "^4.1.0" - is-unicode-supported "^0.1.0" - log-update@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1" @@ -1524,6 +1732,11 @@ make-error@^1.1.1, make-error@^1.3.6: resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= + merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" @@ -1537,6 +1750,18 @@ micromatch@^4.0.4: braces "^3.0.1" picomatch "^2.2.3" +mime-db@1.51.0: + version "1.51.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.51.0.tgz#d9ff62451859b18342d960850dc3cfb77e63fb0c" + integrity sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g== + +mime-types@^2.1.33, mime-types@~2.1.24, mime-types@~2.1.34: + version "2.1.34" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.34.tgz#5a712f9ec1503511a945803640fafe09d3793c24" + integrity sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A== + dependencies: + mime-db "1.51.0" + mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" @@ -1553,9 +1778,9 @@ mimic-response@^3.1.0: integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== dependencies: brace-expansion "^1.1.7" @@ -1565,9 +1790,9 @@ minimist@^1.2.5: integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== minipass@^3.0.0: - version "3.1.3" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.3.tgz#7d42ff1f39635482e15f9cdb53184deebd5815fd" - integrity sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg== + version "3.1.6" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.6.tgz#3b8150aa688a711a1521af5e8779c1d3bb4f45ee" + integrity sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ== dependencies: yallist "^4.0.0" @@ -1607,21 +1832,26 @@ mute-stream@0.0.8: integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== nan@^2.13.2: - version "2.14.2" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" - integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== + version "2.15.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" + integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ== natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== -node-fetch@^2.6.1: +node-fetch@^2.6.5: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== @@ -1652,22 +1882,22 @@ npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" -npmlog@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" - integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== +npmlog@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0" + integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw== dependencies: - are-we-there-yet "~1.1.2" - console-control-strings "~1.1.0" - gauge "~2.7.3" - set-blocking "~2.0.0" + are-we-there-yet "^2.0.0" + console-control-strings "^1.1.0" + gauge "^3.0.0" + set-blocking "^2.0.0" number-is-nan@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= -object-assign@^4.1.0: +object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= @@ -1677,6 +1907,11 @@ object-hash@^2.0.1: resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw== +object-inspect@^1.9.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0" + integrity sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g== + oidc-token-hash@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/oidc-token-hash/-/oidc-token-hash-5.0.1.tgz#ae6beec3ec20f0fd885e5400d175191d6e2f10c6" @@ -1697,9 +1932,9 @@ onetime@^5.1.0, onetime@^5.1.2: mimic-fn "^2.1.0" openid-client@^4.7.4: - version "4.7.4" - resolved "https://registry.yarnpkg.com/openid-client/-/openid-client-4.7.4.tgz#bd9978456d53d38adb89856b14a8fbd094f7732e" - integrity sha512-n+RURXYuR0bBZo9i0pn+CXZSyg5JYQ1nbwEwPQvLE7EcJt/vMZ2iIMjLehl5DvCN53XUoPVZs9KAE5r6d9fxsw== + version "4.9.1" + resolved "https://registry.yarnpkg.com/openid-client/-/openid-client-4.9.1.tgz#4f00a9d1566c0fa08f0dd5986cf0e6b1e5d14186" + integrity sha512-DYUF07AHjI3QDKqKbn2F7RqozT4hyi4JvmpodLrq0HHoNP7t/AjeG/uqiBK1/N2PZSAQEThVjDLHSmJN4iqu/w== dependencies: aggregate-error "^3.1.0" got "^11.8.0" @@ -1782,7 +2017,7 @@ path-key@^3.0.0, path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -path-parse@^1.0.6: +path-parse@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== @@ -1793,9 +2028,9 @@ path-type@^4.0.0: integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3: - version "2.3.0" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" - integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== please-upgrade-node@^3.2.0: version "3.2.0" @@ -1810,14 +2045,9 @@ prelude-ls@~1.1.2: integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= prettier@^2.3.1: - version "2.3.2" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.3.2.tgz#ef280a05ec253712e486233db5c6f23441e7342d" - integrity sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ== - -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + version "2.5.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.5.1.tgz#fff75fa9d519c54cf0fce328c1017d94546bc56a" + integrity sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg== progress@^2.0.0: version "2.0.3" @@ -1854,6 +2084,13 @@ punycode@^2.1.0: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== +qs@^6.10.3: + version "6.10.3" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e" + integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ== + dependencies: + side-channel "^1.0.4" + query-string@^6.13.3: version "6.14.1" resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.14.1.tgz#7ac2dca46da7f309449ba0f86b1fd28255b0c86a" @@ -1864,23 +2101,39 @@ query-string@^6.13.3: split-on-first "^1.0.0" strict-uri-encode "^2.0.0" +query-string@^7.0.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-7.1.1.tgz#754620669db978625a90f635f12617c271a088e1" + integrity sha512-MplouLRDHBZSG9z7fpuAAcI7aAYjDLhtsiVZsevsfaHWDS2IDdORKbSd1kWUA+V4zyva/HZoSfpwnYMMQDhb0w== + dependencies: + decode-uri-component "^0.2.0" + filter-obj "^1.1.0" + split-on-first "^1.0.0" + strict-uri-encode "^2.0.0" + quick-lru@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== -readable-stream@^2.0.6: - version "2.3.7" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" - integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== +random-bytes@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b" + integrity sha1-T2ih3Arli9P7lYSMMDJNt11kNgs= + +range-parser@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +readable-stream@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== dependencies: - 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" + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" readdirp@~3.6.0: version "3.6.0" @@ -1900,9 +2153,9 @@ regexpp@^3.0.0: integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== resolve-alpn@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.1.2.tgz#30b60cfbb0c0b8dc897940fe13fe255afcdd4d28" - integrity sha512-8OyfzhAtA32LVUsJSke3auIyINcwdh5l3cvYKdKO0nvsYSKuiLfTM5i78PJswFPT8y6cPW+L1v6/hE95chcpDA== + version "1.2.1" + resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" + integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g== resolve-from@^4.0.0: version "4.0.0" @@ -1910,12 +2163,13 @@ resolve-from@^4.0.0: integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== resolve@^1.0.0: - version "1.20.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" - integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== + version "1.22.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" + integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== dependencies: - is-core-module "^2.2.0" - path-parse "^1.0.6" + is-core-module "^2.8.1" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" responselike@^2.0.0: version "2.0.0" @@ -1932,6 +2186,11 @@ restore-cursor@^3.1.0: onetime "^5.1.0" signal-exit "^3.0.2" +rfdc@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" + integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== + rimraf@2.6.3: version "2.6.3" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" @@ -1958,23 +2217,25 @@ run-async@^2.4.0: resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== -rxjs@^6.6.0, rxjs@^6.6.7: +rxjs@^6.6.0: version "6.6.7" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== dependencies: tslib "^1.9.0" -safe-buffer@^5.0.1: +rxjs@^7.5.1: + version "7.5.4" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.4.tgz#3d6bd407e6b7ce9a123e76b1e770dc5761aa368d" + integrity sha512-h5M3Hk78r6wAheJF0a5YahB1yRQKCsZ4MsGdZ5O9ETbVtjPcScGfrMmoOq7EBsCRzd4BDkvDJ7ogP8Sz5tTFiQ== + dependencies: + tslib "^2.1.0" + +safe-buffer@^5.0.1, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - "safer-buffer@>= 2.1.2 < 3": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -1995,14 +2256,14 @@ semver@^6.0.0, semver@^6.1.2: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.3.2, semver@^7.3.4: +semver@^7.3.2, semver@^7.3.5: version "7.3.5" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== dependencies: lru-cache "^6.0.0" -set-blocking@~2.0.0: +set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= @@ -2031,10 +2292,19 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" - integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== slice-ansi@^2.1.0: version "2.1.0" @@ -2064,9 +2334,9 @@ slice-ansi@^4.0.0: is-fullwidth-code-point "^3.0.0" source-map-support@^0.5.12, source-map-support@^0.5.17: - version "0.5.19" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" - integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== dependencies: buffer-from "^1.0.0" source-map "^0.6.0" @@ -2086,10 +2356,10 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= -streamsearch@0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" - integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo= +streamsearch@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" + integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== strict-uri-encode@^2.0.0: version "2.0.0" @@ -2110,13 +2380,14 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -"string-width@^1.0.2 || 2": - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" string-width@^3.0.0: version "3.1.0" @@ -2127,23 +2398,14 @@ string-width@^3.0.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" -string-width@^4.1.0, string-width@^4.2.0: - version "4.2.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5" - integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA== +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.0" + safe-buffer "~5.2.0" -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -stringify-object@^3.3.0: +stringify-object@3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629" integrity sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw== @@ -2159,13 +2421,6 @@ strip-ansi@^3.0.0, strip-ansi@^3.0.1: dependencies: ansi-regex "^2.0.0" -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= - dependencies: - ansi-regex "^3.0.0" - strip-ansi@^5.1.0, strip-ansi@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" @@ -2173,12 +2428,12 @@ strip-ansi@^5.1.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" -strip-ansi@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" - integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== dependencies: - ansi-regex "^5.0.0" + ansi-regex "^5.0.1" strip-bom@^3.0.0: version "3.0.0" @@ -2200,6 +2455,13 @@ strip-json-comments@^3.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +supports-color@8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" @@ -2214,6 +2476,35 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +swagger-jsdoc@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/swagger-jsdoc/-/swagger-jsdoc-6.1.0.tgz#c2b86321f2c4dde8947b418fe8a4bc94431d5522" + integrity sha512-xgep5M8Gq31MxpCbQLvJZpNqHfGPfI+sILCzujZbEXIQp2COtkZgoGASs0gacRs4xHmLDH+GuMGdorPITSG4tA== + dependencies: + commander "6.2.0" + doctrine "3.0.0" + glob "7.1.6" + lodash.mergewith "^4.6.2" + swagger-parser "10.0.2" + yaml "2.0.0-1" + +swagger-parser@10.0.2: + version "10.0.2" + resolved "https://registry.yarnpkg.com/swagger-parser/-/swagger-parser-10.0.2.tgz#d7f18faa09c9c145e938977c9bd6c3435998b667" + integrity sha512-9jHkHM+QXyLGFLk1DkXBwV+4HyNm0Za3b8/zk/+mjr8jgOSiqm3FOTHBSDsBjtn9scdL+8eWcHdupp2NLM8tDw== + dependencies: + "@apidevtools/swagger-parser" "10.0.2" + +swagger-ui-dist@^4.5.1: + version "4.5.1" + resolved "https://registry.yarnpkg.com/swagger-ui-dist/-/swagger-ui-dist-4.5.1.tgz#3a71f053784ad3b781e7454743064ce3fa7522b4" + integrity sha512-52iaT+VRsT4EnE2PHrAdccRYd25Vllt9WQLH8ijkQzXAHUe6lgoEEweTGdE63utFlqaJt8JEPUcRc8x04vUyPg== + table@^5.2.3: version "5.4.6" resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" @@ -2224,10 +2515,10 @@ table@^5.2.3: slice-ansi "^2.1.0" string-width "^3.0.0" -tar@^6.1.0: - version "6.1.10" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.10.tgz#8a320a74475fba54398fa136cd9883aa8ad11175" - integrity sha512-kvvfiVvjGMxeUNB6MyYv5z7vhfFRwbwCXJAeL0/lnbrttBVqcMOnpHUf0X42LrPMR8mMpgapkJMchFH4FSHzNA== +tar@^6.1.11: + version "6.1.11" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" + integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" @@ -2320,6 +2611,11 @@ tslib@^1.8.1, tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== +tslib@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" + integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== + tsutils@^3.17.1: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" @@ -2344,14 +2640,29 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== -typescript@^4.5.2: - version "4.5.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.2.tgz#8ac1fba9f52256fdb06fb89e4122fa6a346c2998" - integrity sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw== +type-is@^1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" -uWebSockets.js@uNetworking/uWebSockets.js#v20.4.0: - version "20.4.0" - resolved "https://codeload.github.com/uNetworking/uWebSockets.js/tar.gz/65f39bdff763be3883e6cf18e433dd4fec155845" +typescript@^4.5.2: + version "4.5.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3" + integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA== + +"uWebSockets.js@github:uNetworking/uWebSockets.js#v20.6.0": + version "20.6.0" + resolved "https://codeload.github.com/uNetworking/uWebSockets.js/tar.gz/a58e810e47a23696410f6073c8c905dc38f75da5" + +uid-safe@^2.1.5: + version "2.1.5" + resolved "https://registry.yarnpkg.com/uid-safe/-/uid-safe-2.1.5.tgz#2b3d5c7240e8fc2e58f8aa269e5ee49c0857bd3a" + integrity sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA== + dependencies: + random-bytes "~1.0.0" uri-js@^4.2.2: version "4.4.1" @@ -2360,7 +2671,7 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" -util-deprecate@~1.0.1: +util-deprecate@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= @@ -2371,9 +2682,9 @@ uuid@8.3.2: integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== uuidv4@*, uuidv4@^6.0.7: - version "6.2.11" - resolved "https://registry.yarnpkg.com/uuidv4/-/uuidv4-6.2.11.tgz#34d5a03324eb38296b87ae523a64233b5286cc27" - integrity sha512-OTS4waH9KplrXNADKo+Q1kT9AHWr8DaC0S5F54RQzEwcUaEzBEWQQlJyDUw/u1bkRhJyqkqhLD4M4lbFbV+89g== + version "6.2.12" + resolved "https://registry.yarnpkg.com/uuidv4/-/uuidv4-6.2.12.tgz#e8c1d1d733c3fa4963d4610b8a3a09b4ec58ca96" + integrity sha512-UnN4ThIYWhv3ZUE8UwDnnCvh4JafCNu+sQkxmLyjCVwK3rjLfkg3DYiEv6oCMDIAIVEDP4INg4kX/C5hKaRzZA== dependencies: "@types/uuid" "8.3.1" uuid "8.3.2" @@ -2383,6 +2694,11 @@ v8-compile-cache@^2.0.3: resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== +validator@^13.6.0: + version "13.7.0" + resolved "https://registry.yarnpkg.com/validator/-/validator-13.7.0.tgz#4f9658ba13ba8f3d82ee881d3516489ea85c0857" + integrity sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw== + webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" @@ -2410,12 +2726,12 @@ which@^2.0.1: dependencies: isexe "^2.0.0" -wide-align@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" - integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== +wide-align@^1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" + integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== dependencies: - string-width "^1.0.2 || 2" + string-width "^1.0.2 || 2 || 3 || 4" window-size@^0.1.4: version "0.1.4" @@ -2480,6 +2796,11 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== +yaml@2.0.0-1: + version "2.0.0-1" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.0.0-1.tgz#8c3029b3ee2028306d5bcf396980623115ff8d18" + integrity sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ== + yaml@^1.10.0: version "1.10.2" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" @@ -2502,3 +2823,19 @@ yn@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + +z-schema@^4.2.3: + version "4.2.4" + resolved "https://registry.yarnpkg.com/z-schema/-/z-schema-4.2.4.tgz#73102a49512179b12a8ec50b1daa676b984da6e4" + integrity sha512-YvBeW5RGNeNzKOUJs3rTL4+9rpcvHXt5I051FJbOcitV8bl40pEfcG0Q+dWSwS0/BIYrMZ/9HHoqLllMkFhD0w== + dependencies: + lodash.get "^4.4.2" + lodash.isequal "^4.5.0" + validator "^13.6.0" + optionalDependencies: + commander "^2.7.1" + +zod@^3.12.0: + version "3.12.0" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.12.0.tgz#84ba9f6bdb7835e2483982d5f52cfffcb6a00346" + integrity sha512-w+mmntgEL4hDDL5NLFdN6Fq2DSzxfmlSoJqiYE1/CApO8EkOCxvJvRYEVf8Vr/lRs3i6gqoiyFM6KRcWqqdBzQ==