FEATURE: editing a room in the admin trigger a refresh system

This commit is contained in:
kharhamel 2021-04-01 16:43:12 +02:00
parent 8529037493
commit 88cc15cd02
12 changed files with 148 additions and 118 deletions

View file

@ -1,9 +1,3 @@
import { Group } from "./Group";
import { PointInterface } from "./Websocket/PointInterface";
import {Zone} from "_Model/Zone";
import {Movable} from "_Model/Movable";
import {PositionNotifier} from "_Model/PositionNotifier";
import {ServerDuplexStream} from "grpc";
import {
BatchMessage,
PusherToBackMessage,
@ -11,7 +5,6 @@ import {
ServerToClientMessage,
SubMessage, UserJoinedRoomMessage, UserLeftRoomMessage
} from "../Messages/generated/messages_pb";
import {CharacterLayer} from "_Model/Websocket/CharacterLayer";
import {AdminSocket} from "../RoomManager";

View file

@ -38,12 +38,10 @@ export class GameRoom {
private readonly positionNotifier: PositionNotifier;
public readonly roomId: string;
public readonly anonymous: boolean;
public tags: string[];
public policyType: GameRoomPolicyTypes;
public readonly roomSlug: string;
public readonly worldSlug: string = '';
public readonly organizationSlug: string = '';
private versionNumber:number = 1;
private nextUserId: number = 1;
constructor(roomId: string,
@ -56,11 +54,8 @@ export class GameRoom {
onLeaves: LeavesCallback)
{
this.roomId = roomId;
this.anonymous = isRoomAnonymous(roomId);
this.tags = [];
this.policyType = GameRoomPolicyTypes.ANONYMOUS_POLICY;
if (this.anonymous) {
if (isRoomAnonymous(roomId)) {
this.roomSlug = extractRoomSlugPublicRoomId(this.roomId);
} else {
const {organizationSlug, worldSlug, roomSlug} = extractDataFromPrivateRoomId(this.roomId);
@ -304,10 +299,6 @@ export class GameRoom {
return this.itemsState;
}
public canAccess(userTags: string[]): boolean {
return arrayIntersect(userTags, this.tags);
}
public addZoneListener(call: ZoneSocket, x: number, y: number): Set<Movable> {
return this.positionNotifier.addZoneListener(call, x, y);
}
@ -328,4 +319,9 @@ export class GameRoom {
public adminLeave(admin: Admin): void {
this.admins.delete(admin);
}
public incrementVersion(): number {
this.versionNumber++
return this.versionNumber;
}
}

View file

@ -10,7 +10,7 @@ import {
JoinRoomMessage,
PlayGlobalMessage,
PusherToBackMessage,
QueryJitsiJwtMessage,
QueryJitsiJwtMessage, RefreshRoomPromptMessage,
ServerToAdminClientMessage,
ServerToClientMessage,
SilentMessage,
@ -193,6 +193,10 @@ const roomManager: IRoomManagerServer = {
socketManager.dispatchWorlFullWarning(call.request.getRoomid());
callback(null, new EmptyMessage());
},
sendRefreshRoomPrompt(call: ServerUnaryCall<RefreshRoomPromptMessage>, callback: sendUnaryData<EmptyMessage>): void {
socketManager.dispatchRoomRefresh(call.request.getRoomid());
callback(null, new EmptyMessage());
},
};
export {roomManager};

View file

@ -1,49 +0,0 @@
import {ADMIN_API_TOKEN, ADMIN_API_URL} from "../Enum/EnvironmentVariable";
import Axios from "axios";
export interface AdminApiData {
organizationSlug: string
worldSlug: string
roomSlug: string
mapUrlStart: string
tags: string[]
policy_type: number
userUuid: string
messages?: unknown[],
textures: CharacterTexture[]
}
export interface CharacterTexture {
id: number,
level: number,
url: string,
rights: string
}
class AdminApi {
async fetchMapDetails(organizationSlug: string, worldSlug: string, roomSlug: string|undefined): Promise<AdminApiData> {
if (!ADMIN_API_URL) {
return Promise.reject(new Error('No admin backoffice set!'));
}
const params: { organizationSlug: string, worldSlug: string, roomSlug?: string } = {
organizationSlug,
worldSlug
};
if (roomSlug) {
params.roomSlug = roomSlug;
}
const res = await Axios.get(ADMIN_API_URL + '/api/map',
{
headers: {"Authorization": `${ADMIN_API_TOKEN}`},
params
}
)
return res.data;
}
}
export const adminApi = new AdminApi();

View file

@ -26,7 +26,7 @@ import {
GroupLeftZoneMessage,
WorldFullWarningMessage,
UserLeftZoneMessage,
BanUserMessage,
BanUserMessage, RefreshRoomMessage,
} from "../Messages/generated/messages_pb";
import {User, UserSocket} from "../Model/User";
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
@ -41,7 +41,6 @@ import {
} from "../Enum/EnvironmentVariable";
import {Movable} from "../Model/Movable";
import {PositionInterface} from "../Model/PositionInterface";
import {adminApi, CharacterTexture} from "./AdminApi";
import Jwt from "jsonwebtoken";
import {JITSI_URL} from "../Enum/EnvironmentVariable";
import {clientEventsEmitter} from "./ClientEventsEmitter";
@ -129,15 +128,7 @@ export class SocketManager {
if (viewport === undefined) {
throw new Error('Viewport not found in message');
}
// sending to all clients in room except sender
/*client.position = {
x: position.x,
y: position.y,
direction,
moving: position.moving,
};
client.viewport = viewport;*/
// update position in the world
room.updatePosition(user, ProtobufUtils.toPointInterface(position));
@ -192,21 +183,6 @@ export class SocketManager {
}
}
// TODO: handle this message in pusher
/*async handleReportMessage(client: ExSocketInterface, reportPlayerMessage: ReportPlayerMessage) {
try {
const reportedSocket = this.sockets.get(reportPlayerMessage.getReporteduserid());
if (!reportedSocket) {
throw 'reported socket user not found';
}
//TODO report user on admin application
await adminApi.reportPlayer(reportedSocket.userUuid, reportPlayerMessage.getReportcomment(), client.userUuid)
} catch (e) {
console.error('An error occurred on "handleReportMessage"');
console.error(e);
}
}*/
emitVideo(room: GameRoom, user: User, data: WebRtcSignalToServerMessage): void {
//send only at user
const remoteUser = room.getUsers().get(data.getReceiverid());
@ -289,11 +265,6 @@ export class SocketManager {
(thing: Movable, position:PositionInterface, listener: ZoneSocket) => this.onClientMove(thing, position, listener),
(thing: Movable, newZone: Zone|null, listener: ZoneSocket) => this.onClientLeave(thing, newZone, listener)
);
if (!world.anonymous) {
const data = await adminApi.fetchMapDetails(world.organizationSlug, world.worldSlug, world.roomSlug)
world.tags = data.tags
world.policyType = Number(data.policy_type)
}
gaugeManager.incNbRoomGauge();
this.rooms.set(roomId, world);
}
@ -772,6 +743,25 @@ export class SocketManager {
recipient.socket.write(clientMessage);
});
}
dispatchRoomRefresh(roomId: string,): void {
const room = this.rooms.get(roomId);
if (!room) {
return;
}
const versionNumber = room.incrementVersion();
room.getUsers().forEach((recipient) => {
const worldFullMessage = new RefreshRoomMessage();
worldFullMessage.setRoomid(roomId)
worldFullMessage.setVersionnumber(versionNumber)
const clientMessage = new ServerToClientMessage();
clientMessage.setRefreshroommessage(worldFullMessage);
recipient.socket.write(clientMessage);
});
}
}
export const socketManager = new SocketManager();

View file

@ -187,6 +187,8 @@ export class RoomConnection implements RoomConnection {
adminMessagesService.onSendusermessage(message.getSendusermessage() as BanUserMessage);
} else if (message.hasWorldfullwarningmessage()) {
worldFullWarningStream.onMessage();
} else if (message.hasRefreshroommessage()) {
//todo: implement a way to notify the user the room was refreshed.
} else {
throw new Error('Unknown message received');
}

View file

@ -202,6 +202,13 @@ message WorldFullWarningMessage{
message WorldFullWarningToRoomMessage{
string roomId = 1;
}
message RefreshRoomPromptMessage{
string roomId = 1;
}
message RefreshRoomMessage{
string roomId = 1;
int32 versionNumber = 2;
}
message WorldFullMessage{
}
@ -229,6 +236,7 @@ message ServerToClientMessage {
AdminRoomMessage adminRoomMessage = 14;
WorldFullWarningMessage worldFullWarningMessage = 15;
WorldFullMessage worldFullMessage = 16;
RefreshRoomMessage refreshRoomMessage = 17;
}
}
@ -395,4 +403,5 @@ service RoomManager {
rpc ban(BanMessage) returns (EmptyMessage);
rpc sendAdminMessageToRoom(AdminRoomMessage) returns (EmptyMessage);
rpc sendWorldFullWarningToRoom(WorldFullWarningToRoomMessage) returns (EmptyMessage);
rpc sendRefreshRoomPrompt(RefreshRoomPromptMessage) returns (EmptyMessage);
}

View file

@ -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() {

View file

@ -190,10 +190,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) {

View file

@ -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;
}
}
}

View file

@ -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,7 +43,7 @@ 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(new Error('No admin backoffice set!'));
}

View file

@ -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() {
@ -180,6 +180,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) {
@ -219,7 +224,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;
@ -310,12 +315,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 {
@ -339,19 +344,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);
@ -360,7 +369,7 @@ export class SocketManager implements ZoneEventListener {
}
public getWorlds(): Map<string, PusherRoom> {
return this.Worlds;
return this.rooms;
}
searchClientByUuid(uuid: string): ExSocketInterface | null {
@ -544,6 +553,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();