diff --git a/back/src/Model/Admin.ts b/back/src/Model/Admin.ts index a121d105..0be74b85 100644 --- a/back/src/Model/Admin.ts +++ b/back/src/Model/Admin.ts @@ -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"; diff --git a/back/src/Model/GameRoom.ts b/back/src/Model/GameRoom.ts index 6a592ed0..c628d29d 100644 --- a/back/src/Model/GameRoom.ts +++ b/back/src/Model/GameRoom.ts @@ -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 { 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; + } } diff --git a/back/src/RoomManager.ts b/back/src/RoomManager.ts index 60e90d82..54215698 100644 --- a/back/src/RoomManager.ts +++ b/back/src/RoomManager.ts @@ -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, callback: sendUnaryData): void { + socketManager.dispatchRoomRefresh(call.request.getRoomid()); + callback(null, new EmptyMessage()); + }, }; export {roomManager}; diff --git a/back/src/Services/AdminApi.ts b/back/src/Services/AdminApi.ts deleted file mode 100644 index 0c53e3b3..00000000 --- a/back/src/Services/AdminApi.ts +++ /dev/null @@ -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 { - 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(); diff --git a/back/src/Services/SocketManager.ts b/back/src/Services/SocketManager.ts index c03f4773..16b4d005 100644 --- a/back/src/Services/SocketManager.ts +++ b/back/src/Services/SocketManager.ts @@ -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(); diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index 9efd5433..0220cb52 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -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'); } diff --git a/messages/protos/messages.proto b/messages/protos/messages.proto index cc23ed24..6f8ed036 100644 --- a/messages/protos/messages.proto +++ b/messages/protos/messages.proto @@ -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); } diff --git a/pusher/src/Controller/AdminController.ts b/pusher/src/Controller/AdminController.ts index 3f87fcf1..74d4e792 100644 --- a/pusher/src/Controller/AdminController.ts +++ b/pusher/src/Controller/AdminController.ts @@ -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() { diff --git a/pusher/src/Controller/IoSocketController.ts b/pusher/src/Controller/IoSocketController.ts index 85a80e11..d4b0f98e 100644 --- a/pusher/src/Controller/IoSocketController.ts +++ b/pusher/src/Controller/IoSocketController.ts @@ -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) { diff --git a/pusher/src/Model/PusherRoom.ts b/pusher/src/Model/PusherRoom.ts index 92ff87d1..dcea5859 100644 --- a/pusher/src/Model/PusherRoom.ts +++ b/pusher/src/Model/PusherRoom.ts @@ -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; + } + } } diff --git a/pusher/src/Services/AdminApi.ts b/pusher/src/Services/AdminApi.ts index 0cc6d43e..bd8edeb6 100644 --- a/pusher/src/Services/AdminApi.ts +++ b/pusher/src/Services/AdminApi.ts @@ -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 { + async fetchMapDetails(organizationSlug: string, worldSlug: string, roomSlug: string|undefined): Promise { if (!ADMIN_API_URL) { return Promise.reject(new Error('No admin backoffice set!')); } diff --git a/pusher/src/Services/SocketManager.ts b/pusher/src/Services/SocketManager.ts index 9b698e38..f0734b94 100644 --- a/pusher/src/Services/SocketManager.ts +++ b/pusher/src/Services/SocketManager.ts @@ -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 = new Map(); + private rooms: Map = new Map(); private sockets: Map = new Map(); 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 { //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 { + 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 { - 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();