From 08fffab41097df0c5a68c8c7e485c989d3caf0f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Fri, 25 Feb 2022 17:05:34 +0100 Subject: [PATCH] Switching from "name" to "id" in texture object + using zod for woka/list validation --- front/src/Connexion/LocalUser.ts | 5 -- front/src/Connexion/RoomConnection.ts | 2 +- front/src/Phaser/Entity/Character.ts | 4 +- front/src/Phaser/Entity/PlayerTextures.ts | 7 +-- .../Entity/PlayerTexturesLoadingManager.ts | 18 +++---- front/src/Phaser/Login/CustomizeScene.ts | 6 +-- .../src/Phaser/Login/SelectCharacterScene.ts | 12 ++--- pusher/package.json | 3 +- pusher/src/Enum/PlayerTextures.ts | 53 ++++++++----------- pusher/src/Services/AdminWokaService.ts | 11 ++-- pusher/yarn.lock | 5 ++ 11 files changed, 57 insertions(+), 69 deletions(-) diff --git a/front/src/Connexion/LocalUser.ts b/front/src/Connexion/LocalUser.ts index fce7593d..cc86ac32 100644 --- a/front/src/Connexion/LocalUser.ts +++ b/front/src/Connexion/LocalUser.ts @@ -16,11 +16,6 @@ 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) { - return false; - } - } return true; } diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index f5896955..20b69d62 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -622,7 +622,7 @@ export class RoomConnection implements RoomConnection { characterLayer: CharacterLayerMessage ): BodyResourceDescriptionInterface { return { - name: characterLayer.name, + id: characterLayer.name, img: characterLayer.url, }; } diff --git a/front/src/Phaser/Entity/Character.ts b/front/src/Phaser/Entity/Character.ts index 79669d55..37d92b89 100644 --- a/front/src/Phaser/Entity/Character.ts +++ b/front/src/Phaser/Entity/Character.ts @@ -85,11 +85,11 @@ export abstract class Character extends Container implements OutlineableInterfac .catch(() => { return lazyLoadPlayerCharacterTextures(scene.load, [ { - name: "color_22", + id: "color_22", img: "resources/customisation/character_color/character_color21.png", }, { - name: "eyes_23", + id: "eyes_23", img: "resources/customisation/character_eyes/character_eyes23.png", }, ]).then((textures) => { diff --git a/front/src/Phaser/Entity/PlayerTextures.ts b/front/src/Phaser/Entity/PlayerTextures.ts index 657556b8..84157a92 100644 --- a/front/src/Phaser/Entity/PlayerTextures.ts +++ b/front/src/Phaser/Entity/PlayerTextures.ts @@ -5,7 +5,8 @@ export interface BodyResourceDescriptionListInterface { } export interface BodyResourceDescriptionInterface { - name: string; + id: string; + label: string; img: string; level?: number; } @@ -89,7 +90,7 @@ export class PlayerTextures { const resources: BodyResourceDescriptionListInterface = {}; for (const collection of category.collections) { for (const texture of collection.textures) { - resources[texture.id] = { name: texture.name, img: texture.url }; + resources[texture.id] = { id: texture.id, label: texture.name, img: texture.url }; } } return resources; @@ -97,5 +98,5 @@ export class PlayerTextures { } export const OBJECTS: BodyResourceDescriptionInterface[] = [ - { name: "teleportation", img: "resources/objects/teleportation.png" }, + { id: "teleportation", label: "Teleport", img: "resources/objects/teleportation.png" }, ]; diff --git a/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts b/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts index 0f872e5c..165a6063 100644 --- a/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts +++ b/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts @@ -14,7 +14,7 @@ export const loadAllLayers = (load: LoaderPlugin): BodyResourceDescriptionInterf 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); }); @@ -23,7 +23,7 @@ export const loadAllLayers = (load: LoaderPlugin): BodyResourceDescriptionInterf export const loadAllDefaultModels = (load: LoaderPlugin): BodyResourceDescriptionInterface[] => { const returnArray = Object.values(PlayerTextures.PLAYER_RESOURCES); 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; }; @@ -46,7 +46,7 @@ export const lazyLoadPlayerCharacterTextures = ( textures.forEach((texture) => { try { //TODO refactor - if (!loadPlugin.textureManager.exists(texture.name)) { + if (!loadPlugin.textureManager.exists(texture.id)) { promisesList.push( createLoadingPromise(loadPlugin, texture, { frameWidth: 32, @@ -69,7 +69,7 @@ export const lazyLoadPlayerCharacterTextures = ( //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; }) ); }; @@ -80,22 +80,22 @@ export const createLoadingPromise = ( 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 = () => { @@ -103,7 +103,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/Login/CustomizeScene.ts b/front/src/Phaser/Login/CustomizeScene.ts index b87e3640..e6c6a085 100644 --- a/front/src/Phaser/Login/CustomizeScene.ts +++ b/front/src/Phaser/Login/CustomizeScene.ts @@ -199,13 +199,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; @@ -283,7 +283,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++; } diff --git a/front/src/Phaser/Login/SelectCharacterScene.ts b/front/src/Phaser/Login/SelectCharacterScene.ts index 1708c634..6fd650b3 100644 --- a/front/src/Phaser/Login/SelectCharacterScene.ts +++ b/front/src/Phaser/Login/SelectCharacterScene.ts @@ -151,16 +151,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, }); @@ -185,7 +185,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() { @@ -270,7 +270,7 @@ export class SelectCharacterScene extends AbstractCharacterScene { protected updateSelectedPlayer(): void { 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); } diff --git a/pusher/package.json b/pusher/package.json index e729a262..6db02396 100644 --- a/pusher/package.json +++ b/pusher/package.json @@ -53,7 +53,8 @@ "prom-client": "^12.0.0", "qs": "^6.10.3", "query-string": "^6.13.3", - "uuidv4": "^6.0.7" + "uuidv4": "^6.0.7", + "zod": "^3.12.0" }, "devDependencies": { "@types/circular-json": "^0.4.0", diff --git a/pusher/src/Enum/PlayerTextures.ts b/pusher/src/Enum/PlayerTextures.ts index cc7c43e1..8c7407f9 100644 --- a/pusher/src/Enum/PlayerTextures.ts +++ b/pusher/src/Enum/PlayerTextures.ts @@ -1,46 +1,35 @@ 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 -export const isWokaTexture = new tg.IsInterface() - .withProperties({ - id: tg.isString, - name: tg.isString, - url: tg.isString, - position: tg.isNumber, - }) - .withOptionalProperties({ - tags: tg.isArray(tg.isString), - tintable: tg.isBoolean, - }) - .get(); +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 = tg.GuardedType; +export type WokaTexture = z.infer; -export const isWokaTextureCollection = new tg.IsInterface() - .withProperties({ - name: tg.isString, - position: tg.isNumber, - textures: tg.isArray(isWokaTexture), - }) - .get(); +const wokaTextureCollection = z.object({ + name: z.string(), + textures: z.array(wokaTexture), +}); -export type WokaTextureCollection = tg.GuardedType; +export type WokaTextureCollection = z.infer; -export const isWokaPartType = new tg.IsInterface() - .withProperties({ - collections: tg.isArray(isWokaTextureCollection), - }) - .withOptionalProperties({ - required: tg.isBoolean, - }) - .get(); +const wokaPartType = z.object({ + collections: z.array(wokaTextureCollection), + required: z.boolean().optional(), +}); -export type WokaPartType = tg.GuardedType; +export type WokaPartType = z.infer; -export const isWokaList = new tg.IsInterface().withStringIndexSignature(isWokaPartType).get(); +export const wokaList = z.record(wokaPartType); -export type WokaList = tg.GuardedType; +export type WokaList = z.infer; export const wokaPartNames = ["woka", "body", "eyes", "hair", "clothes", "hat", "accessory"]; diff --git a/pusher/src/Services/AdminWokaService.ts b/pusher/src/Services/AdminWokaService.ts index 96f276d9..52261412 100644 --- a/pusher/src/Services/AdminWokaService.ts +++ b/pusher/src/Services/AdminWokaService.ts @@ -1,6 +1,6 @@ -import axios from "axios"; +import axios, { AxiosResponse } from "axios"; import { ADMIN_API_TOKEN, ADMIN_API_URL } from "../Enum/EnvironmentVariable"; -import { isWokaList, WokaList } from "../Enum/PlayerTextures"; +import { wokaList, WokaList } from "../Enum/PlayerTextures"; import { WokaServiceInterface } from "./WokaServiceInterface"; class AdminWokaService implements WokaServiceInterface { @@ -9,7 +9,7 @@ class AdminWokaService implements WokaServiceInterface { */ getWokaList(roomUrl: string, token: string): Promise { return axios - .get(`${ADMIN_API_URL}/api/woka/list`, { + .get>(`${ADMIN_API_URL}/api/woka/list`, { headers: { Authorization: `${ADMIN_API_TOKEN}` }, params: { roomUrl, @@ -17,10 +17,7 @@ class AdminWokaService implements WokaServiceInterface { }, }) .then((res) => { - if (isWokaList(res.data)) { - throw new Error("Bad response format provided by woka list endpoint"); - } - return res.data; + return wokaList.parse(res.data); }) .catch((err) => { console.error(`Cannot get woka list from admin API with token: ${token}`, err); diff --git a/pusher/yarn.lock b/pusher/yarn.lock index d06edfd9..7243f352 100644 --- a/pusher/yarn.lock +++ b/pusher/yarn.lock @@ -2834,3 +2834,8 @@ z-schema@^4.2.3: 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==