Merge pull request #2075 from thecodingmachine/codeAPI
Refactoring Error Screen
This commit is contained in:
@@ -40,6 +40,7 @@
|
||||
},
|
||||
"homepage": "https://github.com/thecodingmachine/workadventure#readme",
|
||||
"dependencies": {
|
||||
"@anatine/zod-openapi": "^1.3.0",
|
||||
"axios": "^0.21.2",
|
||||
"circular-json": "^0.5.9",
|
||||
"debug": "^4.3.1",
|
||||
@@ -48,6 +49,7 @@
|
||||
"hyper-express": "^5.8.1",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"mkdirp": "^1.0.4",
|
||||
"openapi3-ts": "^2.0.2",
|
||||
"openid-client": "^4.7.4",
|
||||
"prom-client": "^12.0.0",
|
||||
"qs": "^6.10.3",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { v4 } from "uuid";
|
||||
import { BaseHttpController } from "./BaseHttpController";
|
||||
import { adminApi, FetchMemberDataByUuidResponse } from "../Services/AdminApi";
|
||||
import { FetchMemberDataByUuidResponse } from "../Services/AdminApi";
|
||||
import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager";
|
||||
import { parse } from "query-string";
|
||||
import { openIDClient } from "../Services/OpenIDClient";
|
||||
@@ -172,7 +172,8 @@ export class AuthenticateController extends BaseHttpController {
|
||||
authTokenData.identifier,
|
||||
playUri as string,
|
||||
IPAddress,
|
||||
[]
|
||||
[],
|
||||
req.header("accept-language")
|
||||
);
|
||||
|
||||
if (authTokenData.accessToken == undefined) {
|
||||
@@ -224,7 +225,13 @@ export class AuthenticateController extends BaseHttpController {
|
||||
|
||||
//Get user data from Admin Back Office
|
||||
//This is very important to create User Local in LocalStorage in WorkAdventure
|
||||
const data = await adminService.fetchMemberDataByUuid(email, playUri as string, IPAddress, []);
|
||||
const data = await adminService.fetchMemberDataByUuid(
|
||||
email,
|
||||
playUri as string,
|
||||
IPAddress,
|
||||
[],
|
||||
req.header("accept-language")
|
||||
);
|
||||
|
||||
return res.json({ ...data, authToken, username: userInfo?.username, locale: userInfo?.locale });
|
||||
} catch (e) {
|
||||
@@ -327,13 +334,18 @@ export class AuthenticateController extends BaseHttpController {
|
||||
|
||||
try {
|
||||
if (typeof organizationMemberToken != "string") throw new Error("No organization token");
|
||||
const data = await adminApi.fetchMemberDataByToken(organizationMemberToken, playUri);
|
||||
const data = await adminService.fetchMemberDataByToken(
|
||||
organizationMemberToken,
|
||||
playUri,
|
||||
req.header("accept-language")
|
||||
);
|
||||
const userUuid = data.userUuid;
|
||||
const email = data.email;
|
||||
const roomUrl = data.roomUrl;
|
||||
const mapUrlStart = data.mapUrlStart;
|
||||
|
||||
const authToken = jwtTokenManager.createAuthToken(email || userUuid);
|
||||
|
||||
res.json({
|
||||
authToken,
|
||||
userUuid,
|
||||
@@ -419,7 +431,7 @@ export class AuthenticateController extends BaseHttpController {
|
||||
|
||||
//get login profile
|
||||
res.status(302);
|
||||
res.setHeader("Location", adminApi.getProfileUrl(authTokenData.accessToken));
|
||||
res.setHeader("Location", adminService.getProfileUrl(authTokenData.accessToken));
|
||||
res.send("");
|
||||
return;
|
||||
} catch (error) {
|
||||
@@ -485,7 +497,8 @@ export class AuthenticateController extends BaseHttpController {
|
||||
* @param email
|
||||
* @param playUri
|
||||
* @param IPAddress
|
||||
* @return FetchMemberDataByUuidResponse|object
|
||||
* @return
|
||||
|object
|
||||
* @private
|
||||
*/
|
||||
private async getUserByUserIdentifier(
|
||||
@@ -503,7 +516,7 @@ export class AuthenticateController extends BaseHttpController {
|
||||
userRoomToken: undefined,
|
||||
};
|
||||
try {
|
||||
data = await adminApi.fetchMemberDataByUuid(email, playUri, IPAddress, []);
|
||||
data = await adminService.fetchMemberDataByUuid(email, playUri, IPAddress, []);
|
||||
} catch (err) {
|
||||
console.error("openIDCallback => fetchMemberDataByUuid", err);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Server } from "hyper-express";
|
||||
import Response from "hyper-express/types/components/http/Response";
|
||||
import axios from "axios";
|
||||
import { isErrorApiData } from "../Messages/JsonMessages/ErrorApiData";
|
||||
|
||||
export class BaseHttpController {
|
||||
constructor(protected app: Server) {
|
||||
@@ -31,12 +32,15 @@ export class BaseHttpController {
|
||||
|
||||
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)
|
||||
);
|
||||
const errorType = isErrorApiData.safeParse(e?.response?.data);
|
||||
if (!errorType.success) {
|
||||
res.send(
|
||||
"An error occurred: " +
|
||||
e.response.status +
|
||||
" " +
|
||||
(e.response.data && e.response.data.message ? e.response.data.message : e.response.statusText)
|
||||
);
|
||||
} else res.json(errorType.data);
|
||||
return;
|
||||
} else {
|
||||
res.status(500);
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
import { UserMovesMessage } from "../Messages/generated/messages_pb";
|
||||
import { parse } from "query-string";
|
||||
import { AdminSocketTokenData, jwtTokenManager, tokenInvalidException } from "../Services/JWTTokenManager";
|
||||
import { adminApi, FetchMemberDataByUuidResponse } from "../Services/AdminApi";
|
||||
import { FetchMemberDataByUuidResponse } from "../Services/AdminApi";
|
||||
import { socketManager } from "../Services/SocketManager";
|
||||
import { emitInBatch } from "../Services/IoSocketHelpers";
|
||||
import { ADMIN_API_URL, ADMIN_SOCKETS_TOKEN, DISABLE_ANONYMOUS, SOCKET_IDLE_TIMER } from "../Enum/EnvironmentVariable";
|
||||
@@ -39,6 +39,8 @@ import { localWokaService } from "../Services/LocalWokaService";
|
||||
import { WebSocket } from "uWebSockets.js";
|
||||
import { WokaDetail } from "../Messages/JsonMessages/PlayerTextures";
|
||||
import { z } from "zod";
|
||||
import { adminService } from "../Services/AdminService";
|
||||
import { ErrorApiData, isErrorApiData } from "../Messages/JsonMessages/ErrorApiData";
|
||||
|
||||
/**
|
||||
* The object passed between the "open" and the "upgrade" methods when opening a websocket
|
||||
@@ -66,13 +68,21 @@ interface UpgradeData {
|
||||
};
|
||||
}
|
||||
|
||||
interface UpgradeFailedData {
|
||||
interface UpgradeFailedInvalidData {
|
||||
rejected: true;
|
||||
reason: "tokenInvalid" | "textureInvalid" | null;
|
||||
message: string;
|
||||
roomId: string;
|
||||
}
|
||||
|
||||
interface UpgradeFailedErrorData {
|
||||
rejected: true;
|
||||
reason: "error";
|
||||
error: ErrorApiData;
|
||||
}
|
||||
|
||||
type UpgradeFailedData = UpgradeFailedErrorData | UpgradeFailedInvalidData;
|
||||
|
||||
export class IoSocketController {
|
||||
private nextUserId: number = 1;
|
||||
|
||||
@@ -234,6 +244,7 @@ export class IoSocketController {
|
||||
const websocketProtocol = req.getHeader("sec-websocket-protocol");
|
||||
const websocketExtensions = req.getHeader("sec-websocket-extensions");
|
||||
const IPAddress = req.getHeader("x-forwarded-for");
|
||||
const locale = req.getHeader("accept-language");
|
||||
|
||||
const roomId = query.roomId;
|
||||
try {
|
||||
@@ -302,41 +313,46 @@ export class IoSocketController {
|
||||
if (ADMIN_API_URL) {
|
||||
try {
|
||||
try {
|
||||
userData = await adminApi.fetchMemberDataByUuid(
|
||||
userData = await adminService.fetchMemberDataByUuid(
|
||||
userIdentifier,
|
||||
roomId,
|
||||
IPAddress,
|
||||
characterLayers
|
||||
characterLayers,
|
||||
locale
|
||||
);
|
||||
} catch (err) {
|
||||
if (Axios.isAxiosError(err)) {
|
||||
if (err?.response?.status == 404) {
|
||||
// If we get an HTTP 404, the token is invalid. Let's perform an anonymous login!
|
||||
|
||||
console.warn(
|
||||
'Cannot find user with email "' +
|
||||
(userIdentifier || "anonymous") +
|
||||
'". Performing an anonymous login instead.'
|
||||
);
|
||||
} else if (err?.response?.status == 403) {
|
||||
// If we get an HTTP 403, the world is full. We need to broadcast a special error to the client.
|
||||
// we finish immediately the upgrade then we will close the socket as soon as it starts opening.
|
||||
const errorType = isErrorApiData.safeParse(err?.response?.data);
|
||||
if (errorType.success) {
|
||||
return res.upgrade(
|
||||
{
|
||||
rejected: true,
|
||||
message: err?.response?.data.message,
|
||||
reason: "error",
|
||||
status: err?.response?.status,
|
||||
roomId,
|
||||
},
|
||||
error: errorType.data,
|
||||
} as UpgradeFailedData,
|
||||
websocketKey,
|
||||
websocketProtocol,
|
||||
websocketExtensions,
|
||||
context
|
||||
);
|
||||
} else {
|
||||
return res.upgrade(
|
||||
{
|
||||
rejected: true,
|
||||
reason: null,
|
||||
status: 500,
|
||||
message: err?.response?.data,
|
||||
roomId: roomId,
|
||||
} as UpgradeFailedData,
|
||||
websocketKey,
|
||||
websocketProtocol,
|
||||
websocketExtensions,
|
||||
context
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
memberMessages = userData.messages;
|
||||
memberTags = userData.tags;
|
||||
@@ -480,8 +496,8 @@ export class IoSocketController {
|
||||
socketManager.emitTokenExpiredMessage(ws);
|
||||
} else if (ws.reason === "textureInvalid") {
|
||||
socketManager.emitInvalidTextureMessage(ws);
|
||||
} else if (ws.message === "World is full") {
|
||||
socketManager.emitWorldFullMessage(ws);
|
||||
} else if (ws.reason === "error") {
|
||||
socketManager.emitErrorScreenMessage(ws, ws.error);
|
||||
} else {
|
||||
socketManager.emitConnexionErrorMessage(ws, ws.message);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import { adminApi } from "../Services/AdminApi";
|
||||
import { ADMIN_API_URL, DISABLE_ANONYMOUS } from "../Enum/EnvironmentVariable";
|
||||
import { GameRoomPolicyTypes } from "../Model/PusherRoom";
|
||||
import { isMapDetailsData, MapDetailsData } from "../Messages/JsonMessages/MapDetailsData";
|
||||
import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager";
|
||||
import { InvalidTokenError } from "./InvalidTokenError";
|
||||
import { DISABLE_ANONYMOUS } from "../Enum/EnvironmentVariable";
|
||||
import { isMapDetailsData } from "../Messages/JsonMessages/MapDetailsData";
|
||||
import { parse } from "query-string";
|
||||
import { BaseHttpController } from "./BaseHttpController";
|
||||
import { adminService } from "../Services/AdminService";
|
||||
|
||||
export class MapController extends BaseHttpController {
|
||||
// Returns a map mapping map name to file name of the map
|
||||
@@ -107,66 +104,17 @@ export class MapController extends BaseHttpController {
|
||||
return;
|
||||
}
|
||||
|
||||
// If no admin URL is set, let's react on '/_/[instance]/[map url]' URLs
|
||||
if (!ADMIN_API_URL) {
|
||||
const roomUrl = new URL(query.playUri);
|
||||
|
||||
const match = /\/_\/[^/]+\/(.+)/.exec(roomUrl.pathname);
|
||||
if (!match) {
|
||||
res.status(404);
|
||||
res.json({});
|
||||
return;
|
||||
}
|
||||
|
||||
const mapUrl = roomUrl.protocol + "//" + match[1];
|
||||
|
||||
res.json({
|
||||
mapUrl,
|
||||
policy_type: GameRoomPolicyTypes.ANONYMOUS_POLICY,
|
||||
roomSlug: null, // Deprecated
|
||||
group: null,
|
||||
tags: [],
|
||||
contactPage: null,
|
||||
authenticationMandatory: DISABLE_ANONYMOUS,
|
||||
} as MapDetailsData);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
let userId: string | undefined = undefined;
|
||||
if (query.authToken != undefined) {
|
||||
let authTokenData: AuthTokenData;
|
||||
try {
|
||||
authTokenData = jwtTokenManager.verifyJWTToken(query.authToken as string);
|
||||
userId = authTokenData.identifier;
|
||||
} catch (e) {
|
||||
try {
|
||||
// Decode token, in this case we don't need to create new token.
|
||||
authTokenData = jwtTokenManager.verifyJWTToken(query.authToken as string, true);
|
||||
userId = authTokenData.identifier;
|
||||
console.info("JWT expire, but decoded", userId);
|
||||
} catch (e) {
|
||||
if (e instanceof InvalidTokenError) {
|
||||
// The token was not good, redirect user on login page
|
||||
res.status(401);
|
||||
res.send("Token decrypted error");
|
||||
return;
|
||||
} else {
|
||||
this.castErrorToResponse(e, res);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const mapDetails = isMapDetailsData.parse(
|
||||
await adminApi.fetchMapDetails(query.playUri as string, userId)
|
||||
await adminService.fetchMapDetails(
|
||||
query.playUri as string,
|
||||
query.authToken as string,
|
||||
req.header("accept-language")
|
||||
)
|
||||
);
|
||||
|
||||
if (DISABLE_ANONYMOUS) {
|
||||
mapDetails.authenticationMandatory = true;
|
||||
}
|
||||
if (DISABLE_ANONYMOUS) mapDetails.authenticationMandatory = true;
|
||||
|
||||
res.json(mapDetails);
|
||||
return;
|
||||
|
||||
@@ -7,6 +7,8 @@ import { z } from "zod";
|
||||
import { isWokaDetail } from "../Messages/JsonMessages/PlayerTextures";
|
||||
import qs from "qs";
|
||||
import { AdminInterface } from "./AdminInterface";
|
||||
import { AuthTokenData, jwtTokenManager } from "./JWTTokenManager";
|
||||
import { InvalidTokenError } from "../Controller/InvalidTokenError";
|
||||
|
||||
export interface AdminBannedData {
|
||||
is_banned: boolean;
|
||||
@@ -27,14 +29,37 @@ export const isFetchMemberDataByUuidResponse = z.object({
|
||||
export type FetchMemberDataByUuidResponse = z.infer<typeof isFetchMemberDataByUuidResponse>;
|
||||
|
||||
class AdminApi implements AdminInterface {
|
||||
/**
|
||||
* @var playUri: is url of the room
|
||||
* @var userId: can to be undefined or email or uuid
|
||||
* @return MapDetailsData|RoomRedirect
|
||||
*/
|
||||
async fetchMapDetails(playUri: string, userId?: string): Promise<MapDetailsData | RoomRedirect> {
|
||||
if (!ADMIN_API_URL) {
|
||||
return Promise.reject(new Error("No admin backoffice set!"));
|
||||
async fetchMapDetails(
|
||||
playUri: string,
|
||||
authToken?: string,
|
||||
locale?: string
|
||||
): Promise<MapDetailsData | RoomRedirect> {
|
||||
let userId: string | undefined = undefined;
|
||||
if (authToken != undefined) {
|
||||
let authTokenData: AuthTokenData;
|
||||
try {
|
||||
authTokenData = jwtTokenManager.verifyJWTToken(authToken);
|
||||
userId = authTokenData.identifier;
|
||||
} catch (e) {
|
||||
try {
|
||||
// Decode token, in this case we don't need to create new token.
|
||||
authTokenData = jwtTokenManager.verifyJWTToken(authToken, true);
|
||||
userId = authTokenData.identifier;
|
||||
console.info("JWT expire, but decoded", userId);
|
||||
} catch (e) {
|
||||
if (e instanceof InvalidTokenError) {
|
||||
throw new Error("Token decrypted error");
|
||||
// The token was not good, redirect user on login page
|
||||
//res.status(401);
|
||||
//res.send("Token decrypted error");
|
||||
//return;
|
||||
} else {
|
||||
throw new Error("Error on decryption of token :" + e);
|
||||
//this.castErrorToResponse(e, res);
|
||||
//return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const params: { playUri: string; userId?: string } = {
|
||||
@@ -43,7 +68,7 @@ class AdminApi implements AdminInterface {
|
||||
};
|
||||
|
||||
const res = await Axios.get<unknown, AxiosResponse<unknown>>(ADMIN_API_URL + "/api/map", {
|
||||
headers: { Authorization: `${ADMIN_API_TOKEN}` },
|
||||
headers: { Authorization: `${ADMIN_API_TOKEN}`, "Accept-Language": locale ?? "en" },
|
||||
params,
|
||||
});
|
||||
|
||||
@@ -69,11 +94,9 @@ class AdminApi implements AdminInterface {
|
||||
userIdentifier: string,
|
||||
playUri: string,
|
||||
ipAddress: string,
|
||||
characterLayers: string[]
|
||||
characterLayers: string[],
|
||||
locale?: string
|
||||
): Promise<FetchMemberDataByUuidResponse> {
|
||||
if (!ADMIN_API_URL) {
|
||||
return Promise.reject(new Error("No admin backoffice set!"));
|
||||
}
|
||||
const res = await Axios.get<unknown, AxiosResponse<unknown>>(ADMIN_API_URL + "/api/room/access", {
|
||||
params: {
|
||||
userIdentifier,
|
||||
@@ -81,7 +104,7 @@ class AdminApi implements AdminInterface {
|
||||
ipAddress,
|
||||
characterLayers,
|
||||
},
|
||||
headers: { Authorization: `${ADMIN_API_TOKEN}` },
|
||||
headers: { Authorization: `${ADMIN_API_TOKEN}`, "Accept-Language": locale ?? "en" },
|
||||
paramsSerializer: (p) => {
|
||||
return qs.stringify(p, { arrayFormat: "brackets" });
|
||||
},
|
||||
@@ -100,14 +123,15 @@ class AdminApi implements AdminInterface {
|
||||
);
|
||||
}
|
||||
|
||||
async fetchMemberDataByToken(organizationMemberToken: string, playUri: string | null): Promise<AdminApiData> {
|
||||
if (!ADMIN_API_URL) {
|
||||
return Promise.reject(new Error("No admin backoffice set!"));
|
||||
}
|
||||
async fetchMemberDataByToken(
|
||||
organizationMemberToken: string,
|
||||
playUri: string | null,
|
||||
locale?: string
|
||||
): Promise<AdminApiData> {
|
||||
//todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case.
|
||||
const res = await Axios.get(ADMIN_API_URL + "/api/login-url/" + organizationMemberToken, {
|
||||
params: { playUri },
|
||||
headers: { Authorization: `${ADMIN_API_TOKEN}` },
|
||||
headers: { Authorization: `${ADMIN_API_TOKEN}`, "Accept-Language": locale ?? "en" },
|
||||
});
|
||||
|
||||
const adminApiData = isAdminApiData.safeParse(res.data);
|
||||
@@ -125,11 +149,9 @@ class AdminApi implements AdminInterface {
|
||||
reportedUserUuid: string,
|
||||
reportedUserComment: string,
|
||||
reporterUserUuid: string,
|
||||
reportWorldSlug: string
|
||||
reportWorldSlug: string,
|
||||
locale?: string
|
||||
) {
|
||||
if (!ADMIN_API_URL) {
|
||||
return Promise.reject(new Error("No admin backoffice set!"));
|
||||
}
|
||||
return Axios.post(
|
||||
`${ADMIN_API_URL}/api/report`,
|
||||
{
|
||||
@@ -139,15 +161,17 @@ class AdminApi implements AdminInterface {
|
||||
reportWorldSlug,
|
||||
},
|
||||
{
|
||||
headers: { Authorization: `${ADMIN_API_TOKEN}` },
|
||||
headers: { Authorization: `${ADMIN_API_TOKEN}`, "Accept-Language": locale ?? "en" },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async verifyBanUser(userUuid: string, ipAddress: string, roomUrl: string): Promise<AdminBannedData> {
|
||||
if (!ADMIN_API_URL) {
|
||||
return Promise.reject(new Error("No admin backoffice set!"));
|
||||
}
|
||||
async verifyBanUser(
|
||||
userUuid: string,
|
||||
ipAddress: string,
|
||||
roomUrl: string,
|
||||
locale?: string
|
||||
): Promise<AdminBannedData> {
|
||||
//todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case.
|
||||
return Axios.get(
|
||||
ADMIN_API_URL +
|
||||
@@ -158,28 +182,20 @@ class AdminApi implements AdminInterface {
|
||||
encodeURIComponent(userUuid) +
|
||||
"&roomUrl=" +
|
||||
encodeURIComponent(roomUrl),
|
||||
{ headers: { Authorization: `${ADMIN_API_TOKEN}` } }
|
||||
{ headers: { Authorization: `${ADMIN_API_TOKEN}`, "Accept-Language": locale ?? "en" } }
|
||||
).then((data) => {
|
||||
return data.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getUrlRoomsFromSameWorld(roomUrl: string): Promise<string[]> {
|
||||
if (!ADMIN_API_URL) {
|
||||
return Promise.reject(new Error("No admin backoffice set!"));
|
||||
}
|
||||
|
||||
async getUrlRoomsFromSameWorld(roomUrl: string, locale?: string): Promise<string[]> {
|
||||
return Axios.get(ADMIN_API_URL + "/api/room/sameWorld" + "?roomUrl=" + encodeURIComponent(roomUrl), {
|
||||
headers: { Authorization: `${ADMIN_API_TOKEN}` },
|
||||
headers: { Authorization: `${ADMIN_API_TOKEN}`, "Accept-Language": locale ?? "en" },
|
||||
}).then((data) => {
|
||||
return data.data;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param accessToken
|
||||
*/
|
||||
getProfileUrl(accessToken: string): string {
|
||||
if (!OPID_PROFILE_SCREEN_PROVIDER) {
|
||||
throw new Error("No admin backoffice set!");
|
||||
@@ -187,7 +203,7 @@ class AdminApi implements AdminInterface {
|
||||
return `${OPID_PROFILE_SCREEN_PROVIDER}?accessToken=${accessToken}`;
|
||||
}
|
||||
|
||||
async logoutOauth(token: string) {
|
||||
async logoutOauth(token: string): Promise<void> {
|
||||
await Axios.get(ADMIN_API_URL + `/oauth/logout?token=${token}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,82 @@
|
||||
import { FetchMemberDataByUuidResponse } from "./AdminApi";
|
||||
import { AdminBannedData, FetchMemberDataByUuidResponse } from "./AdminApi";
|
||||
import { MapDetailsData } from "../Messages/JsonMessages/MapDetailsData";
|
||||
import { RoomRedirect } from "../Messages/JsonMessages/RoomRedirect";
|
||||
import { AdminApiData } from "../Messages/JsonMessages/AdminApiData";
|
||||
|
||||
export interface AdminInterface {
|
||||
/**
|
||||
* @var playUri: is url of the room
|
||||
* @var userIdentifier: can to be undefined or email or uuid
|
||||
* @var ipAddress
|
||||
* @var characterLayers
|
||||
* @return MapDetailsData|RoomRedirect
|
||||
*/
|
||||
fetchMemberDataByUuid(
|
||||
userIdentifier: string,
|
||||
playUri: string,
|
||||
ipAddress: string,
|
||||
characterLayers: string[]
|
||||
characterLayers: string[],
|
||||
locale?: string
|
||||
): Promise<FetchMemberDataByUuidResponse>;
|
||||
|
||||
/**
|
||||
* @var playUri: is url of the room
|
||||
* @var userId: can to be undefined or email or uuid
|
||||
* @return MapDetailsData|RoomRedirect
|
||||
*/
|
||||
fetchMapDetails(playUri: string, authToken?: string, locale?: string): Promise<MapDetailsData | RoomRedirect>;
|
||||
|
||||
/**
|
||||
* @param locale
|
||||
* @param organizationMemberToken
|
||||
* @param playUri
|
||||
* @return AdminApiData
|
||||
*/
|
||||
fetchMemberDataByToken(
|
||||
organizationMemberToken: string,
|
||||
playUri: string | null,
|
||||
locale?: string
|
||||
): Promise<AdminApiData>;
|
||||
|
||||
/**
|
||||
* @param locale
|
||||
* @param reportedUserUuid
|
||||
* @param reportedUserComment
|
||||
* @param reporterUserUuid
|
||||
* @param reportWorldSlug
|
||||
*/
|
||||
reportPlayer(
|
||||
reportedUserUuid: string,
|
||||
reportedUserComment: string,
|
||||
reporterUserUuid: string,
|
||||
reportWorldSlug: string,
|
||||
locale?: string
|
||||
): Promise<unknown>;
|
||||
|
||||
/**
|
||||
* @param locale
|
||||
* @param userUuid
|
||||
* @param ipAddress
|
||||
* @param roomUrl
|
||||
* @return AdminBannedData
|
||||
*/
|
||||
verifyBanUser(userUuid: string, ipAddress: string, roomUrl: string, locale?: string): Promise<AdminBannedData>;
|
||||
|
||||
/**
|
||||
* @param locale
|
||||
* @param roomUrl
|
||||
* @return string[]
|
||||
*/
|
||||
getUrlRoomsFromSameWorld(roomUrl: string, locale?: string): Promise<string[]>;
|
||||
|
||||
/**
|
||||
* @param accessToken
|
||||
* @return string
|
||||
*/
|
||||
getProfileUrl(accessToken: string): string;
|
||||
|
||||
/**
|
||||
* @param token
|
||||
*/
|
||||
logoutOauth(token: string): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { FetchMemberDataByUuidResponse } from "./AdminApi";
|
||||
import { AdminBannedData, FetchMemberDataByUuidResponse } from "./AdminApi";
|
||||
import { AdminInterface } from "./AdminInterface";
|
||||
import { MapDetailsData } from "../Messages/JsonMessages/MapDetailsData";
|
||||
import { RoomRedirect } from "../Messages/JsonMessages/RoomRedirect";
|
||||
import { GameRoomPolicyTypes } from "../Model/PusherRoom";
|
||||
import { DISABLE_ANONYMOUS } from "../Enum/EnvironmentVariable";
|
||||
import { AdminApiData } from "../Messages/JsonMessages/AdminApiData";
|
||||
|
||||
/**
|
||||
* A local class mocking a real admin if no admin is configured.
|
||||
@@ -12,7 +17,9 @@ class LocalAdmin implements AdminInterface {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
ipAddress: string,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
characterLayers: string[]
|
||||
characterLayers: string[],
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
locale?: string
|
||||
): Promise<FetchMemberDataByUuidResponse> {
|
||||
return Promise.resolve({
|
||||
email: userIdentifier,
|
||||
@@ -24,6 +31,99 @@ class LocalAdmin implements AdminInterface {
|
||||
userRoomToken: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
fetchMapDetails(
|
||||
playUri: string,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
authToken?: string,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
locale?: string
|
||||
): Promise<MapDetailsData | RoomRedirect> {
|
||||
const roomUrl = new URL(playUri);
|
||||
|
||||
const match = /\/_\/[^/]+\/(.+)/.exec(roomUrl.pathname);
|
||||
if (!match) {
|
||||
throw new Error("URL format is not good");
|
||||
}
|
||||
|
||||
const mapUrl = roomUrl.protocol + "//" + match[1];
|
||||
|
||||
return Promise.resolve({
|
||||
mapUrl,
|
||||
policy_type: GameRoomPolicyTypes.ANONYMOUS_POLICY,
|
||||
tags: [],
|
||||
authenticationMandatory: DISABLE_ANONYMOUS,
|
||||
roomSlug: null,
|
||||
contactPage: null,
|
||||
group: null,
|
||||
iframeAuthentication: null,
|
||||
loadingLogo: null,
|
||||
loginSceneLogo: null,
|
||||
});
|
||||
}
|
||||
|
||||
async fetchMemberDataByToken(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
organizationMemberToken: string,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
playUri: string | null,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
locale?: string
|
||||
): Promise<AdminApiData> {
|
||||
return Promise.reject(new Error("No admin backoffice set!"));
|
||||
}
|
||||
|
||||
reportPlayer(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
reportedUserUuid: string,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
reportedUserComment: string,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
reporterUserUuid: string,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
reportWorldSlug: string,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
locale?: string
|
||||
) {
|
||||
return Promise.reject(new Error("No admin backoffice set!"));
|
||||
}
|
||||
|
||||
async verifyBanUser(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
userUuid: string,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
ipAddress: string,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
roomUrl: string,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
locale?: string
|
||||
): Promise<AdminBannedData> {
|
||||
return Promise.reject(new Error("No admin backoffice set!"));
|
||||
}
|
||||
|
||||
async getUrlRoomsFromSameWorld(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
roomUrl: string,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
locale?: string
|
||||
): Promise<string[]> {
|
||||
return Promise.reject(new Error("No admin backoffice set!"));
|
||||
}
|
||||
|
||||
getProfileUrl(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
accessToken: string
|
||||
): string {
|
||||
new Error("No admin backoffice set!");
|
||||
return "";
|
||||
}
|
||||
|
||||
async logoutOauth(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
token: string
|
||||
): Promise<void> {
|
||||
return Promise.reject(new Error("No admin backoffice set!"));
|
||||
}
|
||||
}
|
||||
|
||||
export const localAdmin = new LocalAdmin();
|
||||
|
||||
@@ -39,10 +39,10 @@ import {
|
||||
PlayerDetailsUpdatedMessage,
|
||||
LockGroupPromptMessage,
|
||||
InvalidTextureMessage,
|
||||
ErrorScreenMessage,
|
||||
} 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";
|
||||
import { adminApi } from "./AdminApi";
|
||||
import { emitInBatch } from "./IoSocketHelpers";
|
||||
import Jwt from "jsonwebtoken";
|
||||
import { clientEventsEmitter } from "./ClientEventsEmitter";
|
||||
@@ -53,6 +53,9 @@ import Debug from "debug";
|
||||
import { ExAdminSocketInterface } from "../Model/Websocket/ExAdminSocketInterface";
|
||||
import { compressors } from "hyper-express";
|
||||
import { isMapDetailsData } from "../Messages/JsonMessages/MapDetailsData";
|
||||
import { adminService } from "./AdminService";
|
||||
import { ErrorApiData } from "../Messages/JsonMessages/ErrorApiData";
|
||||
import { BoolValue, Int32Value, StringValue } from "google-protobuf/google/protobuf/wrappers_pb";
|
||||
|
||||
const debug = Debug("socket");
|
||||
|
||||
@@ -375,7 +378,8 @@ export class SocketManager implements ZoneEventListener {
|
||||
|
||||
async handleReportMessage(client: ExSocketInterface, reportPlayerMessage: ReportPlayerMessage) {
|
||||
try {
|
||||
await adminApi.reportPlayer(
|
||||
await adminService.reportPlayer(
|
||||
"en",
|
||||
reportPlayerMessage.getReporteduseruuid(),
|
||||
reportPlayerMessage.getReportcomment(),
|
||||
client.userUuid,
|
||||
@@ -461,7 +465,7 @@ export class SocketManager implements ZoneEventListener {
|
||||
}
|
||||
|
||||
public async updateRoomWithAdminData(room: PusherRoom): Promise<void> {
|
||||
const data = await adminApi.fetchMapDetails(room.roomUrl);
|
||||
const data = await adminService.fetchMapDetails(room.roomUrl);
|
||||
const mapDetailsData = isMapDetailsData.safeParse(data);
|
||||
|
||||
if (mapDetailsData.success) {
|
||||
@@ -661,6 +665,34 @@ export class SocketManager implements ZoneEventListener {
|
||||
client.send(serverToClientMessage.serializeBinary().buffer, true);
|
||||
}
|
||||
|
||||
public emitErrorScreenMessage(client: compressors.WebSocket, errorApi: ErrorApiData) {
|
||||
const errorMessage = new ErrorScreenMessage();
|
||||
errorMessage.setType(errorApi.type);
|
||||
if (errorApi.type == "retry" || errorApi.type == "error") {
|
||||
errorMessage.setCode(new StringValue().setValue(errorApi.code));
|
||||
errorMessage.setTitle(new StringValue().setValue(errorApi.title));
|
||||
errorMessage.setSubtitle(new StringValue().setValue(errorApi.subtitle));
|
||||
errorMessage.setDetails(new StringValue().setValue(errorApi.details));
|
||||
errorMessage.setImage(new StringValue().setValue(errorApi.image));
|
||||
}
|
||||
if (errorApi.type == "retry") {
|
||||
if (errorApi.buttonTitle) errorMessage.setButtontitle(new StringValue().setValue(errorApi.buttonTitle));
|
||||
if (errorApi.canRetryManual !== undefined)
|
||||
errorMessage.setCanretrymanual(new BoolValue().setValue(errorApi.canRetryManual));
|
||||
if (errorApi.timeToRetry)
|
||||
errorMessage.setTimetoretry(new Int32Value().setValue(Number(errorApi.timeToRetry)));
|
||||
}
|
||||
if (errorApi.type == "redirect" && errorApi.urlToRedirect)
|
||||
errorMessage.setUrltoredirect(new StringValue().setValue(errorApi.urlToRedirect));
|
||||
|
||||
const serverToClientMessage = new ServerToClientMessage();
|
||||
serverToClientMessage.setErrorscreenmessage(errorMessage);
|
||||
|
||||
//if (!client.disconnecting) {
|
||||
client.send(serverToClientMessage.serializeBinary().buffer, true);
|
||||
//}
|
||||
}
|
||||
|
||||
private refreshRoomData(roomId: string, versionNumber: number): void {
|
||||
const room = this.rooms.get(roomId);
|
||||
//this function is run for every users connected to the room, so we need to make sure the room wasn't already refreshed.
|
||||
@@ -688,7 +720,7 @@ export class SocketManager implements ZoneEventListener {
|
||||
let tabUrlRooms: string[];
|
||||
|
||||
if (playGlobalMessageEvent.getBroadcasttoworld()) {
|
||||
tabUrlRooms = await adminApi.getUrlRoomsFromSameWorld(clientRoomUrl);
|
||||
tabUrlRooms = await adminService.getUrlRoomsFromSameWorld("en", clientRoomUrl);
|
||||
} else {
|
||||
tabUrlRooms = [clientRoomUrl];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user