Add endpoints on pusher to resolve wokas
This commit is contained in:
committed by
David Négrier
parent
f993aa4f5a
commit
2161a40e05
@@ -13,4 +13,6 @@ Check out the [contributing guide](../../CONTRIBUTING.md)
|
|||||||
|
|
||||||
## Front documentation
|
## Front documentation
|
||||||
|
|
||||||
|
- [How to add translations](how-to-translate.md)
|
||||||
- [How to add new functions in the scripting API](contributing-to-scripting-api.md)
|
- [How to add new functions in the scripting API](contributing-to-scripting-api.md)
|
||||||
|
- [About Wokas](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.
|
||||||
@@ -220,6 +220,16 @@ class ConnectionManager {
|
|||||||
if (this.localUser.textures.length === 0) {
|
if (this.localUser.textures.length === 0) {
|
||||||
this.localUser.textures = this._currentRoom.textures;
|
this.localUser.textures = this._currentRoom.textures;
|
||||||
} else {
|
} else {
|
||||||
|
// TODO: the local store should NOT be used as a buffer for all the texture we were authorized to have. Bad idea.
|
||||||
|
// Instead, it is the responsibility of the ADMIN to return the EXACT list of textures we can have in a given context
|
||||||
|
// + this list can change over time or over rooms.
|
||||||
|
|
||||||
|
// 1- a room could forbid a particular dress code. In this case, the user MUST change its skin.
|
||||||
|
// 2- a room can allow "external skins from other maps" => important: think about fediverse! => switch to URLs? (with a whitelist mechanism?) => but what about NFTs?
|
||||||
|
|
||||||
|
// Note: stocker des URL dans le localstorage pour les utilisateurs actuels: mauvaise idée (empêche de mettre l'URL à jour dans le futur) => en même temps, problème avec le portage de user d'un serveur à l'autre
|
||||||
|
// Réfléchir à une notion de "character server" ??
|
||||||
|
|
||||||
this._currentRoom.textures.forEach((newTexture) => {
|
this._currentRoom.textures.forEach((newTexture) => {
|
||||||
const alreadyExistTexture = this.localUser.textures.find((c) => newTexture.id === c.id);
|
const alreadyExistTexture = this.localUser.textures.find((c) => newTexture.id === c.id);
|
||||||
if (this.localUser.textures.findIndex((c) => newTexture.id === c.id) !== -1) {
|
if (this.localUser.textures.findIndex((c) => newTexture.id === c.id) !== -1) {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,7 @@ import { PrometheusController } from "./Controller/PrometheusController";
|
|||||||
import { DebugController } from "./Controller/DebugController";
|
import { DebugController } from "./Controller/DebugController";
|
||||||
import { AdminController } from "./Controller/AdminController";
|
import { AdminController } from "./Controller/AdminController";
|
||||||
import { OpenIdProfileController } from "./Controller/OpenIdProfileController";
|
import { OpenIdProfileController } from "./Controller/OpenIdProfileController";
|
||||||
|
import { WokaListController } from "./Controller/WokaListController";
|
||||||
import HyperExpress from "hyper-express";
|
import HyperExpress from "hyper-express";
|
||||||
import { cors } from "./Middleware/Cors";
|
import { cors } from "./Middleware/Cors";
|
||||||
|
|
||||||
@@ -29,6 +30,7 @@ class App {
|
|||||||
new DebugController(webserver);
|
new DebugController(webserver);
|
||||||
new AdminController(webserver);
|
new AdminController(webserver);
|
||||||
new OpenIdProfileController(webserver);
|
new OpenIdProfileController(webserver);
|
||||||
|
new WokaListController(webserver);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export class AuthenticateController extends BaseHttpController {
|
|||||||
//if not nonce and code, user connected in anonymous
|
//if not nonce and code, user connected in anonymous
|
||||||
//get data with identifier and return token
|
//get data with identifier and return token
|
||||||
if (!code && !nonce) {
|
if (!code && !nonce) {
|
||||||
return res.json(JSON.stringify({ ...resUserData, authToken: token }));
|
return res.json({ ...resUserData, authToken: token });
|
||||||
}
|
}
|
||||||
console.error("Token cannot to be check on OpenId provider");
|
console.error("Token cannot to be check on OpenId provider");
|
||||||
res.status(500);
|
res.status(500);
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { hasToken } from "../Middleware/HasToken";
|
||||||
|
import { BaseHttpController } from "./BaseHttpController";
|
||||||
|
import { ADMIN_API_URL } from "../Enum/EnvironmentVariable";
|
||||||
|
import { adminWokaService } from "..//Services/AdminWokaService";
|
||||||
|
import { localWokaService } from "..//Services/LocalWokaService";
|
||||||
|
import { WokaServiceInterface } from "src/Services/WokaServiceInterface";
|
||||||
|
import { Server } from "hyper-express";
|
||||||
|
|
||||||
|
export class WokaListController extends BaseHttpController {
|
||||||
|
private wokaService: WokaServiceInterface;
|
||||||
|
|
||||||
|
constructor(app: Server) {
|
||||||
|
super(app);
|
||||||
|
this.wokaService = ADMIN_API_URL ? adminWokaService : localWokaService;
|
||||||
|
}
|
||||||
|
|
||||||
|
routes() {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
|
this.app.get("/woka-list", { middlewares: [hasToken] }, async (req, res) => {
|
||||||
|
const token = req.header("Authorization");
|
||||||
|
const wokaList = await this.wokaService.getWokaList(token);
|
||||||
|
|
||||||
|
if (!wokaList) {
|
||||||
|
return res.status(500).send("Error on getting woka list");
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json(wokaList);
|
||||||
|
});
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
|
this.app.post("/woka-details", async (req, res) => {
|
||||||
|
const body = await req.json();
|
||||||
|
if (!body || !body.textureIds) {
|
||||||
|
return res.status(400);
|
||||||
|
}
|
||||||
|
|
||||||
|
const textureIds = body.textureIds;
|
||||||
|
const wokaDetails = await this.wokaService.fetchWokaDetails(textureIds);
|
||||||
|
|
||||||
|
if (!wokaDetails) {
|
||||||
|
return res.json({ details: [] });
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.json(wokaDetails);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
//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();
|
||||||
|
|
||||||
|
export type WokaTexture = tg.GuardedType<typeof isWokaTexture>;
|
||||||
|
|
||||||
|
export const isWokaTextureCollection = new tg.IsInterface()
|
||||||
|
.withProperties({
|
||||||
|
name: tg.isString,
|
||||||
|
position: tg.isNumber,
|
||||||
|
textures: tg.isArray(isWokaTexture),
|
||||||
|
})
|
||||||
|
.get();
|
||||||
|
|
||||||
|
export type WokaTextureCollection = tg.GuardedType<typeof isWokaTextureCollection>;
|
||||||
|
|
||||||
|
export const isWokaPartType = new tg.IsInterface()
|
||||||
|
.withProperties({
|
||||||
|
collections: tg.isArray(isWokaTextureCollection),
|
||||||
|
})
|
||||||
|
.withOptionalProperties({
|
||||||
|
required: tg.isBoolean,
|
||||||
|
})
|
||||||
|
.get();
|
||||||
|
|
||||||
|
export type WokaPartType = tg.GuardedType<typeof isWokaPartType>;
|
||||||
|
|
||||||
|
export const isWokaList = new tg.IsInterface().withStringIndexSignature(isWokaPartType).get();
|
||||||
|
|
||||||
|
export type WokaList = tg.GuardedType<typeof isWokaList>;
|
||||||
|
|
||||||
|
export const wokaPartNames = ["woka", "body", "eyes", "hair", "clothes", "hat", "accessory"];
|
||||||
|
|
||||||
|
export const isWokaDetail = new tg.IsInterface()
|
||||||
|
.withProperties({
|
||||||
|
id: tg.isString,
|
||||||
|
})
|
||||||
|
.withOptionalProperties({
|
||||||
|
texture: tg.isString,
|
||||||
|
})
|
||||||
|
.get();
|
||||||
|
|
||||||
|
export type WokaDetail = tg.GuardedType<typeof isWokaDetail>;
|
||||||
|
|
||||||
|
export const isWokaDetailsResult = new tg.IsInterface()
|
||||||
|
.withProperties({
|
||||||
|
details: tg.isArray(isWokaDetail),
|
||||||
|
})
|
||||||
|
.get();
|
||||||
|
|
||||||
|
export type WokaDetailsResult = tg.GuardedType<typeof isWokaDetailsResult>;
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
import { ADMIN_API_TOKEN, ADMIN_API_URL } from "../Enum/EnvironmentVariable";
|
||||||
|
import { isWokaDetailsResult, isWokaList, WokaDetailsResult, WokaList } from "../Enum/PlayerTextures";
|
||||||
|
import { WokaServiceInterface } from "./WokaServiceInterface";
|
||||||
|
|
||||||
|
class AdminWokaService implements WokaServiceInterface {
|
||||||
|
/**
|
||||||
|
* Returns the list of all available Wokas for the current user.
|
||||||
|
*/
|
||||||
|
getWokaList(token: string): Promise<WokaList | undefined> {
|
||||||
|
return axios
|
||||||
|
.get(`${ADMIN_API_URL}/api/woka-list/${token}`, {
|
||||||
|
headers: { Authorization: `${ADMIN_API_TOKEN}` },
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
if (isWokaList(res.data)) {
|
||||||
|
throw new Error("Bad response format provided by woka list endpoint");
|
||||||
|
}
|
||||||
|
return res.data;
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(`Cannot get woka list from admin API with token: ${token}`, err);
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
fetchWokaDetails(textureIds: string[]): Promise<WokaDetailsResult | undefined> {
|
||||||
|
return axios
|
||||||
|
.post(
|
||||||
|
`${ADMIN_API_URL}/api/woka-details`,
|
||||||
|
{
|
||||||
|
textureIds,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: { Authorization: `${ADMIN_API_TOKEN}` },
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then((res) => {
|
||||||
|
if (isWokaDetailsResult(res.data)) {
|
||||||
|
throw new Error("Bad response format provided by woka detail endpoint");
|
||||||
|
}
|
||||||
|
|
||||||
|
const result: WokaDetailsResult = res.data;
|
||||||
|
if (result.details.length !== textureIds.length) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const detail of result.details) {
|
||||||
|
if (!detail.texture) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.data;
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(`Cannot get woka details from admin API with ids: ${textureIds}`, err);
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const adminWokaService = new AdminWokaService();
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
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(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, 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, texture.url);
|
||||||
|
searchIds.delete(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (textureIds.length !== textures.size) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const details: WokaDetail[] = [];
|
||||||
|
|
||||||
|
textures.forEach((value, key) => {
|
||||||
|
details.push({
|
||||||
|
id: key,
|
||||||
|
texture: value,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return { details };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const localWokaService = new LocalWokaService();
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { WokaDetailsResult, WokaList } from "../Enum/PlayerTextures";
|
||||||
|
|
||||||
|
export interface WokaServiceInterface {
|
||||||
|
/**
|
||||||
|
* Returns the list of all available Wokas for the current user.
|
||||||
|
*/
|
||||||
|
getWokaList(token: string): Promise<WokaList | undefined>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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!)
|
||||||
|
*/
|
||||||
|
fetchWokaDetails(textureIds: string[]): Promise<WokaDetailsResult | undefined>;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user