Merge branch 'develop' into feature/player-companion
This commit is contained in:
commit
4aa082d83b
25 changed files with 421 additions and 166 deletions
|
@ -2,7 +2,7 @@ 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} from "../Messages/generated/messages_pb";
|
||||
import {AdminRoomMessage, WorldFullWarningToRoomMessage, RefreshRoomPromptMessage} from "../Messages/generated/messages_pb";
|
||||
|
||||
|
||||
export class AdminController extends BaseController{
|
||||
|
@ -11,6 +11,56 @@ export class AdminController extends BaseController{
|
|||
super();
|
||||
this.App = App;
|
||||
this.receiveGlobalMessagePrompt();
|
||||
this.receiveRoomEditionPrompt();
|
||||
}
|
||||
|
||||
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 (token !== ADMIN_API_TOKEN) {
|
||||
console.error('Admin access refused for token: '+token)
|
||||
res.writeStatus("401 Unauthorized").end('Incorrect token');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeof body.roomId !== 'string') {
|
||||
throw 'Incorrect roomId parameter'
|
||||
}
|
||||
const roomId: string = body.roomId;
|
||||
|
||||
await apiClientRepository.getClient(roomId).then((roomClient) =>{
|
||||
return new Promise((res, rej) => {
|
||||
const roomMessage = new RefreshRoomPromptMessage();
|
||||
roomMessage.setRoomid(roomId);
|
||||
|
||||
roomClient.sendRefreshRoomPrompt(roomMessage, (err) => {
|
||||
err ? rej(err) : res();
|
||||
});
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
this.errorToResponse(err, res);
|
||||
return;
|
||||
}
|
||||
|
||||
res.writeStatus("200");
|
||||
res.end('ok');
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
receiveGlobalMessagePrompt() {
|
||||
|
|
|
@ -13,8 +13,20 @@ export class BaseController {
|
|||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
protected errorToResponse(e: any, res: HttpResponse): void {
|
||||
console.error(e.message || "An error happened.", e?.config.url);
|
||||
console.error(e.stack || 'no stack defined.');
|
||||
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);
|
||||
|
|
|
@ -198,10 +198,10 @@ export class IoSocketController {
|
|||
memberMessages = userData.messages;
|
||||
memberTags = userData.tags;
|
||||
memberTextures = userData.textures;
|
||||
if (!room.anonymous && room.policyType === GameRoomPolicyTypes.USE_TAGS_POLICY && (userData.anonymous === true || !room.canAccess(memberTags))) {
|
||||
if (!room.public && room.policyType === GameRoomPolicyTypes.USE_TAGS_POLICY && (userData.anonymous === true || !room.canAccess(memberTags))) {
|
||||
throw new Error('No correct tags')
|
||||
}
|
||||
if (!room.anonymous && room.policyType === GameRoomPolicyTypes.MEMBERS_ONLY_POLICY && userData.anonymous === true) {
|
||||
if (!room.public && room.policyType === GameRoomPolicyTypes.MEMBERS_ONLY_POLICY && userData.anonymous === true) {
|
||||
throw new Error('No correct member')
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
|
@ -13,21 +13,22 @@ export enum GameRoomPolicyTypes {
|
|||
|
||||
export class PusherRoom {
|
||||
private readonly positionNotifier: PositionDispatcher;
|
||||
public readonly anonymous: boolean;
|
||||
public readonly public: boolean;
|
||||
public tags: string[];
|
||||
public policyType: GameRoomPolicyTypes;
|
||||
public readonly roomSlug: string;
|
||||
public readonly worldSlug: string = '';
|
||||
public readonly organizationSlug: string = '';
|
||||
private versionNumber: number = 1;
|
||||
|
||||
constructor(public readonly roomId: string,
|
||||
private socketListener: ZoneEventListener)
|
||||
{
|
||||
this.anonymous = isRoomAnonymous(roomId);
|
||||
this.public = isRoomAnonymous(roomId);
|
||||
this.tags = [];
|
||||
this.policyType = GameRoomPolicyTypes.ANONYMUS_POLICY;
|
||||
|
||||
if (this.anonymous) {
|
||||
if (this.public) {
|
||||
this.roomSlug = extractRoomSlugPublicRoomId(this.roomId);
|
||||
} else {
|
||||
const {organizationSlug, worldSlug, roomSlug} = extractDataFromPrivateRoomId(this.roomId);
|
||||
|
@ -55,4 +56,13 @@ export class PusherRoom {
|
|||
public isEmpty(): boolean {
|
||||
return this.positionNotifier.isEmpty();
|
||||
}
|
||||
|
||||
public needsUpdate(versionNumber: number): boolean {
|
||||
if (this.versionNumber < versionNumber) {
|
||||
this.versionNumber = versionNumber;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import {ADMIN_API_TOKEN, ADMIN_API_URL} from "../Enum/EnvironmentVariable";
|
||||
import Axios from "axios";
|
||||
import {GameRoomPolicyTypes} from "_Model/PusherRoom";
|
||||
|
||||
export interface AdminApiData {
|
||||
organizationSlug: string
|
||||
|
@ -13,6 +14,13 @@ export interface AdminApiData {
|
|||
textures: CharacterTexture[]
|
||||
}
|
||||
|
||||
export interface MapDetailsData {
|
||||
roomSlug: string,
|
||||
mapUrl: string,
|
||||
policy_type: GameRoomPolicyTypes,
|
||||
tags: string[],
|
||||
}
|
||||
|
||||
export interface AdminBannedData {
|
||||
is_banned: boolean,
|
||||
message: string
|
||||
|
@ -35,9 +43,9 @@ export interface FetchMemberDataByUuidResponse {
|
|||
|
||||
class AdminApi {
|
||||
|
||||
async fetchMapDetails(organizationSlug: string, worldSlug: string, roomSlug: string|undefined): Promise<AdminApiData> {
|
||||
async fetchMapDetails(organizationSlug: string, worldSlug: string, roomSlug: string|undefined): Promise<MapDetailsData> {
|
||||
if (!ADMIN_API_URL) {
|
||||
return Promise.reject('No admin backoffice set!');
|
||||
return Promise.reject(new Error('No admin backoffice set!'));
|
||||
}
|
||||
|
||||
const params: { organizationSlug: string, worldSlug: string, roomSlug?: string } = {
|
||||
|
@ -60,7 +68,7 @@ class AdminApi {
|
|||
|
||||
async fetchMemberDataByUuid(uuid: string, roomId: string): Promise<FetchMemberDataByUuidResponse> {
|
||||
if (!ADMIN_API_URL) {
|
||||
return Promise.reject('No admin backoffice set!');
|
||||
return Promise.reject(new Error('No admin backoffice set!'));
|
||||
}
|
||||
const res = await Axios.get(ADMIN_API_URL+'/api/room/access',
|
||||
{ params: {uuid, roomId}, headers: {"Authorization" : `${ADMIN_API_TOKEN}`} }
|
||||
|
@ -70,7 +78,7 @@ class AdminApi {
|
|||
|
||||
async fetchMemberDataByToken(organizationMemberToken: string): Promise<AdminApiData> {
|
||||
if (!ADMIN_API_URL) {
|
||||
return Promise.reject('No admin backoffice set!');
|
||||
return Promise.reject(new Error('No admin backoffice set!'));
|
||||
}
|
||||
//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,
|
||||
|
@ -81,7 +89,7 @@ class AdminApi {
|
|||
|
||||
async fetchCheckUserByToken(organizationMemberToken: string): Promise<AdminApiData> {
|
||||
if (!ADMIN_API_URL) {
|
||||
return Promise.reject('No admin backoffice set!');
|
||||
return Promise.reject(new Error('No admin backoffice set!'));
|
||||
}
|
||||
//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/check-user/'+organizationMemberToken,
|
||||
|
@ -104,7 +112,7 @@ class AdminApi {
|
|||
|
||||
async verifyBanUser(organizationMemberToken: string, ipAddress: string, organization: string, world: string): Promise<AdminBannedData> {
|
||||
if (!ADMIN_API_URL) {
|
||||
return Promise.reject('No admin backoffice set!');
|
||||
return Promise.reject(new Error('No admin backoffice set!'));
|
||||
}
|
||||
//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 + '/api/check-moderate-user/'+organization+'/'+world+'?ipAddress='+ipAddress+'&token='+organizationMemberToken,
|
||||
|
|
|
@ -22,7 +22,7 @@ import {
|
|||
WorldFullMessage,
|
||||
AdminPusherToBackMessage,
|
||||
ServerToAdminClientMessage,
|
||||
UserJoinedRoomMessage, UserLeftRoomMessage, AdminMessage, BanMessage
|
||||
UserJoinedRoomMessage, UserLeftRoomMessage, AdminMessage, BanMessage, RefreshRoomMessage
|
||||
} from "../Messages/generated/messages_pb";
|
||||
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
|
||||
import {JITSI_ISS, SECRET_JITSI_KEY} from "../Enum/EnvironmentVariable";
|
||||
|
@ -54,7 +54,7 @@ export interface AdminSocketData {
|
|||
|
||||
export class SocketManager implements ZoneEventListener {
|
||||
|
||||
private Worlds: Map<string, PusherRoom> = new Map<string, PusherRoom>();
|
||||
private rooms: Map<string, PusherRoom> = new Map<string, PusherRoom>();
|
||||
private sockets: Map<number, ExSocketInterface> = new Map<number, ExSocketInterface>();
|
||||
|
||||
constructor() {
|
||||
|
@ -182,6 +182,11 @@ export class SocketManager implements ZoneEventListener {
|
|||
// If this is the first message sent, send back the viewport.
|
||||
this.handleViewport(client, viewport);
|
||||
}
|
||||
|
||||
if (message.hasRefreshroommessage()) {
|
||||
const refreshMessage:RefreshRoomMessage = message.getRefreshroommessage() as unknown as RefreshRoomMessage;
|
||||
this.refreshRoomData(refreshMessage.getRoomid(), refreshMessage.getVersionnumber())
|
||||
}
|
||||
|
||||
// Let's pass data over from the back to the client.
|
||||
if (!client.disconnecting) {
|
||||
|
@ -221,7 +226,7 @@ export class SocketManager implements ZoneEventListener {
|
|||
try {
|
||||
client.viewport = viewport;
|
||||
|
||||
const world = this.Worlds.get(client.roomId);
|
||||
const world = this.rooms.get(client.roomId);
|
||||
if (!world) {
|
||||
console.error("In SET_VIEWPORT, could not find world with id '", client.roomId, "'");
|
||||
return;
|
||||
|
@ -312,12 +317,12 @@ export class SocketManager implements ZoneEventListener {
|
|||
if (socket.roomId) {
|
||||
try {
|
||||
//user leaves room
|
||||
const room: PusherRoom | undefined = this.Worlds.get(socket.roomId);
|
||||
const room: PusherRoom | undefined = this.rooms.get(socket.roomId);
|
||||
if (room) {
|
||||
debug('Leaving room %s.', socket.roomId);
|
||||
room.leave(socket);
|
||||
if (room.isEmpty()) {
|
||||
this.Worlds.delete(socket.roomId);
|
||||
this.rooms.delete(socket.roomId);
|
||||
debug('Room %s is empty. Deleting.', socket.roomId);
|
||||
}
|
||||
} else {
|
||||
|
@ -341,19 +346,23 @@ export class SocketManager implements ZoneEventListener {
|
|||
|
||||
async getOrCreateRoom(roomId: string): Promise<PusherRoom> {
|
||||
//check and create new world for a room
|
||||
let world = this.Worlds.get(roomId)
|
||||
let world = this.rooms.get(roomId)
|
||||
if(world === undefined){
|
||||
world = new PusherRoom(roomId, this);
|
||||
if (!world.anonymous) {
|
||||
const data = await adminApi.fetchMapDetails(world.organizationSlug, world.worldSlug, world.roomSlug)
|
||||
world.tags = data.tags
|
||||
world.policyType = Number(data.policy_type)
|
||||
if (!world.public) {
|
||||
await this.updateRoomWithAdminData(world);
|
||||
}
|
||||
this.Worlds.set(roomId, world);
|
||||
this.rooms.set(roomId, world);
|
||||
}
|
||||
return Promise.resolve(world)
|
||||
}
|
||||
|
||||
public async updateRoomWithAdminData(world: PusherRoom): Promise<void> {
|
||||
const data = await adminApi.fetchMapDetails(world.organizationSlug, world.worldSlug, world.roomSlug)
|
||||
world.tags = data.tags;
|
||||
world.policyType = Number(data.policy_type);
|
||||
}
|
||||
|
||||
emitPlayGlobalMessage(client: ExSocketInterface, playglobalmessage: PlayGlobalMessage) {
|
||||
const pusherToBackMessage = new PusherToBackMessage();
|
||||
pusherToBackMessage.setPlayglobalmessage(playglobalmessage);
|
||||
|
@ -362,7 +371,7 @@ export class SocketManager implements ZoneEventListener {
|
|||
}
|
||||
|
||||
public getWorlds(): Map<string, PusherRoom> {
|
||||
return this.Worlds;
|
||||
return this.rooms;
|
||||
}
|
||||
|
||||
searchClientByUuid(uuid: string): ExSocketInterface | null {
|
||||
|
@ -546,6 +555,14 @@ export class SocketManager implements ZoneEventListener {
|
|||
|
||||
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.
|
||||
if (!room || !room.needsUpdate(versionNumber)) return;
|
||||
|
||||
this.updateRoomWithAdminData(room);
|
||||
}
|
||||
}
|
||||
|
||||
export const socketManager = new SocketManager();
|
||||
|
|
|
@ -3032,9 +3032,9 @@ xtend@^4.0.0:
|
|||
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
|
||||
|
||||
y18n@^3.2.0:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
|
||||
integrity sha1-bRX7qITAhnnA136I53WegR4H+kE=
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.2.tgz#85c901bd6470ce71fc4bb723ad209b70f7f28696"
|
||||
integrity sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==
|
||||
|
||||
yallist@^3.0.0, yallist@^3.0.3:
|
||||
version "3.1.1"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue