Refactoring Woka management (#1810)

* 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 <piotr@ltmp.co>
Co-authored-by: David Négrier <d.negrier@thecodingmachine.com>

* Add returns on pusher endpoints

Co-authored-by: Alexis Faizeau <a.faizeau@workadventu.re>
Co-authored-by: Hanusiak Piotr <piotr@ltmp.co>
Co-authored-by: Piotr Hanusiak <wacneg@gmail.com>
This commit is contained in:
David Négrier
2022-03-11 17:02:58 +01:00
committed by GitHub
parent d3862a3afd
commit 6540f15c5b
71 changed files with 3979 additions and 1810 deletions
+34 -16
View File
@@ -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<typeof isFetchMemberDataByUuidResponse>;
class AdminApi {
/**
@@ -48,15 +56,25 @@ class AdminApi {
async fetchMemberDataByUuid(
userIdentifier: string | null,
roomId: string,
ipAddress: string
ipAddress: string,
characterLayers: string[]
): Promise<FetchMemberDataByUuidResponse> {
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<unknown, AxiosResponse<unknown>>(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;
}
+29
View File
@@ -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<WokaList | undefined> {
return axios
.get<unknown, AxiosResponse<unknown>>(`${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();
+74
View File
@@ -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<WokaList | undefined> {
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<WokaDetailsResult | undefined> {
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();
+22 -36
View File
@@ -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);
+5
View File
@@ -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;
@@ -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<WokaList | undefined>;
}